@webqit/oohtml 3.1.18 → 3.1.20

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": "3.1.18",
17
+ "version": "3.1.20",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "test": "mocha --extension .test.js --exit",
31
31
  "test:coverage": "c8 --reporter=text-lcov npm run test | coveralls",
32
32
  "build": "esbuild main=src/api.global.js main.lite=src/api.global.lite.js context-api=src/context-api/targets.browser.js bindings-api=src/bindings-api/targets.browser.js namespaced-html=src/namespaced-html/targets.browser.js html-imports=src/html-imports/targets.browser.js data-binding=src/data-binding/targets.browser.js scoped-css=src/scoped-css/targets.browser.js scoped-js=src/scoped-js/targets.browser.js --bundle --minify --sourcemap --outdir=dist",
33
- "preversion": "npm run build && git add -A dist",
33
+ "preversion": "npm test && npm run build && git add -A dist",
34
34
  "postversion": "npm publish",
35
35
  "postpublish": "git push && git push --tags"
36
36
  },
@@ -116,13 +116,11 @@ export function rewriteSelector( selectorText, namespaceUUID, scopeSelector = nu
116
116
  }
117
117
 
118
118
  /**
119
- * Returns the "namespace" object associated with the given node.
120
- *
121
119
  * @param Element node
122
120
  *
123
121
  * @return Object
124
122
  */
