@@ -3227,6 +3227,9 @@ class TemplateSet {
32273227 }
32283228 }
32293229 this . getRawTemplate = config . getTemplate ;
3230+ this . customDirectives = config . customDirectives || { } ;
3231+ this . runtimeUtils = { ...helpers , __globals__ : config . globalValues || { } } ;
3232+ this . hasGlobalValues = Boolean ( config . globalValues && Object . keys ( config . globalValues ) . length ) ;
32303233 }
32313234 static registerTemplate ( name , fn ) {
32323235 globalTemplates [ name ] = fn ;
@@ -3283,7 +3286,7 @@ class TemplateSet {
32833286 this . templates [ name ] = function ( context , parent ) {
32843287 return templates [ name ] . call ( this , context , parent ) ;
32853288 } ;
3286- const template = templateFn ( this , bdom , helpers ) ;
3289+ const template = templateFn ( this , bdom , this . runtimeUtils ) ;
32873290 this . templates [ name ] = template ;
32883291 }
32893292 return this . templates [ name ] ;
@@ -3334,7 +3337,7 @@ TemplateSet.registerTemplate("__portal__", portalTemplate);
33343337//------------------------------------------------------------------------------
33353338// Misc types, constants and helpers
33363339//------------------------------------------------------------------------------
3337- const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date" . split ( "," ) ;
3340+ const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date,__globals__ " . split ( "," ) ;
33383341const WORD_REPLACEMENT = Object . assign ( Object . create ( null ) , {
33393342 and : "&&" ,
33403343 or : "||" ,
@@ -3806,6 +3809,9 @@ class CodeGenerator {
38063809 this . dev = options . dev || false ;
38073810 this . ast = ast ;
38083811 this . templateName = options . name ;
3812+ if ( options . hasGlobalValues ) {
3813+ this . helpers . add ( "__globals__" ) ;
3814+ }
38093815 }
38103816 generateCode ( ) {
38113817 const ast = this . ast ;
@@ -4844,29 +4850,33 @@ class CodeGenerator {
48444850// Parser
48454851// -----------------------------------------------------------------------------
48464852const cache = new WeakMap ( ) ;
4847- function parse ( xml ) {
4853+ function parse ( xml , customDir ) {
4854+ const ctx = {
4855+ inPreTag : false ,
4856+ customDirectives : customDir ,
4857+ } ;
48484858 if ( typeof xml === "string" ) {
48494859 const elem = parseXML ( `<t>${ xml } </t>` ) . firstChild ;
4850- return _parse ( elem ) ;
4860+ return _parse ( elem , ctx ) ;
48514861 }
48524862 let ast = cache . get ( xml ) ;
48534863 if ( ! ast ) {
48544864 // we clone here the xml to prevent modifying it in place
4855- ast = _parse ( xml . cloneNode ( true ) ) ;
4865+ ast = _parse ( xml . cloneNode ( true ) , ctx ) ;
48564866 cache . set ( xml , ast ) ;
48574867 }
48584868 return ast ;
48594869}
4860- function _parse ( xml ) {
4870+ function _parse ( xml , ctx ) {
48614871 normalizeXML ( xml ) ;
4862- const ctx = { inPreTag : false } ;
48634872 return parseNode ( xml , ctx ) || { type : 0 /* Text */ , value : "" } ;
48644873}
48654874function parseNode ( node , ctx ) {
48664875 if ( ! ( node instanceof Element ) ) {
48674876 return parseTextCommentNode ( node , ctx ) ;
48684877 }
4869- return ( parseTDebugLog ( node , ctx ) ||
4878+ return ( parseTCustom ( node , ctx ) ||
4879+ parseTDebugLog ( node , ctx ) ||
48704880 parseTForEach ( node , ctx ) ||
48714881 parseTIf ( node , ctx ) ||
48724882 parseTPortal ( node , ctx ) ||
@@ -4908,6 +4918,35 @@ function parseTextCommentNode(node, ctx) {
49084918 }
49094919 return null ;
49104920}
4921+ function parseTCustom ( node , ctx ) {
4922+ if ( ! ctx . customDirectives ) {
4923+ return null ;
4924+ }
4925+ const nodeAttrsNames = node . getAttributeNames ( ) ;
4926+ for ( let attr of nodeAttrsNames ) {
4927+ if ( attr === "t-custom" || attr === "t-custom-" ) {
4928+ throw new OwlError ( "Missing custom directive name with t-custom directive" ) ;
4929+ }
4930+ if ( attr . startsWith ( "t-custom-" ) ) {
4931+ const directiveName = attr . split ( "." ) [ 0 ] . slice ( 9 ) ;
4932+ const customDirective = ctx . customDirectives [ directiveName ] ;
4933+ if ( ! customDirective ) {
4934+ throw new OwlError ( `Custom directive "${ directiveName } " is not defined` ) ;
4935+ }
4936+ const value = node . getAttribute ( attr ) ;
4937+ const modifier = attr . split ( "." ) . length > 1 ? attr . split ( "." ) [ 1 ] : undefined ;
4938+ node . removeAttribute ( attr ) ;
4939+ try {
4940+ customDirective ( node , value , modifier ) ;
4941+ }
4942+ catch ( error ) {
4943+ throw new OwlError ( `Custom directive "${ directiveName } " throw the following error: ${ error } ` ) ;
4944+ }
4945+ return parseNode ( node , ctx ) ;
4946+ }
4947+ }
4948+ return null ;
4949+ }
49114950// -----------------------------------------------------------------------------
49124951// debugging
49134952// -----------------------------------------------------------------------------
@@ -5539,9 +5578,11 @@ function normalizeXML(el) {
55395578 normalizeTEscTOut ( el ) ;
55405579}
55415580
5542- function compile ( template , options = { } ) {
5581+ function compile ( template , options = {
5582+ hasGlobalValues : false ,
5583+ } ) {
55435584 // parsing
5544- const ast = parse ( template ) ;
5585+ const ast = parse ( template , options . customDirectives ) ;
55455586 // some work
55465587 const hasSafeContext = template instanceof Node
55475588 ? ! ( template instanceof Element ) || template . querySelector ( "[t-set], [t-call]" ) === null
@@ -5563,7 +5604,7 @@ function compile(template, options = {}) {
55635604}
55645605
55655606// do not modify manually. This file is generated by the release script.
5566- const version = "2.4.1 " ;
5607+ const version = "2.5.0 " ;
55675608
55685609// -----------------------------------------------------------------------------
55695610// Scheduler
@@ -5642,7 +5683,14 @@ class Scheduler {
56425683 if ( ! hasError ) {
56435684 fiber . complete ( ) ;
56445685 }
5645- this . tasks . delete ( fiber ) ;
5686+ // at this point, the fiber should have been applied to the DOM, so we can
5687+ // remove it from the task list. If it is not the case, it means that there
5688+ // was an error and an error handler triggered a new rendering that recycled
5689+ // the fiber, so in that case, we actually want to keep the fiber around,
5690+ // otherwise it will just be ignored.
5691+ if ( fiber . appliedToDom ) {
5692+ this . tasks . delete ( fiber ) ;
5693+ }
56465694 }
56475695 }
56485696}
@@ -6026,12 +6074,14 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
60266074 dev : this . dev ,
60276075 translateFn : this . translateFn ,
60286076 translatableAttributes : this . translatableAttributes ,
6077+ customDirectives : this . customDirectives ,
6078+ hasGlobalValues : this . hasGlobalValues ,
60296079 } ) ;
60306080} ;
60316081
60326082export { App , Component , EventBus , OwlError , __info__ , batched , blockDom , loadFile , markRaw , markup , mount , onError , onMounted , onPatched , onRendered , onWillDestroy , onWillPatch , onWillRender , onWillStart , onWillUnmount , onWillUpdateProps , reactive , status , toRaw , useChildSubEnv , useComponent , useEffect , useEnv , useExternalListener , useRef , useState , useSubEnv , validate , validateType , whenReady , xml } ;
60336083
60346084
6035- __info__ . date = '2024-10-31T09:42:30.824Z ' ;
6036- __info__ . hash = 'b8d09e5 ' ;
6085+ __info__ . date = '2024-11-25T09:30:45.930Z ' ;
6086+ __info__ . hash = '6b24864 ' ;
60376087__info__ . url = 'https://github.com/odoo/owl' ;
0 commit comments