@webqit/oohtml 3.1.21 → 3.1.22

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.21",
17
+ "version": "3.1.22",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@webqit/quantum-js": "^4.2.11",
39
- "@webqit/realdom": "^2.1.22",
39
+ "@webqit/realdom": "^2.1.23",
40
40
  "@webqit/util": "^0.8.11"
41
41
  },
42
42
  "devDependencies": {
@@ -3,7 +3,7 @@
3
3
  * @imports
4
4
  */
5
5
  import DOMNamingContext from './DOMNamingContext.js';
6
- import { _, _init, _splitOuter, _fromHash, _toHash } from '../util.js';
6
+ import { _, _init, _splitOuter, _fromHash, _toHash, getInternalAttrInteraction, internalAttrInteraction } from '../util.js';
7
7
 
8
8
  /**
9
9
  * @init
@@ -19,6 +19,7 @@ export default function init( $config = {} ) {
19
19
  } );
20
20
  config.lidSelector = `[${ window.CSS.escape( config.attr.lid ) }]`;
21
21
  config.namespaceSelector = `[${ window.CSS.escape( config.attr.namespace ) }]`;
22
+ config.documentNamespaceUUID = getNamespaceUUID( getOwnNamespaceObject.call( window, window.document ) );
22
23
  window.webqit.DOMNamingContext = DOMNamingContext;
23
24
  exposeAPIs.call( window, config );
24
25
  realtime.call( window, config );
@@ -39,8 +40,8 @@ function lidUtil( config ) {
39
40
  lidrefSeparator( escapeMode = 0 ) { return escapeMode ? this.escape( lidrefSeparator, escapeMode ) : lidrefSeparator; },
40
41
  isUuid( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && str.includes( this.lidrefSeparator( escapeMode ) ); },
41
42
  isLidref( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && !str.includes( this.lidrefSeparator( escapeMode ) ); },
42
- toUuid( hash, lid, escapeMode = 0 ) { return `${ this.lidrefPrefix( escapeMode ) }${ hash }${ this.lidrefSeparator( escapeMode ) }${ lid }`; },
43
- uuidToLid( str, escapeMode = 0 ) { return this.isUuid( str ) ? str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] : str; },
43
+ toUuid( hash, lid, escapeMode = 0 ) { return hash === config.documentNamespaceUUID ? lid : `${ this.lidrefPrefix( escapeMode ) }${ hash }${ this.lidrefSeparator( escapeMode ) }${ lid }`; },
44
+ uuidToId( str, escapeMode = 0 ) { return this.isUuid( str ) ? str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] : str; },
44
45
  uuidToLidref( str, escapeMode = 0 ) { return this.isUuid( str ) ? `${ this.lidrefPrefix( escapeMode ) }${ str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] }` : str; },
45
46
  }
46
47
  }
@@ -85,22 +86,20 @@ export function rewriteSelector( selectorText, namespaceUUID, scopeSelector = nu
85
86
  return `#${ $lidUtil.escape( match.replace( '#', '' ), 1 ) }`;
86
87
  }
87
88
  // Rewrite relative ID selector
88
- let lowerBoundFactor = false;
89
89
  if ( isLidref ) {
90
- if ( config.attr.lid === 'id' && namespaceUUID ) {
90
+ if ( config.attr.lid === 'id' && namespaceUUID && namespaceUUID !== config.documentNamespaceUUID ) {
91
91
  return `#${ $lidUtil.toUuid( namespaceUUID, id, 1 ) }`;
92
92
  }
93
93
  // Fallback to attr-based
94
- lowerBoundFactor = true;
95
94
  }
96
95
  // Rewrite absolute ID selector
97
96
  let rewrite;
98
97
  if ( config.attr.lid === 'id' ) {
99
- rewrite = `[id^="${ $lidUtil.lidrefPrefix( escapeMode ) }"][id$="${ $lidUtil.lidrefSeparator( escapeMode ) }${ id }"]`;
98
+ rewrite = `:is(#${ id },[id^="${ $lidUtil.lidrefPrefix( escapeMode ) }"][id$="${ $lidUtil.lidrefSeparator( escapeMode ) }${ id }"])`;
100
99
  } else {
101
100
  rewrite = `:is(#${ id },[${ window.CSS.escape( config.attr.lid ) }="${ id }"])`;
102
101
  }
103
- return scopeSelector && lowerBoundFactor ? `:is(${ rewrite }):not(${ scopeSelector } [${ config.attr.namespace }] *)` : rewrite;
102
+ return isLidref ? `:is(${ rewrite }):not(${ scopeSelector ? scopeSelector + ' ' : '' }[${ config.attr.namespace }] *)` : rewrite;
104
103
  } );
105
104
  // Category 2 has :scope and category 1 does not
106
105
  return hadScopeSelector ? [ cat1, cat2.concat( selector ) ] : [ cat1.concat( selector ), cat2 ];
@@ -189,27 +188,46 @@ function realtime( config ) {
189
188
  const attrList = [ config.attr.lid, ...idRefsAttrs, ...idRefAttrs ];
190
189
  const relMap = { id: 'id'/* just in case it's in attrList */, for: 'htmlFor', 'aria-owns': 'ariaOwns', 'aria-controls': 'ariaControls', 'aria-labelledby': 'ariaLabelledBy', 'aria-describedby': 'ariaDescribedBy', 'aria-flowto': 'ariaFlowto', 'aria-activedescendant': 'ariaActiveDescendant', 'aria-details': 'ariaDetails', 'aria-errormessage': 'ariaErrorMessage' };