125
- export function getNamespaceObject( node ) {
123
+ export function getOwnNamespaceObject( node ) {
126
124
  if ( !_( node ).has( 'namespace' ) ) {
127
125
  const namespaceObj = Object.create( null );
128
126
  _( node ).set( 'namespace', namespaceObj );
@@ -132,12 +130,21 @@ export function getNamespaceObject( node ) {
132
130
 
133
131
  /**
134
132
  * @param Element node
133
+ * @param Bool forID
135
134
  *
136
- * @return String
135
+ * @return Object
137
136
  */
138
- export function getNamespaceUUID( node ) {
137
+ export function getOwnerNamespaceObject( node, forID = false ) {
139
138
  const window = this, { webqit: { oohtml: { configs: { NAMESPACED_HTML: config } } } } = window;
140
- const namespaceObj = getNamespaceObject( node instanceof window.Document ? node : ( node.closest( `[${ config.attr.namespace }]` ) || node.ownerDocument ) );
139
+ return getOwnNamespaceObject( node instanceof window.Document ? node : ( ( forID ? node.parentNode : node )?.closest( `[${ config.attr.namespace }]` ) || node.ownerDocument ) );
140
+ }
141
+
142
+ /**
143
+ * @param Object namespaceObj
144
+ *
145
+ * @return String
146
+ */
147
+ export function getNamespaceUUID( namespaceObj ) {
141
148
  return _fromHash( namespaceObj ) || _toHash( namespaceObj );
142
149
  }
143
150
 
@@ -155,10 +162,10 @@ function exposeAPIs( config ) {
155
162
  if ( config.api.namespace in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.api.namespace }" property!` ); }
156
163
  // Definitions
157
164
  Object.defineProperty( window.document, config.api.namespace, { get: function() {
158
- return Observer.proxy( getNamespaceObject.call( window, window.document ) );
165
+ return Observer.proxy( getOwnNamespaceObject.call( window, window.document ) );
159
166
  } });
160
167
  Object.defineProperty( window.Element.prototype, config.api.namespace, { get: function() {
161
- return Observer.proxy( getNamespaceObject.call( window, this ) );
168
+ return Observer.proxy( getOwnNamespaceObject.call( window, this ) );
162
169
  } } );
163
170
  }
164
171
 
@@ -171,24 +178,6 @@ function exposeAPIs( config ) {
171
178
  */
172
179
  function realtime( config ) {
173
180
  const window = this, { webqit: { Observer, realdom, oohtml: { configs }, DOMNamingContext } } = window;
174
-
175
- // ------------
176
- // NAMESPACE
177
- // ------------
178
- window.document[ configs.CONTEXT_API.api.contexts ].attach( new DOMNamingContext );
179
- realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( config.namespaceSelector, record => {
180
- record.exits.forEach( entry => {
181
- const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
182
- const ctx = contextsApi.find( DOMNamingContext.kind );
183
- if ( ctx ) { contextsApi.detach( ctx ); }
184
- } );
185
- record.entrants.forEach( entry => {
186
- const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
187
- if ( !contextsApi.find( DOMNamingContext.kind ) ) {
188
- contextsApi.attach( new DOMNamingContext );
189
- }
190
- } );
191
- }, { live: true, timing: 'sync', staticSensitivity: true } );
192
181
 
193
182
  // ------------
194
183
  // APIS
@@ -205,7 +194,7 @@ function realtime( config ) {
205
194
  const getAttributeDescr = Object.getOwnPropertyDescriptor( window.Element.prototype, 'getAttribute' );
206
195
  Object.defineProperty( window.Element.prototype, 'getAttribute', { ...getAttributeDescr, value( attrName ) {
207
196
  const value = getAttributeDescr.value.call( this, attrName );
208
- return !_( this, 'lock' ).get( attrName ) && attrList.includes( attrName ) ? ( attrName === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, value ) : value;
197
+ return value && !_( this, 'lock' ).get( attrName ) && attrList.includes( attrName ) ? ( attrName === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, value ) : value;
209
198
  } } );
210
199
  // Intercept getElementById()
211
200
  const getElementByIdDescr = Object.getOwnPropertyDescriptor( window.Document.prototype, 'getElementById' );
@@ -237,7 +226,7 @@ function realtime( config ) {
237
226
  const propertyDescr = Object.getOwnPropertyDescriptor( window.Attr.prototype, 'value' );
238
227
  Object.defineProperty( window.Attr.prototype, 'value', { ...propertyDescr, get() {
239
228
  const value = propertyDescr.get.call( this );
240
- return attrList.includes( this.name ) ? ( this.name === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, value ) : value;
229
+ return value && attrList.includes( this.name ) ? ( this.name === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, value ) : value;
241
230
  } } );
242
231
  if ( config.attr.lid !== 'id' ) {
243
232
  // Reflect the custom [config.attr.lid] attribute
@@ -251,94 +240,105 @@ function realtime( config ) {
251
240
  // ------------
252
241
  // LOCAL IDS & IDREFS
253
242
  // ------------
254
- const cleanupBinding = ( entry, attrName, oldValue ) => {
243
+ const attrChange = ( entry, attrName, value, callback ) => {
255
244
  // Create or honour locking
256
245
  if ( _( entry, 'lock' ).get( attrName ) ) return;
257
246
  _( entry, 'lock' ).set( attrName, true );
258
- // A function can be passed in to be called after having done the _( entry, 'lock' ).set( attrName, true ); flag
259
- if ( typeof oldValue === 'function' ) oldValue = oldValue();
260
- // Get down to work
261
- const namespaceObj = _( entry ).get( 'ownerNamespace' );
262
- if ( attrName === config.attr.lid ) {
263
- const lid = $lidUtil.uuidToLid( oldValue );
264
- if ( Observer.get( namespaceObj, lid ) === entry ) { Observer.deleteProperty( namespaceObj, lid ); }
265
- } else {
266
- const newAttrValue = oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
267
- entry.setAttribute( attrName, newAttrValue );
268
- }
269
- // Release locking
270
- _( entry, 'lock' ).delete( attrName );
271
- };
272
- const setupBinding = ( entry, attrName, value ) => {
273
- // Create or honour locking
274
- if ( _( entry, 'lock' ).get( attrName ) ) return;
275
- _( entry, 'lock' ).set( attrName, true );
276
- // A function can be passed in to be called after having done the _( entry, 'lock' ).set( attrName, true ); flag
277
247
  if ( typeof value === 'function' ) value = value();
278
- // Get down to work
279
- const namespaceObj = _( entry ).get( 'ownerNamespace' );
280
- const namespaceUUID = _( entry ).get( 'namespaceUUID' );
281
- if ( attrName === config.attr.lid ) {
282
- const lid = $lidUtil.uuidToLid( value );
283
- if ( Observer.get( namespaceObj, lid ) !== entry ) {
284
- // Setup new namespace relationships
285
- entry.setAttribute( 'id', $lidUtil.toUuid( namespaceUUID, lid ) );
286
- Observer.set( namespaceObj, lid, entry );
287
- }
288
- } else {
289
- const newAttrValue = value.split( ' ' ).map( lid => ( lid = lid.trim() ) && !$lidUtil.isLidref( lid ) ? lid : $lidUtil.toUuid( namespaceUUID, lid.replace( $lidUtil.lidrefPrefix(), '' ) ) ).join( ' ' );
290
- entry.setAttribute( attrName, newAttrValue );
291
- }
292
- // Release locking
248
+ callback( value );
293
249
  _( entry, 'lock' ).delete( attrName );
294
250
  };
295
-
296
- const cleanupAllBindings = ( entry, total = true ) => {
297
- for ( const attrName of attrList ) {
298
- if ( !entry.hasAttribute( attrName ) ) continue;
299
- cleanupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
300
- }
301
- _( entry ).delete( 'ownerNamespace' );
302
- _( entry ).delete( 'namespaceUUID' );
303
- if ( total ) {
304
- _( entry ).get( 'namespaceBinding' ).abort();
305
- _( entry ).delete( 'namespaceBinding' );
306
- }
251
+ const setupBinding = ( entry, attrName, value, newNamespaceObj = null ) => {
252
+ attrChange( entry, attrName, value, value => {
253
+ const isLidAttr = attrName === config.attr.lid;
254
+ const namespaceObj = newNamespaceObj || getOwnerNamespaceObject( entry, isLidAttr );
255
+ const namespaceUUID = getNamespaceUUID( namespaceObj );
256
+ if ( isLidAttr ) {
257
+ const lid = $lidUtil.uuidToLid( value );
258
+ if ( Observer.get( namespaceObj, lid ) !== entry ) {
259
+ entry.setAttribute( 'id', $lidUtil.toUuid( namespaceUUID, lid ) );
260
+ Observer.set( namespaceObj, lid, entry );
261
+ }
262
+ } else {
263
+ const newAttrValue = value.split( ' ' ).map( lid => ( lid = lid.trim() ) && !$lidUtil.isLidref( lid ) ? lid : $lidUtil.toUuid( namespaceUUID, lid.replace( $lidUtil.lidrefPrefix(), '' ) ) ).join( ' ' );
264
+ entry.setAttribute( attrName, newAttrValue );
265
+ _( namespaceObj ).set( 'idrefs', _( namespaceObj ).get( 'idrefs' ) || new Set );
266
+ _( namespaceObj ).get( 'idrefs' ).add( entry );
267
+ }
268
+ } );
307
269
  };
308
- const setupAllBindings = ( entry ) => {
309
- if ( !_( entry ).get( 'ownerNamespace' ) ) {
310
- const request = { ...DOMNamingContext.createRequest(), live: true };
311
- const binding = entry.parentNode[ configs.CONTEXT_API.api.contexts ].request( request, namespaceObj => {
312
- // Cleanup of previous namespace?
313
- if ( _( entry ).get( 'namespaceUUID' ) ) {
314
- cleanupAllBindings( entry, false );
270
+ const cleanupBinding = ( entry, attrName, oldValue, prevNamespaceObj = null ) => {
271
+ attrChange( entry, attrName, oldValue, oldValue => {
272
+ const isLidAttr = attrName === config.attr.lid;
273
+ const namespaceObj = prevNamespaceObj || getOwnerNamespaceObject( entry, isLidAttr );
274
+ if ( isLidAttr ) {
275
+ const lid = $lidUtil.uuidToLid( oldValue );
276
+ if ( Observer.get( namespaceObj, lid ) === entry ) {
277
+ Observer.deleteProperty( namespaceObj, lid );
315
278
  }
316
- // Setup new namespace
317
- _( entry ).set( 'ownerNamespace', namespaceObj );
318
- _( entry ).set( 'namespaceUUID', _fromHash( namespaceObj ) || _toHash( namespaceObj ) );
319
- setupAllBindings( entry );
320
- } );
321
- _( entry ).set( 'namespaceBinding', binding );
322
- return;
323
- }
324
- for ( const attrName of attrList ) {
325
- if ( !entry.hasAttribute( attrName ) ) continue;
326
- setupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
327
- }
279
+ } else {
280
+ const newAttrValue = oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
281
+ entry.setAttribute( attrName, newAttrValue );
282
+ _( namespaceObj ).get( 'idrefs' ).delete( entry );
283
+ }
284
+ } );
328
285
  };
329
286
 
287
+ // ------------
288
+ // NAMESPACE
289
+ // ------------
290
+ window.document[ configs.CONTEXT_API.api.contexts ].attach( new DOMNamingContext );
291
+ realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( config.namespaceSelector, record => {
292
+ const reAssociate = ( entry, attrName, oldNamespaceObj, newNamespaceObj ) => {
293
+ if ( !entry.hasAttribute( attrName ) ) return;
294
+ const attrValue = () => entry.getAttribute( attrName );
295
+ cleanupBinding( entry, attrName, attrValue, oldNamespaceObj );
296
+ if ( entry.isConnected ) { setupBinding( entry, attrName, attrValue, newNamespaceObj ); }
297
+ };
298
+ record.exits.forEach( entry => {
299
+ const namespaceObj = getOwnNamespaceObject( entry );
300
+ // Detach ID and IDREF associations
301
+ for ( const node of new Set( [ ...Object.values( namespaceObj ), ..._( namespaceObj ).get( 'idrefs' ) ] ) ) {
302
+ for ( const attrName of attrList ) { reAssociate( node, attrName, namespaceObj ); }
303
+ }
304
+ // Detach ID associations
305
+ const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
306
+ const ctx = contextsApi.find( DOMNamingContext.kind );
307
+ // Detach namespace instance
308
+ if ( ctx ) { contextsApi.detach( ctx ); }
309
+ } );
310
+ record.entrants.forEach( entry => {
311
+ // Claim ID and IDREF associations
312
+ let newSuperNamespaceObj;
313
+ const superNamespaceObj = getOwnerNamespaceObject( entry, true );
314
+ for ( const node of new Set( [ ...Object.values( superNamespaceObj ), ...( _( superNamespaceObj ).get( 'idrefs' ) || [] ) ] ) ) {
315
+ if ( ( newSuperNamespaceObj = getOwnerNamespaceObject( node, true ) ) === superNamespaceObj ) continue;
316
+ for ( const attrName of attrList ) { reAssociate( node, attrName, superNamespaceObj, newSuperNamespaceObj ); }
317
+ }
318
+ // Attach namespace instance
319
+ const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
320
+ if ( !contextsApi.find( DOMNamingContext.kind ) ) { contextsApi.attach( new DOMNamingContext ); }
321
+ } );
322
+ }, { live: true, timing: 'sync', staticSensitivity: true } );
323
+
330
324
  // DOM realtime
331
325
  realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( `[${ attrList.map( attrName => window.CSS.escape( attrName ) ).join( '],[' ) }]`, record => {
332
- record.exits.forEach( cleanupAllBindings );
333
- record.entrants.forEach( setupAllBindings );
326
+ for ( const attrName of attrList ) {
327
+ record.exits.forEach( entry => {
328
+ if ( !entry.hasAttribute( attrName ) ) return;
329
+ cleanupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
330
+ } );
331
+ record.entrants.forEach( entry => {
332
+ if ( !entry.hasAttribute( attrName ) ) return;
333
+ setupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
334
+ } );
335
+ }
334
336
  }, { live: true, timing: 'sync' } );
335
337
  // Attr realtime
336
338
  realdom.realtime( window.document, 'attr' ).observe( attrList, records => {
337
339
  for ( const record of records ) {
338
- if ( record.oldValue ) {
339
- cleanupBinding( record.target, record.name, record.oldValue );
340
- if ( record.value ) { setupBinding( record.target, record.name, record.value ); }
341
- } else { setupAllBindings( entry ); }
340
+ if ( record.oldValue ) { cleanupBinding( record.target, record.name, record.oldValue ); }
341
+ if ( record.value ) { setupBinding( record.target, record.name, record.value ); }
342
342
  }
343
343
  }, { subtree: true, timing: 'sync', newValue: true, oldValue: true } );
344
344
 
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import { rewriteSelector, getNamespaceUUID } from '../namespaced-html/index.js';
5
+ import { rewriteSelector, getOwnerNamespaceObject, getNamespaceUUID } from '../namespaced-html/index.js';
6
6
  import { _init, _toHash } from '../util.js';
7
7
 
8
8
  /**
@@ -70,7 +70,7 @@ function realtime( config ) {
70
70
  const scopeSelector = supportsHAS ? `:has(> style[rand-${ sourceHash }])` : `[rand-${ sourceHash }]`;
71
71
  const supportsScope = window.CSSScopeRule && false/* Disabled for buggy behaviour: rewriting selectorText within an @scope block invalidates the scoping */;
72
72
  ( supportsHAS ? style : style.parentNode ).toggleAttribute( `rand-${ sourceHash }`, true );
73
- if ( false ) {
73
+ if ( style.hasAttribute( 'shared' ) ) {
74
74
  let compiledSheet;
75
75
  if ( !( compiledSheet = oohtml.Style.compileCache.get( sourceHash ) ) ) {
76
76
  compiledSheet = createAdoptableStylesheet.call( window, style, null, supportsScope, scopeSelector );
@@ -80,7 +80,7 @@ function realtime( config ) {
80
80
  Object.defineProperty( style, 'sheet', { value: compiledSheet, configurable: true } );
81
81
  style.textContent = '\n/*[ Shared style sheet ]*/\n';
82
82
  } else {
83
- const namespaceUUID = getNamespaceUUID.call( window, style );
83
+ const namespaceUUID = getNamespaceUUID( getOwnerNamespaceObject.call( window, style ) );
84
84
  upgradeSheet.call( this, style.sheet, namespaceUUID, !supportsScope && scopeSelector );
85
85
  }
86
86
  } );