@webqit/oohtml 4.3.0 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "wicg-proposal"
15
15
  ],
16
16
  "homepage": "https://webqit.io/tooling/oohtml",
17
- "version": "4.3.0",
17
+ "version": "4.3.2",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@webqit/quantum-js": "^4.5.11",
43
- "@webqit/realdom": "^2.1.30",
43
+ "@webqit/realdom": "^2.1.31",
44
44
  "@webqit/util": "^0.8.11"
45
45
  },
46
46
  "devDependencies": {
@@ -38,14 +38,20 @@ export default function init( $config = {} ) {
38
38
  function realtime( config ) {
39
39
  const window = this, { webqit: { realdom } } = window;
40
40
  // ----------------
41
- realdom.realtime( window.document ).query( `(${ config.discreteBindingsSelector })`, record => {
42
- cleanup.call( this, ...record.exits );
43
- mountDiscreteBindings.call( window, config, ...record.entrants );
44
- }, { live: true, subtree: 'cross-roots', timing: 'sync' } );
41
+ /**
42
+ * For an element, render should happen first
43
+ <div render="">
44
+ <?{ content }?>
45
+ </div>
46
+ */
45
47
  realdom.realtime( window.document ).query( config.attrSelector, record => {
46
48
  cleanup.call( this, ...record.exits );
47
49
  mountInlineBindings.call( window, config, ...record.entrants );
48
- }, { live: true, subtree: 'cross-roots', timing: 'sync', eventDetails: true, staticSensitivity: true } );
50
+ }, { id: 'data-binding:attr', live: true, subtree: 'cross-roots', timing: 'sync', eventDetails: true, staticSensitivity: true } );
51
+ realdom.realtime( window.document ).query( `(${ config.discreteBindingsSelector })`, record => {
52
+ cleanup.call( this, ...record.exits );
53
+ mountDiscreteBindings.call( window, config, ...record.entrants );
54
+ }, { id: 'data-binding:descrete', live: true, subtree: 'cross-roots', timing: 'sync' } );
49
55
  }
50
56
 
