@@ -3227,6 +3227,9 @@ class TemplateSet {
3227
3227
}
3228
3228
}
3229
3229
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 ) ;
3230
3233
}
3231
3234
static registerTemplate ( name , fn ) {
3232
3235
globalTemplates [ name ] = fn ;
@@ -3283,7 +3286,7 @@ class TemplateSet {
3283
3286
this . templates [ name ] = function ( context , parent ) {
3284
3287
return templates [ name ] . call ( this , context , parent ) ;
3285
3288
} ;
3286
- const template = templateFn ( this , bdom , helpers ) ;
3289
+ const template = templateFn ( this , bdom , this . runtimeUtils ) ;
3287
3290
this . templates [ name ] = template ;
3288
3291
}
3289
3292
return this . templates [ name ] ;
@@ -3334,7 +3337,7 @@ TemplateSet.registerTemplate("__portal__", portalTemplate);
3334
3337
//------------------------------------------------------------------------------
3335
3338
// Misc types, constants and helpers
3336
3339
//------------------------------------------------------------------------------
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 ( "," ) ;
3338
3341
const WORD_REPLACEMENT = Object . assign ( Object . create ( null ) , {
3339
3342
and : "&&" ,
3340
3343
or : "||" ,
@@ -3806,6 +3809,9 @@ class CodeGenerator {
3806
3809
this . dev = options . dev || false ;
3807
3810
this . ast = ast ;
3808
3811
this . templateName = options . name ;
3812
+ if ( options . hasGlobalValues ) {
3813
+ this . helpers . add ( "__globals__" ) ;
3814
+ }
3809
3815
}
3810
3816
generateCode ( ) {
3811
3817
const ast = this . ast ;
@@ -4844,29 +4850,33 @@ class CodeGenerator {
4844
4850
// Parser
4845
4851
// -----------------------------------------------------------------------------
4846
4852
const cache = new WeakMap ( ) ;
4847
- function parse ( xml ) {
4853
+ function parse ( xml , customDir ) {
4854
+ const ctx = {
4855
+ inPreTag : false ,
4856
+ customDirectives : customDir ,
4857
+ } ;
4848
4858
if ( typeof xml === "string" ) {
4849
4859
const elem = parseXML ( `<t>${ xml } </t>` ) . firstChild ;
4850
- return _parse ( elem ) ;
4860
+ return _parse ( elem , ctx ) ;
4851
4861
}
4852
4862
let ast = cache . get ( xml ) ;
4853
4863
if ( ! ast ) {
4854
4864
// we clone here the xml to prevent modifying it in place
4855
- ast = _parse ( xml . cloneNode ( true ) ) ;
4865
+ ast = _parse ( xml . cloneNode ( true ) , ctx ) ;
4856
4866
cache . set ( xml , ast ) ;
4857
4867
}
4858
4868
return ast ;
4859
4869
}
4860
- function _parse ( xml ) {
4870
+ function _parse ( xml , ctx ) {
4861
4871
normalizeXML ( xml ) ;
4862
- const ctx = { inPreTag : false } ;
4863
4872
return parseNode ( xml , ctx ) || { type : 0 /* Text */ , value : "" } ;
4864
4873
}
4865
4874
function parseNode ( node , ctx ) {
4866
4875
if ( ! ( node instanceof Element ) ) {
4867
4876
return parseTextCommentNode ( node , ctx ) ;
4868
4877
}
4869
- return ( parseTDebugLog ( node , ctx ) ||
4878
+ return ( parseTCustom ( node , ctx ) ||
4879
+ parseTDebugLog ( node , ctx ) ||
4870
4880
parseTForEach ( node , ctx ) ||
4871
4881
parseTIf ( node , ctx ) ||
4872
4882
parseTPortal ( node , ctx ) ||
@@ -4908,6 +4918,35 @@ function parseTextCommentNode(node, ctx) {
4908
4918
}
4909
4919
return null ;
4910
4920
}
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
+ }
4911
4950
// -----------------------------------------------------------------------------
4912
4951
// debugging
4913
4952
// -----------------------------------------------------------------------------
@@ -5539,9 +5578,11 @@ function normalizeXML(el) {
5539
5578
normalizeTEscTOut ( el ) ;
5540
5579
}
5541
5580
5542
- function compile ( template , options = { } ) {
5581
+ function compile ( template , options = {
5582
+ hasGlobalValues : false ,
5583
+ } ) {
5543
5584
// parsing
5544
- const ast = parse ( template ) ;
5585
+ const ast = parse ( template , options . customDirectives ) ;
5545
5586
// some work
5546
5587
const hasSafeContext = template instanceof Node
5547
5588
? ! ( template instanceof Element ) || template . querySelector ( "[t-set], [t-call]" ) === null
@@ -5563,7 +5604,7 @@ function compile(template, options = {}) {
5563
5604
}
5564
5605
5565
5606
// do not modify manually. This file is generated by the release script.
5566
- const version = "2.4.1 " ;
5607
+ const version = "2.5.0 " ;
5567
5608
5568
5609
// -----------------------------------------------------------------------------
5569
5610
// Scheduler
@@ -5642,7 +5683,14 @@ class Scheduler {
5642
5683
if ( ! hasError ) {
5643
5684
fiber . complete ( ) ;
5644
5685
}
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
+ }
5646
5694
}
5647
5695
}
5648
5696
}
@@ -6026,12 +6074,14 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
6026
6074
dev : this . dev ,
6027
6075
translateFn : this . translateFn ,
6028
6076
translatableAttributes : this . translatableAttributes ,
6077
+ customDirectives : this . customDirectives ,
6078
+ hasGlobalValues : this . hasGlobalValues ,
6029
6079
} ) ;
6030
6080
} ;
6031
6081
6032
6082
export { 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 } ;
6033
6083
6034
6084
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 ' ;
6037
6087
__info__ . url = 'https://github.com/odoo/owl' ;
0 commit comments