@webqit/oohtml 4.3.1 → 4.3.3

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.1",
17
+ "version": "4.3.3",
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.32",
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 ) => {
@@ -266,11 +273,12 @@ const escDouble = str => str.replace(/"/g, '\\"');
266
273
 
267
274
  export function idleCompiler( node ) {
268
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
+ } );
269
280
  xpathQuery( window, node, `(${ config.discreteBindingsSelector })` ).forEach( node => {
270
281
  const template = patternMatch( config, node.nodeValue );
271
282
  compileDiscreteBindings.call( window, config, template.expr );
272
283
  } );
273
- ( node?.matches( config.attrSelector ) ? [ node ] : [] ).concat([ ...( node?.querySelectorAll( config.attrSelector ) || [] ) ]).forEach( node => {
274
- compileInlineBindings.call( window, config, node.getAttribute( config.attr.render ) );
275
- } );
276
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, 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', deferred: 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' });
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,7 +338,7 @@ 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
344
  realdom.realtime( window.document ).query( `[${ attrList.map( attrName => window.CSS.escape( attrName ) ).join( '],[' ) }]`, record => {
@@ -365,14 +365,14 @@ function realtime( config ) {
365
365
  }
366
366
  namespaceNodesToTest.forID.clear();
367
367
  namespaceNodesToTest.forOther.clear();
368
- }, { live: true, subtree: 'cross-roots', timing: 'sync' } );
368
+ }, { id: 'namespace-html:attrs', live: true, subtree: 'cross-roots', timing: 'sync' } );
369
369
  // Attr realtime
370
370
  realdom.realtime( window.document, 'attr' ).observe( attrList, records => {
371
371
  for ( const record of records ) {
372
372
  if ( record.oldValue ) { cleanupBinding( record.target, record.name, record.oldValue/* Current resolved value as-is */ ); }
373
373
  if ( record.value ) { setupBinding( record.target, record.name, record.value/* Raw value (as-is) that will be saved as original */ ); }
374
374
  }
375
- }, { subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true } );
375
+ }, { id: 'namespace-html:attr(attrs)', subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true } );
376
376
 
377
377
  // ------------
378
378
  // 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