51
57
  function createDynamicScope( config, root ) {
@@ -54,16 +60,17 @@ function createDynamicScope( config, root ) {
54
60
  const scope = Object.create( null ), abortController = new AbortController;
55
61
  scope[ '$exec__' ] = ( target, prop, ...args ) => {
56
62
  const exec = () => {
57
- try { target[ prop ]( ...args ); } catch( e ) { console.error( `Error executing "${ prop }": ${ e.message } at ${ e.cause }` ); }
63
+ try { target[ prop ]( ...args ); }
64
+ catch( e ) { console.error( `Error executing "${ prop }": ${ e.message } at ${ e.cause }` ); }
58
65
  };
59
- realdom.schedule( 'write', exec );
66
+ exec();
60
67
  };
61
68
  scope[ '$assign__' ] = ( target, prop, val ) => {
62
69
  const exec = () => {
63
70
  try { target[ prop ] = val; }
64
71
  catch( e ) { console.error( `${ e.message } at ${ e.cause }` ); }
65
72
  };
66
- realdom.schedule( 'write', exec );
73
+ exec();
67
74
  };
68
75
  Observer.intercept( scope, {
69
76
  get: ( e, recieved, next ) => {
@@ -172,6 +179,7 @@ const inlineParseCache = new Map;
172
179
  function compileInlineBindings( config, str ) {
173
180
  if ( inlineParseCache.has( str ) ) return inlineParseCache.get( str );
174
181
  const validation = {};
182
+ let $event_i = -1;
175
183
  const source = _splitOuter( str, ';' ).map( str => {
176
184
  const [ left, right ] = _splitOuter( str, ':' ).map( x => x.trim() );
177
185
  const directive = left[ 0 ], param = left.slice( 1 ).trim();
@@ -241,11 +249,12 @@ function compileInlineBindings( config, str ) {
241
249
  }
242
250
  // Events
243
251
  if ( directive === '@' ) {
252
+ $event_i++;
244
253
  return `
245
- const handler = event => ${ right.startsWith('{') ? right : arg };
246
- this.addEventListener( '${ param }', handler );
247
- const abort = () => this.removeEventListener( '${ param }', handler );
248
- this.$oohtml_internal_databinding_signals?.push( { abort } );
254
+ const handler${ $event_i } = event => ${ right.startsWith('{') ? right : arg };
255
+ this.addEventListener( '${ param }', handler${ $event_i } );
256
+ const abort${ $event_i } = () => this.removeEventListener( '${ param }', handler${ $event_i } );
257
+ this.$oohtml_internal_databinding_signals?.push( { abort: abort${ $event_i } } );
249
258
  `;
250
259
  }
251
260
  // Functions
@@ -264,11 +273,12 @@ const escDouble = str => str.replace(/"/g, '\\"');
264
273
 
265
274
  export function idleCompiler( node ) {
266
275
  const window = this, { webqit: { oohtml: { configs: { DATA_BINDING: config } } } } = window;
276
+ // Attr selector must also come first, as in above
277
+ ( node?.matches( config.attrSelector ) ? [ node ] : [] ).concat([ ...( node?.querySelectorAll( config.attrSelector ) || [] ) ]).forEach( node => {
278
+ compileInlineBindings.call( window, config, node.getAttribute( config.attr.render ) );
279
+ } );
267
280
  xpathQuery( window, node, `(${ config.discreteBindingsSelector })` ).forEach( node => {
268
281
  const template = patternMatch( config, node.nodeValue );
269
282
  compileDiscreteBindings.call( window, config, template.expr );
270
283
  } );
271
- ( node?.matches( config.attrSelector ) ? [ node ] : [] ).concat([ ...( node?.querySelectorAll( config.attrSelector ) || [] ) ]).forEach( node => {
272
- compileInlineBindings.call( window, config, node.getAttribute( config.attr.render ) );
273
- } );
274
284
  }
@@ -63,6 +63,7 @@ export default function() {
63
63
  } );
64
64
  }, { live: true, timing: 'sync', lifecycleSignals: true } );
65
65
  priv.autoDestroyRealtime = realdom.realtime( window.document ).track( parentNode, () => {
66
+ this.state = null;
66
67
  priv.die();
67
68
  }, { subtree: 'cross-roots', timing: 'sync', generation: 'exits' } );
68
69
  };
@@ -97,9 +98,8 @@ export default function() {
97
98
  const restore = () => {
98
99
  if (this.el.isConnected) return;
99
100
  this.el.setAttribute( 'data-nodecount', 0 );
100
- this.el.slottingAction = true;
101
+ this.state = 'restored';
101
102
  priv.anchorNode.replaceWith( this.el );
102
- this.el.slottingAction = false;
103
103
  priv.setAnchorNode( null );
104
104
  };
105
105
  if ( !priv.slottedElements.size ) return restore();
@@ -119,12 +119,12 @@ export default function() {
119
119
  };
120
120
 
121
121
  priv.connectedCallback = () => {
122
- if ( this.el.slottingAction ) return;
122
+ if ( this.state === 'restored' ) return;
123
123
  priv.live( fragments => this.fill( fragments ) );
124
124
  };
125
125
 
126
126
  priv.disconnectedCallback = () => {
127
- if ( this.el.slottingAction ) return;
127
+ if ( this.state === 'resolved' ) return;
128
128
  priv.die();
129
129
  };
130
130
  }
@@ -175,9 +175,8 @@ export default function() {
175
175
  // not the import element itslef - but all only when we have slottableElements.size
176
176
  if ( slottableElements.size && this.el.isConnected ) {
177
177
  const newAnchorNode = this[ '#' ].setAnchorNode( this.createAnchorNode() );
178
- this.el.slottingAction = true;
178
+ this.state = 'resolved';
179
179
  this.el.replaceWith( newAnchorNode );
180
- this.el.slottingAction = false;
181
180
  }
182
181
  // Insert slottables now
183
182
  slottableElements.forEach( slottableElement => {
@@ -154,6 +154,7 @@ function realtime(config) {
154
154
  record.exits.forEach(entry => {
155
155
  if (entry.matches(config.templateSelector)) {
156
156
  const htmlModule = HTMLModule.instance(entry);
157
+ //if (!htmlModule.ownerContext) return; // JSDOM sometimes
157
158
  const ownerContextModulesObj = getDefs(htmlModule.ownerContext);
158
159
  if (htmlModule.defId && htmlModule.ownerContext.isConnected) { Observer.deleteProperty(ownerContextModulesObj, htmlModule.defId); }
159
160
  detachImportsContext(htmlModule.ownerContext);
@@ -161,7 +162,7 @@ function realtime(config) {
161
162
  detachImportsContext(entry);
162
163
  }
163
164
  });
164
- }, { live: true, subtree: 'cross-roots', timing: 'sync', staticSensitivity: true, eventDetails: true });
165
+ }, { id: 'imports:template/importscontext', live: true, subtree: 'cross-roots', timing: 'sync', staticSensitivity: true, recursiveOk: true, eventDetails: true });
165
166
 
166
167
  // ------------
167
168
  // IMPORTS
@@ -169,7 +170,7 @@ function realtime(config) {
169
170
  realdom.realtime(window.document).query(config.elements.import, record => {
170
171
  record.entrants.forEach(node => handleRealtime(node, true, record));
171
172
  record.exits.forEach(node => handleRealtime(node, false, record));
172
- }, { live: true, subtree: 'cross-roots', timing: 'sync' });
173
+ }, { id: 'imports:import', live: true, subtree: 'cross-roots', timing: 'sync', recursiveOk: true });
173
174
  function handleRealtime(entry, connectedState) {
174
175
  const elInstance = HTMLImportElement.instance(entry);
175
176
  if (connectedState) { elInstance['#'].connectedCallback(); }
@@ -192,5 +193,5 @@ function realtime(config) {
192
193
  }
193
194
  HTMLImportElement.instance(importEl)['#'].hydrate(anchorNode, slottedElements);
194
195
  });
195
- }, { live: true, subtree: 'cross-roots', timing: 'sync' });
196
+ }, { id: 'imports:hydration', live: true, subtree: 'cross-roots', timing: 'sync', recursiveOk: true });
196
197
  }