191
190
  const $lidUtil = lidUtil( config );
191
+ const uuidsToLidrefs = ( node, attrName, getter ) => {
192
+ if ( !getInternalAttrInteraction( node, attrName ) && _( node, 'attrOriginals' ).has( attrName ) ) {
193
+ return _( node, 'attrOriginals' ).get( attrName );
194
+ }
195
+ const value = getter();
196
+ if ( getInternalAttrInteraction( node, attrName ) ) return value;
197
+ return value && value.split( ' ' ).map( x => ( x = x.trim() ) && ( attrName === config.attr.lid ? $lidUtil.uuidToId : $lidUtil.uuidToLidref ).call( $lidUtil, x ) ).join( ' ' );
198
+ };
192
199
 
193
- // Intercept getAttribute()
194
- const getAttributeDescr = Object.getOwnPropertyDescriptor( window.Element.prototype, 'getAttribute' );
195
- Object.defineProperty( window.Element.prototype, 'getAttribute', { ...getAttributeDescr, value( attrName ) {
196
- const value = getAttributeDescr.value.call( this, attrName );
197
- return value && !_( this, 'lock' ).get( attrName ) && attrList.includes( attrName ) ? ( attrName === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, value ) : value;
198
- } } );
199
200
  // Intercept getElementById()
200
201
  const getElementByIdDescr = Object.getOwnPropertyDescriptor( window.Document.prototype, 'getElementById' );
201
202
  Object.defineProperty( window.Document.prototype, 'getElementById', { ...getElementByIdDescr, value( id ) {
202
- return this.querySelector( `#${ id }`/* Will be rewritten at querySelector() */ );
203
+ return this.querySelector( `#${ id }` ); // To be rewritten at querySelector()
203
204
  } } );
204
205
  // Intercept querySelector() and querySelectorAll()
205
206
  for ( const queryApi of [ 'querySelector', 'querySelectorAll' ] ) {
206
207
  for ( nodeApi of [ window.Document, window.Element ] ) {
207
208
  const querySelectorDescr = Object.getOwnPropertyDescriptor( nodeApi.prototype, queryApi );
208
209
  Object.defineProperty( nodeApi.prototype, queryApi, { ...querySelectorDescr, value( selector ) {
209
- return querySelectorDescr.value.call( this, rewriteSelector.call( window, selector, getNamespaceUUID.call( window, this ) ) );
210
+ return querySelectorDescr.value.call( this, rewriteSelector.call( window, selector, getNamespaceUUID( getOwnNamespaceObject.call( window, this ) ) ) );
210
211
  } } );
211
212
  }
212
213
  }
214
+ // Intercept getAttribute()
215
+ const getAttributeDescr = Object.getOwnPropertyDescriptor( window.Element.prototype, 'getAttribute' );
216
+ Object.defineProperty( window.Element.prototype, 'getAttribute', { ...getAttributeDescr, value( attrName ) {
217
+ const getter = () => getAttributeDescr.value.call( this, attrName );
218
+ return attrList.includes( attrName ) && !_( this, 'lock' ).get( attrName ) ? uuidsToLidrefs( this, attrName, getter ) : getter();
219
+ } } );
220
+ // Hide implementation details on the Attr node too.
221
+ const propertyDescr = Object.getOwnPropertyDescriptor( window.Attr.prototype, 'value' );
222
+ Object.defineProperty( window.Attr.prototype, 'value', { ...propertyDescr, get() {
223
+ const getter = () => propertyDescr.get.call( this );
224
+ return attrList.includes( this.name ) ? uuidsToLidrefs( this.ownerElement, this.name, getter ) : getter();
225
+ } } );
226
+ const propertyDescr2 = Object.getOwnPropertyDescriptor( window.Node.prototype, 'nodeValue' );
227
+ Object.defineProperty( window.Node.prototype, 'nodeValue', { ...propertyDescr2, get() {
228
+ const getter = () => propertyDescr2.get.call( this );
229
+ return this instanceof window.Attr && attrList.includes( this.name ) ? uuidsToLidrefs( this.ownerElement, this.name, getter ) : getter();
230
+ } } );
213
231
  // These APIs should return LIDREFS minus the hash part