package/src/init.js CHANGED
@@ -19,10 +19,10 @@ export default function init( QuantumJS, configs = {} ) {
19
19
  // --------------
20
20
  ContextAPI.call( this, ( configs.CONTEXT_API || {} ) );
21
21
  BindingsAPI.call( this, ( configs.BINDINGS_API || {} ) ); // Depends on ContextAPI
22
- NamespacedHTML.call( this, ( configs.NAMESPACED_HTML || {} ) ); // Depends on ContextAPI
22
+ // Imports must happen before the rest... structure must be flattened before the other things below which query the DOM
23
23
  HTMLImports.call( this, { ...( configs.HTML_IMPORTS || {} ), idleCompilers: [ idleCompiler1, idleCompiler2 ] } ); // Depends on ContextAPI
24
+ NamespacedHTML.call( this, ( configs.NAMESPACED_HTML || {} ) ); // Depends on ContextAPI
24
25
  DataBinding.call( this, ( configs.DATA_BINDING || {} ) ); // Depends on ContextAPI, BindingsAPI, HTMLImports
25
26
  ScopedCSS.call( this, ( configs.SCOPED_CSS || {} ) ); // Depends on NamespacedHTML
26
27
  ScopedJS.call( this, ( configs.SCOPED_JS || {} ) );
27
- // --------------
28
28
  }
@@ -38,7 +38,7 @@ function lidUtil( config ) {
38
38
  lidrefPrefix( escapeMode = 0 ) { return escapeMode ? this.escape( lidrefPrefix, escapeMode ) : lidrefPrefix; },
39
39
  lidrefSeparator( escapeMode = 0 ) { return escapeMode ? this.escape( lidrefSeparator, escapeMode ) : lidrefSeparator; },
40
40
  isUuid( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && str.includes( this.lidrefSeparator( escapeMode ) ); },
41
- isLidref( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && !str.includes( this.lidrefSeparator( escapeMode ) ); },
41
+ //isLidref( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && !str.includes( this.lidrefSeparator( escapeMode ) ); },
42
42
  toUuid( hash, lid, escapeMode = 0 ) { return hash.endsWith( '-root' ) ? lid : `${ this.lidrefPrefix( escapeMode ) }${ hash }${ this.lidrefSeparator( escapeMode ) }${ lid }`; },
43
43
  uuidToId( str, escapeMode = 0 ) { return this.isUuid( str ) ? str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] : str; },
44
44
  uuidToLidref( str, escapeMode = 0 ) { return this.isUuid( str ) ? `${ this.lidrefPrefix( escapeMode ) }${ str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] }` : str; },
@@ -278,7 +278,7 @@ function realtime( config ) {
278
278
  }
279
279
  } else {
280
280
  _( entry, 'attrOriginals' ).set( attrName, value ); // Save original before rewrite
281
- const newAttrValue = value.split( ' ' ).map( idref => ( idref = idref.trim() ) && !$lidUtil.isLidref( idref ) ? idref : $lidUtil.toUuid( namespaceUUID, idref.replace( $lidUtil.lidrefPrefix(), '' ) ) ).join( ' ' );
281
+ const newAttrValue = value.split( ' ' ).map( idref => ( idref = idref.trim() ) && $lidUtil.isUuid( idref ) ? idref : $lidUtil.toUuid( namespaceUUID, idref ) ).join( ' ' );
282
282
  entry.setAttribute( attrName, newAttrValue );
283
283
  _( namespaceObj ).set( 'idrefs', _( namespaceObj ).get( 'idrefs' ) || new Set );
284
284
  _( namespaceObj ).get( 'idrefs' ).add( entry );
@@ -296,7 +296,7 @@ function realtime( config ) {
296
296
  }
297
297
  } else {
298
298
  const newAttrValue = _( entry, 'attrOriginals' ).get( attrName );// oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
299
- entry.setAttribute( attrName, newAttrValue );
299
+ if ( entry.hasAttribute( attrName ) ) entry.setAttribute( attrName, newAttrValue );
300
300
  _( namespaceObj ).get( 'idrefs' )?.delete( entry );
301
301
  }
302
302
  } );
@@ -338,9 +338,10 @@ function realtime( config ) {
338
338
  const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
339
339
  if ( !contextsApi.find( DOMNamingContext.kind ) ) { contextsApi.attach( new DOMNamingContext ); }
340
340
  } );
341
- }, { live: true, subtree: 'cross-roots', timing: 'sync', staticSensitivity: true, eventDetails: true } );
341
+ }, { id: 'namespace-html:namespace', live: true, subtree: 'cross-roots', timing: 'sync', staticSensitivity: true, eventDetails: true } );
342
342
 
343
343
  // DOM realtime
344
+ // ISSUE implied elements that are direct chidren of shadow roots not caught. Maybe try testing with params.recursiveOk to see.
344
345
  realdom.realtime( window.document ).query( `[${ attrList.map( attrName => window.CSS.escape( attrName ) ).join( '],[' ) }]`, record => {
345
346
  // We do some caching to prevent redundanct lookups
346
347
  const namespaceNodesToTest = { forID: new Map, forOther: new Map, };
@@ -365,14 +366,14 @@ function realtime( config ) {
365
366
  }
366
367
  namespaceNodesToTest.forID.clear();
367
368
  namespaceNodesToTest.forOther.clear();
368
- }, { live: true, subtree: 'cross-roots', timing: 'sync' } );
369
+ }, { id: 'namespace-html:attrs', live: true, subtree: 'cross-roots', timing: 'sync' } );
369
370
  // Attr realtime
370
371
  realdom.realtime( window.document, 'attr' ).observe( attrList, records => {
371
372
  for ( const record of records ) {
372
373
  if ( record.oldValue ) { cleanupBinding( record.target, record.name, record.oldValue/* Current resolved value as-is */ ); }
373
374
  if ( record.value ) { setupBinding( record.target, record.name, record.value/* Raw value (as-is) that will be saved as original */ ); }
374
375
  }
375
- }, { subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true } );
376
+ }, { id: 'namespace-html:attr(attrs)', subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true } );
376
377
 
377
378
  // ------------
378
379
  // TARGETS
@@ -100,7 +100,7 @@ function realtime( config ) {
100
100
  else { setTimeout( () => { transform(); }, 0 ); }
101
101
  }