214
232
  for ( const attrName of attrList ) {
215
233
  if ( !( attrName in relMap ) ) continue;
@@ -218,16 +236,11 @@ function realtime( config ) {
218
236
  const propertyDescr = Object.getOwnPropertyDescriptor( domApi.prototype, relMap[ attrName ] );
219
237
  if ( !propertyDescr ) continue;
220
238
  Object.defineProperty( domApi.prototype, relMap[ attrName ], { ...propertyDescr, get() {
221
- return ( attrName === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, propertyDescr.get.call( this, attrName ) || '' );
239
+ const getter = () => propertyDescr.get.call( this, attrName );
240
+ return uuidsToLidrefs( this, attrName, getter );
222
241
  } } );
223
242
  }
224
243
  }
225
- // Hide implementation details on the Attr node too.
226
- const propertyDescr = Object.getOwnPropertyDescriptor( window.Attr.prototype, 'value' );
227
- Object.defineProperty( window.Attr.prototype, 'value', { ...propertyDescr, get() {
228
- const value = propertyDescr.get.call( this );
229
- return value && attrList.includes( this.name ) ? ( this.name === 'id' ? $lidUtil.uuidToLid : $lidUtil.uuidToLidref ).call( $lidUtil, value ) : value;
230
- } } );
231
244
  if ( config.attr.lid !== 'id' ) {
232
245
  // Reflect the custom [config.attr.lid] attribute
233
246
  Object.defineProperty( window.Element.prototype, config.attr.lid, { configurable: true, enumerable: true, get() {
@@ -241,12 +254,10 @@ function realtime( config ) {
241
254
  // LOCAL IDS & IDREFS
242
255
  // ------------
243
256
  const attrChange = ( entry, attrName, value, callback ) => {
244
- // Create or honour locking
245
- if ( _( entry, 'lock' ).get( attrName ) ) return;
246
- _( entry, 'lock' ).set( attrName, true );
247
- if ( typeof value === 'function' ) value = value();
248
- callback( value );
249
- _( entry, 'lock' ).delete( attrName );
257
+ return internalAttrInteraction( entry, attrName, () => {
258
+ if ( typeof value === 'function' ) value = value();
259
+ return callback( value );
260
+ } );
250
261
  };
251
262
  const setupBinding = ( entry, attrName, value, newNamespaceObj = null ) => {
252
263
  attrChange( entry, attrName, value, value => {
@@ -254,13 +265,15 @@ function realtime( config ) {
254
265
  const namespaceObj = newNamespaceObj || getOwnerNamespaceObject( entry, isLidAttr );
255
266
  const namespaceUUID = getNamespaceUUID( namespaceObj );
256
267
  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 );
268
+ const id = $lidUtil.uuidToId( value );
269
+ if ( Observer.get( namespaceObj, id ) !== entry ) {
270
+ const uuid = $lidUtil.toUuid( namespaceUUID, id );
271
+ if ( uuid !== value ) { entry.setAttribute( 'id', uuid ); }
272
+ Observer.set( namespaceObj, id, entry );
261
273
  }
262
274
  } else {
263
- const newAttrValue = value.split( ' ' ).map( lid => ( lid = lid.trim() ) && !$lidUtil.isLidref( lid ) ? lid : $lidUtil.toUuid( namespaceUUID, lid.replace( $lidUtil.lidrefPrefix(), '' ) ) ).join( ' ' );
275
+ _( entry, 'attrOriginals' ).set( attrName, value ); // Save original before rewrite
276
+ const newAttrValue = value.split( ' ' ).map( idref => ( idref = idref.trim() ) && !$lidUtil.isLidref( idref ) ? idref : $lidUtil.toUuid( namespaceUUID, idref.replace( $lidUtil.lidrefPrefix(), '' ) ) ).join( ' ' );
264
277
  entry.setAttribute( attrName, newAttrValue );
265
278
  _( namespaceObj ).set( 'idrefs', _( namespaceObj ).get( 'idrefs' ) || new Set );
266
279
  _( namespaceObj ).get( 'idrefs' ).add( entry );
@@ -272,12 +285,12 @@ function realtime( config ) {
272
285
  const isLidAttr = attrName === config.attr.lid;
273
286
  const namespaceObj = prevNamespaceObj || getOwnerNamespaceObject( entry, isLidAttr );
274
287
  if ( isLidAttr ) {
275
- const lid = $lidUtil.uuidToLid( oldValue );
276
- if ( Observer.get( namespaceObj, lid ) === entry ) {
277
- Observer.deleteProperty( namespaceObj, lid );
288
+ const id = $lidUtil.uuidToId( oldValue );
289
+ if ( Observer.get( namespaceObj, id ) === entry ) {
290
+ Observer.deleteProperty( namespaceObj, id );
278
291
  }
279
292
  } else {
280
- const newAttrValue = oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
293
+ const newAttrValue = _( entry, 'attrOriginals' ).get( attrName );// oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
281
294
  entry.setAttribute( attrName, newAttrValue );
282
295
  _( namespaceObj ).get( 'idrefs' ).delete( entry );
283
296
  }
@@ -292,8 +305,8 @@ function realtime( config ) {
292
305
  const reAssociate = ( entry, attrName, oldNamespaceObj, newNamespaceObj ) => {
293
306
  if ( !entry.hasAttribute( attrName ) ) return;
294
307
  const attrValue = () => entry.getAttribute( attrName );
295
- cleanupBinding( entry, attrName, attrValue, oldNamespaceObj );
296
- if ( entry.isConnected ) { setupBinding( entry, attrName, attrValue, newNamespaceObj ); }
308
+ cleanupBinding( entry, attrName, attrValue/* Current resolved value as-is */, oldNamespaceObj );
309
+ if ( entry.isConnected ) { setupBinding( entry, attrName, _( entry, 'attrOriginals' ).get( attrName )/* Saved original value */ || attrValue/* Lest it's ID */, newNamespaceObj ); }
297
310
  };
298
311
  record.exits.forEach( entry => {
299
312
  const namespaceObj = getOwnNamespaceObject( entry );
@@ -326,19 +339,19 @@ function realtime( config ) {
326
339
  for ( const attrName of attrList ) {
327
340
  record.exits.forEach( entry => {
328
341
  if ( !entry.hasAttribute( attrName ) ) return;
329
- cleanupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
342
+ cleanupBinding( entry, attrName, () => entry.getAttribute( attrName )/* Current resolved value as-is */ );
330
343
  } );
331
344
  record.entrants.forEach( entry => {
332
345
  if ( !entry.hasAttribute( attrName ) ) return;
333
- setupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
346
+ setupBinding( entry, attrName, () => entry.getAttribute( attrName )/* Raw value (as-is) that will be saved as original */ );
334
347
  } );
335
348
  }
336
349
  }, { live: true, timing: 'sync' } );
337
350
  // Attr realtime
338
351
  realdom.realtime( window.document, 'attr' ).observe( attrList, records => {
339
352
  for ( const record of records ) {
340
- if ( record.oldValue ) { cleanupBinding( record.target, record.name, record.oldValue ); }
341
- if ( record.value ) { setupBinding( record.target, record.name, record.value ); }
353
+ if ( record.oldValue ) { cleanupBinding( record.target, record.name, record.oldValue/* Current resolved value as-is */ ); }
354
+ if ( record.value ) { setupBinding( record.target, record.name, record.value/* Raw value (as-is) that will be saved as original */ ); }
342
355
  }
343
356
  }, { subtree: true, timing: 'sync', newValue: true, oldValue: true } );
344
357
 
@@ -17,7 +17,7 @@ export default function init({ advanced = {}, ...$config }) {
17
17
  } );
18
18
  config.styleSelector = (Array.isArray( config.style.mimeType ) ? config.style.mimeType : [ config.style.mimeType ] ).reduce( ( selector, mm ) => {
19
19
  const qualifier = mm ? `[type=${ window.CSS.escape( mm ) }]` : '';
20
- return selector.concat( `style${ qualifier }[scoped]` );
20
+ return selector.concat( `style${ qualifier }` );
21
21
  }, [] ).join( ',' );
22
22
  window.webqit.oohtml.Style = {
23
23
  compileCache: new Map,
@@ -62,15 +62,16 @@ function realtime( config ) {
62
62
  record.entrants.forEach( style => {
63
63
  if ( handled.has( style ) ) return;
64
64
  handled.add( style );
65
- if ( !style.scoped ) return;
66
- style.parentNode[ config.api.styleSheets ].push( style );
67
65
  // Do compilation
68
66
  const sourceHash = _toHash( style.textContent );
69
67
  const supportsHAS = CSS.supports( 'selector(:has(a,b))' );
70
- const scopeSelector = supportsHAS ? `:has(> style[rand-${ sourceHash }])` : `[rand-${ sourceHash }]`;
71
- const supportsScope = window.CSSScopeRule && false/* Disabled for buggy behaviour: rewriting selectorText within an @scope block invalidates the scoping */;
72
- ( supportsHAS ? style : style.parentNode ).toggleAttribute( `rand-${ sourceHash }`, true );
73
- if ( style.hasAttribute( 'shared' ) ) {
68
+ const scopeSelector = style.scoped && ( supportsHAS ? `:has(> style[rand-${ sourceHash }])` : `[rand-${ sourceHash }]` );
69
+ const supportsScope = style.scoped && window.CSSScopeRule && false/* Disabled for buggy behaviour: rewriting selectorText within an @scope block invalidates the scoping */;
70
+ if ( style.scoped ) {
71
+ style.parentNode[ config.api.styleSheets ].push( style );
72
+ ( supportsHAS ? style : style.parentNode ).toggleAttribute( `rand-${ sourceHash }`, true );
73
+ }
74
+ if ( style.scoped && style.hasAttribute( 'shared' ) ) {
74
75
  let compiledSheet;
75
76
  if ( !( compiledSheet = oohtml.Style.compileCache.get( sourceHash ) ) ) {
76
77
  compiledSheet = createAdoptableStylesheet.call( window, style, null, supportsScope, scopeSelector );
@@ -81,7 +82,7 @@ function realtime( config ) {
81
82
  style.textContent = '\n/*[ Shared style sheet ]*/\n';
82
83
  } else {
83
84
  const transform = () => {
84
- const namespaceUUID = getNamespaceUUID( getOwnerNamespaceObject.call( window, style ) );
85
+ const namespaceUUID = getNamespaceUUID( getOwnerNamespaceObject.call( window, style.scoped ? style : window.document ) );
85
86
  upgradeSheet.call( this, style.sheet, namespaceUUID, !supportsScope && scopeSelector );
86
87
  };
87
88
  if ( style.isConnected ) { transform(); }
@@ -94,7 +95,7 @@ function realtime( config ) {
94
95
 
95
96
  function createAdoptableStylesheet( style, namespaceUUID, supportsScope, scopeSelector ) {
96
97
  const window = this, textContent = style.textContent;
97
- let styleSheet, cssText = supportsScope ? `@scope (${ scopeSelector }) {\n${ textContent.trim() }\n}` : textContent.trim();
98
+ let styleSheet, cssText = supportsScope && scopeSelector ? `@scope (${ scopeSelector }) {\n${ textContent.trim() }\n}` : textContent.trim();
98
99
  try {
99
100
  styleSheet = new window.CSSStyleSheet;
100
101
  styleSheet.replaceSync( cssText );
package/src/util.js CHANGED
@@ -42,6 +42,17 @@ export function _init( name, $config, $defaults ) {
42
42
  return { config: window.webqit.oohtml.configs[ configKey ], realdom, window };
43
43
  }
44
44
 
45
+ export function getInternalAttrInteraction( node, attrName ) {
46
+ return _internals( node, 'internalAttrInteractions' ).get( attrName );
47
+ }
48
+ export function internalAttrInteraction( node, attrName, callback ) {
49
+ const savedAttrLocking = _internals( node, 'internalAttrInteractions' ).get( attrName );
50
+ _internals( node, 'internalAttrInteractions' ).set( attrName, true );
51
+ const value = callback();
52
+ _internals( node, 'internalAttrInteractions' ).set( attrName, savedAttrLocking );
53
+ return value;
54
+ }
55
+
45
56
  export function _compare( a, b, depth = 1, objectSizing = false ) {
46
57
  if ( depth && typeof a === 'object' && a && typeof b === 'object' && b && ( !objectSizing || Object.keys( a ).length === Object.keys( b ).length ) ) {
47
58
  for ( let key in a ) {