102
102
  } );
103
- }, { live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants' } );
103
+ }, { id: 'scoped-css', live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants' } );
104
104
  // ---
105
105
  }
106
106
 
@@ -16,10 +16,8 @@ export default function init({ advanced = {}, ...$config }) {
16
16
  api: { scripts: 'scripts' },
17
17
  advanced: resolveParams(advanced),
18
18
  } );
19
- config.scriptSelector = ( Array.isArray( config.script.mimeTypes ) ? config.script.mimeTypes : config.script.mimeTypes.split( '|' ) ).concat( '' ).reduce( ( selector, mm ) => {
20
- const qualifier = mm ? `[type="${ window.CSS.escape( mm ) }"]` : ':not([type])';
21
- return selector.concat( `script${ qualifier }` );
22
- }, [] ).join( ',' );
19
+ const customTypes = Array.isArray( config.script.mimeTypes ) ? config.script.mimeTypes : config.script.mimeTypes.split( '|' ).filter( t => t );
20
+ config.scriptSelector = customTypes.map( t => `script[type="${ window.CSS.escape( t ) }"]`).concat(`script:not([type])`).join( ',' );
23
21
  window.webqit.oohtml.Script = {
24
22
  compileCache: [ new Map, new Map, ],
25
23
  execute: execute.bind( window, config ),
@@ -38,10 +36,12 @@ export default function init({ advanced = {}, ...$config }) {
38
36
  function exposeAPIs( config ) {
39
37
  const window = this, scriptsMap = new Map;
40
38
  if ( config.api.scripts in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.api.scripts }" property!` ); }
41
- Object.defineProperty( window.Element.prototype, config.api.scripts, { get: function() {
42
- if ( !scriptsMap.has( this ) ) { scriptsMap.set( this, [] ); }
43
- return scriptsMap.get( this );
44
- }, } );
39
+ [ window.ShadowRoot.prototype, window.Element.prototype ].forEach( proto => {
40
+ Object.defineProperty( proto, config.api.scripts, { get: function() {
41
+ if ( !scriptsMap.has( this ) ) { scriptsMap.set( this, [] ); }
42
+ return scriptsMap.get( this );
43
+ }, } );
44
+ } );
45
45
  Object.defineProperties( window.HTMLScriptElement.prototype, {
46
46
  scoped: {
47
47
  configurable: true,
@@ -80,7 +80,7 @@ async function execute( config, execHash ) {
80
80
  realdom.realtime( window.document ).observe( script, () => {
81
81
  if ( script.quantum ) { state.dispose(); }
82
82
  if ( thisContext instanceof window.Element ) { thisContext[ config.api.scripts ]?.splice( thisContext[ config.api.scripts ].indexOf( script, 1 ) ); }
83
- }, { subtree: 'cross-roots', timing: 'sync', generation: 'exits' } );
83
+ }, { id: 'scoped-js:script-exits', subtree: 'cross-roots', timing: 'sync', generation: 'exits' } );
84
84
  }
85
85
 
86
86
  /**
@@ -105,12 +105,12 @@ function realtime( config ) {
105
105
  const thisContext = script.scoped ? script.parentNode || record.target : ( script.type === 'module' ? undefined : window );
106
106
  if ( script.scoped ) { thisContext[ config.api.scripts ].push( script ); }
107
107
  const execHash = _toHash( { script, compiledScript, thisContext } );
108
- const manualHandling = record.type === 'query' || ( script.type && !window.HTMLScriptElement.supports( script.type ) );
108
+ const manualHandling = record.type === 'query' || ( script.type && !window.HTMLScriptElement.supports( script.type ) ) || script.getAttribute('data-handling') === 'manual';
109
109
  if ( manualHandling || config.script.timing === 'manual' ) { oohtml.Script.execute( execHash ); } else {
110
110
  script.textContent = `webqit.oohtml.Script.execute( '${ execHash }' );`;
111
111
  }
112
112
  } );
113
- }, { live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants', eventDetails: true } );
113
+ }, { id: 'scoped-js:script-entries', live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants', eventDetails: true } );
114
114
  // ---
115
115
  }
116
116