@webqit/oohtml 2.1.58 → 2.1.60

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.
Files changed (38) hide show
  1. package/dist/bindings-api.js +1 -1
  2. package/dist/bindings-api.js.map +2 -2
  3. package/dist/context-api.js +1 -1
  4. package/dist/context-api.js.map +2 -2
  5. package/dist/data-binding.js +54 -0
  6. package/dist/data-binding.js.map +7 -0
  7. package/dist/html-imports.js +1 -1
  8. package/dist/html-imports.js.map +3 -3
  9. package/dist/main.js +23 -23
  10. package/dist/main.js.map +3 -3
  11. package/dist/namespaced-html.js +2 -0
  12. package/dist/{html-namespaces.js.map → namespaced-html.js.map} +3 -3
  13. package/dist/scoped-css.js +1 -1
  14. package/dist/scoped-css.js.map +2 -2
  15. package/dist/scoped-js.js +2 -2
  16. package/dist/scoped-js.js.map +3 -3
  17. package/package.json +5 -5
  18. package/src/bindings-api/_HTMLBindingsProvider.js +13 -0
  19. package/src/bindings-api/index.js +19 -8
  20. package/src/context-api/index.js +1 -0
  21. package/src/{html-bindings → data-binding}/index.js +52 -52
  22. package/src/html-imports/_HTMLImportElement copy.js +217 -0
  23. package/src/html-imports/_HTMLImportElement.js +27 -18
  24. package/src/html-imports/_HTMLImportsProvider.js +2 -2
  25. package/src/html-imports/index.js +27 -45
  26. package/src/index.js +9 -9
  27. package/src/{html-namespaces → namespaced-html}/index.js +1 -1
  28. package/src/scoped-js/index.js +2 -2
  29. package/test/imports.test.js +6 -4
  30. package/test/index.js +1 -1
  31. package/test/scoped-js.test.js +2 -2
  32. package/dist/html-bindings.js +0 -54
  33. package/dist/html-bindings.js.map +0 -7
  34. package/dist/html-bracelets.js +0 -2
  35. package/dist/html-bracelets.js.map +0 -7
  36. package/dist/html-namespaces.js +0 -2
  37. /package/src/{html-bindings → data-binding}/targets.browser.js +0 -0
  38. /package/src/{html-namespaces → namespaced-html}/targets.browser.js +0 -0
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "wicg-proposal"
15
15
  ],
16
16
  "homepage": "https://webqit.io/tooling/oohtml",
17
- "version": "2.1.58",
17
+ "version": "2.1.60",
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
@@ -29,19 +29,19 @@
29
29
  "scripts": {
30
30
  "test": "mocha --extension .test.js --exit",
31
31
  "test:coverage": "c8 --reporter=text-lcov npm run test | coveralls",
32
- "build": "esbuild main=src/targets.browser.js context-api=src/context-api/targets.browser.js bindings-api=src/bindings-api/targets.browser.js html-bindings=src/html-bindings/targets.browser.js html-namespaces=src/html-namespaces/targets.browser.js html-imports=src/html-imports/targets.browser.js scoped-js=src/scoped-js/targets.browser.js scoped-css=src/scoped-css/targets.browser.js --bundle --minify --sourcemap --outdir=dist",
33
- "preversion": "npm run build && git add -A dist",
32
+ "build": "esbuild main=src/targets.browser.js namespaced-html=src/namespaced-html/targets.browser.js scoped-css=src/scoped-css/targets.browser.js scoped-js=src/scoped-js/targets.browser.js context-api=src/context-api/targets.browser.js bindings-api=src/bindings-api/targets.browser.js html-imports=src/html-imports/targets.browser.js data-binding=src/data-binding/targets.browser.js --bundle --minify --sourcemap --outdir=dist",
33
+ "preversion": "npm run test && npm run build && git add -A dist",
34
34
  "postversion": "npm publish",
35
35
  "postpublish": "git push && git push --tags"
36
36
  },
37
37
  "dependencies": {
38
38
  "@webqit/observer": "^2.2.9",
39
- "@webqit/realdom": "^2.1.17",
39
+ "@webqit/realdom": "^2.1.19",
40
40
  "@webqit/stateful-js": "^3.0.27",
41
41
  "@webqit/util": "^0.8.11"
42
42
  },
43
43
  "devDependencies": {
44
- "@webqit/oohtml-ssr": "^1.2.16",
44
+ "@webqit/oohtml-ssr": "^1.2.19",
45
45
  "chai": "^4.3.4",
46
46
  "coveralls": "^3.1.1",
47
47
  "esbuild": "^0.14.43",
@@ -9,6 +9,19 @@ export default class _HTMLBindingsProvider extends HTMLContextProvider {
9
9
 
10
10
  static type = 'bindings';
11
11
 
12
+ /**
13
+ * @createRequest
14
+ */
15
+ static createRequest( fields = {} ) {
16
+ const request = super.createRequest( fields );
17
+ if ( request.detail?.startsWith( '@' ) ) {
18
+ const [ contextName, ...detail ] = request.detail.slice( 1 ).split( '.' ).map( s => s.trim() );
19
+ request.contextName = contextName;
20
+ request.detail = detail.join( '.' );
21
+ }
22
+ return request;
23
+ }
24
+
12
25
  /**
13
26
  * @matchesRequest
14
27
  */
@@ -15,9 +15,11 @@ export { Observer }
15
15
  */
16
16
  export default function init( $config = {} ) {
17
17
  const { config, window } = _init.call( this, 'bindings-api', $config, {
18
- context: { attr: { contextname: 'contextname' }, },
18
+ context: { attr: { bindingscontext: 'bindings' }, },
19
19
  api: { bind: 'bind', bindings: 'bindings', },
20
20
  } );
21
+ config.CONTEXT_API = window.webqit.oohtml.configs.CONTEXT_API;
22
+ config.context.attr.contextname = config.CONTEXT_API.attr.contextname; // Inherit this
21
23
  window.webqit.HTMLBindingsProvider = class extends _HTMLBindingsProvider {
22
24
  static get config() { return config; }
23
25
  };
@@ -31,7 +33,8 @@ export default function init( $config = {} ) {
31
33
  * The internal bindings object
32
34
  * within elements and the document object.
33
35
  */
34
- function getBindingsObject( node ) {
36
+ function getBindingsObject( config, node ) {
37
+ const window = this;
35
38
  if ( !_( node ).has( 'bindings' ) ) {
36
39
  const bindingsObj = Object.create( null );
37
40
  _( node ).set( 'bindings', bindingsObj );
@@ -41,6 +44,13 @@ function getBindingsObject( node ) {
41
44
  detachBindingsContext.call( this, node, mutation.key );
42
45
  } else { attachBindingsContext.call( this, node, mutation.key ); }
43
46
  }
47
+ const props = Object.keys( bindingsObj );
48
+ const targetNode = node === window.document ? window.document.documentElement : node;
49
+ if ( props.length ) {
50
+ targetNode.setAttribute( config.context.attr.bindingscontext, props.join( ' ') );
51
+ } else {
52
+ targetNode.toggleAttribute( config.context.attr.bindingscontext, false );
53
+ }
44
54
  } );
45
55
  }
46
56
  return _( node ).get( 'bindings' );
@@ -61,14 +71,15 @@ function detachBindingsContext( host, key ) {
61
71
  /**
62
72
  * Exposes Bindings with native APIs.
63
73
  *
74
+ * @param Object config
64
75
  * @param document|Element target
65
76
  * @param Object bindings
66
77
  * @param Object params
67
78
  *
68
79
  * @return Void
69
80
  */
70
- function applyBindings( target, bindings, { merge, diff, namespace } = {} ) {
71
- const bindingsObj = getBindingsObject.call( this, target );
81
+ function applyBindings( config, target, bindings, { merge, diff, namespace } = {} ) {
82
+ const bindingsObj = getBindingsObject.call( this, config, target );
72
83
  const $params = { diff, namespace };
73
84
  const exitingKeys = merge ? [] : Observer.ownKeys( bindingsObj, $params ).filter( key => !( key in bindings ) );
74
85
  return Observer.batch( bindingsObj, () => {
@@ -93,15 +104,15 @@ function exposeAPIs( config ) {
93
104
  if ( config.api.bindings in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.api.bindings }" property!` ); }
94
105
  // Definitions
95
106
  Object.defineProperty( window.document, config.api.bind, { value: function( bindings, config = {} ) {
96
- return applyBindings.call( window, window.document, bindings, config );
107
+ return applyBindings.call( window, config, window.document, bindings );
97
108
  } });
98
109
  Object.defineProperty( window.document, config.api.bindings, { get: function() {
99
- return Observer.proxy( getBindingsObject.call( window, window.document ) );
110
+ return Observer.proxy( getBindingsObject.call( window, config, window.document ) );
100
111
  } });
101
112
  Object.defineProperty( window.Element.prototype, config.api.bind, { value: function( bindings, config = {} ) {
102
- return applyBindings.call( window, this, bindings, config );
113
+ return applyBindings.call( window, config, this, bindings );
103
114
  } });
104
115
  Object.defineProperty( window.Element.prototype, config.api.bindings, { get: function() {
105
- return Observer.proxy( getBindingsObject.call( window, this ) );
116
+ return Observer.proxy( getBindingsObject.call( window, config, this ) );
106
117
  } } );
107
118
  }
@@ -23,6 +23,7 @@ export {
23
23
  */
24
24
  export default function init( $config = {} ) {
25
25
  const { config, window } = _init.call( this, 'context-api', $config, {
26
+ attr: { contextname: 'contextname', },
26
27
  api: { context: 'context', },
27
28
  } );
28
29
  window.webqit.HTMLContextProvider = HTMLContextProvider;
@@ -3,7 +3,6 @@
3
3
  * @imports
4
4
  */
5
5
  import Observer from '@webqit/observer';
6
- import { HTMLContext } from '../context-api/index.js';
7
6
  import _HTMLBindingsProvider from '../bindings-api/_HTMLBindingsProvider.js';
8
7
  import { StatefulAsyncFunction } from '@webqit/stateful-js/async';
9
8
  import { _, _init } from '../util.js';
@@ -17,22 +16,21 @@ import { _, _init } from '../util.js';
17
16
  */
18
17
  export default function init( $config = {} ) {
19
18
  const { config, window } = _init.call( this, 'html-bindings', $config, {
20
- attr: { bind: 'bind', itemIndex: 'data-index' },
19
+ attr: { binding: 'binding', itemIndex: 'data-index' },
21
20
  tokens: { nodeType: 'processing-instruction', tagStart: '?{', tagEnd: '}?', stateStart: '; [=', stateEnd: ']' },
22
21
  staticsensitivity: true,
23
22
  isomorphic: true,
24
23
  } );
25
- config.api = {
26
- bind: window.webqit.oohtml.configs.BINDINGS_API.api.bind,
27
- import: window.webqit.oohtml.configs.HTML_IMPORTS.context.api.import,
28
- };
29
- config.attrSelector = `[${ window.CSS.escape( config.attr.bind ) }]`;
30
- const braceletMatch = ( start, end ) => {
24
+ config.CONTEXT_API = window.webqit.oohtml.configs.CONTEXT_API;
25
+ config.BINDINGS_API = window.webqit.oohtml.configs.BINDINGS_API;
26
+ config.HTML_IMPORTS = window.webqit.oohtml.configs.HTML_IMPORTS;
27
+ config.attrSelector = `[${ window.CSS.escape( config.attr.binding ) }]`;
28
+ const discreteBindingsMatch = ( start, end ) => {
31
29
  const starting = `starts-with(., "${ start }")`;
32
30
  const ending = `substring(., string-length(.) - string-length("${ end }") + 1) = "${ end }"`;
33
31
  return `${ starting } and ${ ending }`;
34
32
  }
35
- config.braceletSelector = `comment()[${ braceletMatch( config.tokens.tagStart, config.tokens.tagEnd ) }]`;
33
+ config.discreteBindingsSelector = `comment()[${ discreteBindingsMatch( config.tokens.tagStart, config.tokens.tagEnd ) }]`;
36
34
  window.webqit.Observer = Observer;
37
35
  realtime.call( window, config );
38
36
  }
@@ -47,27 +45,27 @@ export default function init( $config = {} ) {
47
45
  function realtime( config ) {
48
46
  const window = this, { realdom } = window.webqit;
49
47
  // ----------------
50
- realdom.realtime( window.document ).subtree( `(${ config.braceletSelector })`, record => {
48
+ realdom.realtime( window.document ).subtree( `(${ config.discreteBindingsSelector })`, record => {
51
49
  cleanup.call( this, ...record.exits );
52
- mountBracelets.call( this, config, ...record.entrants );
50
+ mountDiscreteBindings.call( this, config, ...record.entrants );
53
51
  }, { live: true } );
54
52
  realdom.realtime( window.document ).subtree( config.attrSelector, record => {
55
53
  cleanup.call( this, ...record.exits );
56
- mountInlineSubscript.call( this, config, ...record.entrants );
54
+ mountInlineBindings.call( this, config, ...record.entrants );
57
55
  }, { live: true, timing: 'sync', staticSensitivity: config.staticsensitivity } );
58
56
  }
59
57
 
60
- function createDynamicScope( root ) {
61
- if ( _( root ).has( 'subscripts' ) ) return _( root ).get( 'subscripts' );
58
+ function createDynamicScope( config, root ) {
59
+ if ( _( root ).has( 'data-binding' ) ) return _( root ).get( 'data-binding' );
62
60
  const scope = {}, abortController = new AbortController;
63
- scope.$set = function( node, prop, val ) {
61
+ scope.$set__ = function( node, prop, val ) {
64
62
  node && ( node[ prop ] = val );
65
63
  }
66
64
  Observer.intercept( scope, {
67
65
  get: ( e, recieved, next ) => {
68
66
  if ( !( e.key in scope ) ) {
69
67
  const request = _HTMLBindingsProvider.createRequest( { detail: e.key, live: true, signal: abortController.signal } );
70
- HTMLContext.instance( root ).request( request, value => {
68
+ root[ config.CONTEXT_API.api.context ].request( request, value => {
71
69
  Observer.set( scope, e.key, value );
72
70
  } );
73
71
  }
@@ -75,27 +73,27 @@ function createDynamicScope( root ) {
75
73
  },
76
74
  has: ( e, recieved, next ) => { return next( true ); }
77
75
  } );
78
- const instance = { scope, abortController, subscripts: new Map };
79
- _( root ).set( 'subscripts', instance );
76
+ const instance = { scope, abortController, bindings: new Map };
77
+ _( root ).set( 'data-binding', instance );
80
78
  return instance;
81
79
  }
82
80
 
83
81
  function cleanup( ...entries ) {
84
82
  for ( const node of entries ) {
85
83
  const root = node.nodeName === '#text' ? node.parentNode : node;
86
- const { subscripts, abortController } = _( root ).get( 'subscripts' ) || {};
87
- if ( !subscripts?.has( node ) ) return;
88
- subscripts.get( node ).state.dispose();
89
- subscripts.get( node ).signals.forEach( s => s.abort() );
90
- subscripts.delete( node );
91
- if ( !subscripts.size ) {
84
+ const { bindings, abortController } = _( root ).get( 'data-binding' ) || {};
85
+ if ( !bindings?.has( node ) ) return;
86
+ bindings.get( node ).state.dispose();
87
+ bindings.get( node ).signals.forEach( s => s.abort() );
88
+ bindings.delete( node );
89
+ if ( !bindings.size ) {
92
90
  abortController.abort();
93
- _( root ).delete( 'subscripts' );
91
+ _( root ).delete( 'data-binding' );
94
92
  }
95
93
  }
96
94
  }
97
95
 
98
- async function mountBracelets( config, ...entries ) {
96
+ async function mountDiscreteBindings( config, ...entries ) {
99
97
  const window = this;
100
98
  const patternMatch = str => {
101
99
  const tagStart = config.tokens.tagStart.split( '' ).map( x => `\\${ x }` ).join( '' );
@@ -108,44 +106,45 @@ async function mountBracelets( config, ...entries ) {
108
106
  };
109
107
 
110
108
  const instances = entries.reduce( ( instances, node ) => {
111
- if ( node.isBracelet ) return instances;
109
+ if ( node.isBound ) return instances;
112
110
  const template = patternMatch( node.nodeValue );
113
111
  let textNode = node;
114
112
  if ( template.span ) {
115
113
  textNode = node.nextSibling;
116
114
  if ( textNode?.nodeName !== '#text' || textNode.nodeValue.length < template.span ) return instances;
117
115
  if ( textNode.nodeValue.length > template.span ) { textNode.splitText( template.span ); }
118
- } else if ( node.nextSibling ) {
119
- textNode = node.parentNode.insertBefore( node.ownerDocument.createTextNode( '' ), node.nextSibling );
120
- } else { textNode = node.parentNode.appendChild( node.ownerDocument.createTextNode( '' ) ); }
121
- textNode.isBracelet = true;
122
- let stateNode = node;
116
+ } else {
117
+ textNode = node.ownerDocument.createTextNode( '' );
118
+ node.after( textNode );
119
+ }
120
+ textNode.isBound = true;
121
+ let anchorNode = node;
123
122
  if ( window.webqit.env !== 'server' ) {
124
- stateNode.remove();
125
- stateNode = null;
123
+ anchorNode.remove();
124
+ anchorNode = null;
126
125
  }
127
- return instances.concat( { textNode, template, stateNode } );
126
+ return instances.concat( { textNode, template, anchorNode } );
128
127
  }, [] );
129
128
 
130
- for ( const { textNode, template, stateNode } of instances ) {
131
- const { scope: env, subscripts } = createDynamicScope( textNode.parentNode );
129
+ for ( const { textNode, template, anchorNode } of instances ) {
130
+ const { scope: env, bindings } = createDynamicScope( config, textNode.parentNode );
132
131
  let source = '';
133
132
  source += `let content = ((${ template.expr }) ?? '') + '';`;
134
- source += `$set(this, 'nodeValue', content);`;
135
- if ( stateNode ) { source += `$set($stateNode__, 'nodeValue', \`${ config.tokens.tagStart }${ template.expr }${ config.tokens.stateStart }\` + content.length + \`${ config.tokens.stateEnd } ${ config.tokens.tagEnd }\`);`; }
136
- const compiled = new StatefulAsyncFunction( '$signals__', `$stateNode__`, source, { env } );
133
+ source += `$set__(this, 'nodeValue', content);`;
134
+ if ( anchorNode ) { source += `$set__($anchorNode__, 'nodeValue', \`${ config.tokens.tagStart }${ template.expr }${ config.tokens.stateStart }\` + content.length + \`${ config.tokens.stateEnd } ${ config.tokens.tagEnd }\`);`; }
135
+ const compiled = new StatefulAsyncFunction( '$signals__', `$anchorNode__`, source, { env } );
137
136
  const signals = [];
138
- subscripts.set( textNode, { compiled, signals, state: await compiled.call( textNode, signals, stateNode ), } );
137
+ bindings.set( textNode, { compiled, signals, state: await compiled.call( textNode, signals, anchorNode ), } );
139
138
  }
140
139
  }
141
140
 
142
- async function mountInlineSubscript( config, ...entries ) {
141
+ async function mountInlineBindings( config, ...entries ) {
143
142
  for ( const node of entries ) {
144
- const source = parseInlineBindings( config, node.getAttribute( config.attr.bind ) );
145
- const { scope: env, subscripts } = createDynamicScope( node );
143
+ const source = parseInlineBindings( config, node.getAttribute( config.attr.binding ) );
144
+ const { scope: env, bindings } = createDynamicScope( config, node );
146
145
  const compiled = new StatefulAsyncFunction( '$signals__', source, { env } );
147
146
  const signals = [];
148
- subscripts.set( node, { compiled, signals, state: await compiled.call( node, signals ), } );
147
+ bindings.set( node, { compiled, signals, state: await compiled.call( node, signals ), } );
149
148
  }
150
149
  }
151
150
 
@@ -159,17 +158,18 @@ function parseInlineBindings( config, str ) {
159
158
  const $expr = `(${ right })`, $$expr = `(${ $expr } ?? '')`;
160
159
  if ( token === '&' ) return `this.style[\`${ param }\`] = ${ $$expr };`;
161
160
  if ( token === '%' ) return `this.classList.toggle(\`${ param }\`, !!${ $expr });`;
162
- if ( token === '@' ) {
161
+ if ( token === '~' ) {
163
162
  if ( param.endsWith( '?' ) ) return `this.toggleAttribute(\`${ param.substring( 0, -1 ).trim() }\`, !!${ $expr });`;
164
163
  return `this.setAttribute(\`${ param }\`, ${ $$expr });`;
165
164
  }
166
- if ( token === '~' ) {
165
+ if ( token === '@' ) {
167
166
  if ( validation[ param ] ) throw new Error( `Duplicate binding: ${ left }.` );
168
167
  validation[ param ] = true;
169
- if ( param === 'text' ) return `$set(this, 'textContent', ${ $$expr });`;
168
+ if ( param === 'text' ) return `$set__(this, 'textContent', ${ $$expr });`;
170
169
  if ( param === 'html' ) return `this.setHTML(${ $$expr });`;
171
170
  if ( param === 'items' ) {
172
171
  const [ iterationSpec, importSpec ] = splitOuter( right, '/' );
172
+ if ( !importSpec ) throw new Error( `Invalid ${ token }items spec: ${ str }; no import specifier.` );
173
173
  let [ raw, production, kind, iteratee ] = iterationSpec.trim().match( /(.*?[\)\s+])(of|in)([\(\{\[\s+].*)/i ) || [];
174
174
  if ( !raw ) throw new Error( `Invalid ${ token }items spec: ${ str }.` );
175
175
  if ( production.startsWith( '(' ) ) {
@@ -179,7 +179,7 @@ function parseInlineBindings( config, str ) {
179
179
  const indices = kind === 'in' ? production[ 2 ] : ( production[ 1 ] || '$index__' );
180
180
  return `
181
181
  let $iteratee__ = ${ iteratee };
182
- let $import__ = this.${ config.api.import }( ${ importSpec.trim() }, true );
182
+ let $import__ = this.${ config.HTML_IMPORTS.context.api.import }( ${ importSpec.trim() }, true );
183
183
  $signals__.push( $import__ );
184
184
 
185
185
  if ( $import__.value && $iteratee__ ) {
@@ -198,12 +198,12 @@ function parseInlineBindings( config, str ) {
198
198
  if ( $itemNode__ ) {
199
199
  $existing__.delete( $key___ );
200
200
  } else {
201
- $itemNode__ = ( Array.isArray( $import__.value ) ? $import__.value[ 0 ] : ( $import__.value.content ? $import__.value.content.firstElementChild : $import__.value ) ).cloneNode( true );
201
+ $itemNode__ = ( Array.isArray( $import__.value ) ? $import__.value[ 0 ] : ( $import__.value instanceof window.HTMLTemplateElement ? $import__.value.content.firstElementChild : $import__.value ) ).cloneNode( true );
202
202
  $itemNode__.setAttribute( "${ config.attr.itemIndex }", $key___ );
203
203
  this.appendChild( $itemNode__ );
204
204
  }
205
-
206
- $itemNode__.${ config.api.bind }( $itemBinding__ );
205
+
206
+ $itemNode__.${ config.BINDINGS_API.api.bind }( $itemBinding__ );
207
207
  if ( ${ kind === 'in' ? `!( ${ production[ 0 ] } in $iteratee__ )` : `typeof ${ production[ 0 ] } === 'undefined'` } ) { $itemNode__.remove(); }
208
208
  }
209
209
  $existing__.forEach( x => x.remove() );
@@ -0,0 +1,217 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _HTMLImportsContext from './_HTMLImportsProvider.js';
6
+ import { _ } from '../util.js';
7
+
8
+ /**
9
+ * Creates the HTMLImportElement class.
10
+ *
11
+ * @param Object config
12
+ *
13
+ * @return HTMLImportElement
14
+ */
15
+ export default function( config ) {
16
+ const window = this, { realdom } = window.webqit;
17
+ const BaseElement = config.import.tagName.includes( '-' ) ? window.HTMLElement : class {};
18
+ class HTMLImportElement extends BaseElement {
19
+
20
+ /**
21
+ * @instance
22
+ *
23
+ * @param HTMLElement node
24
+ *
25
+ * @returns
26
+ */
27
+ static instance( node ) {
28
+ if ( config.import.tagName.includes( '-' ) && ( node instanceof this ) ) return node;
29
+ return _( node ).get( 'import::instance' ) || new this( node );
30
+ }
31
+
32
+ /**
33
+ * @constructor
34
+ */
35
+ constructor( ...args ) {
36
+ super();
37
+ // --------
38
+ const el = args[ 0 ] || this;
39
+ _( el ).set( 'import::instance', this );
40
+ Object.defineProperty( this, 'el', { get: () => el, configurable: false } );
41
+
42
+ const priv = {};
43
+ Object.defineProperty( this, '#', { get: () => priv, configurable: false } );
44
+ priv.slottedElements = new Set;
45
+
46
+ priv.setAnchorNode = anchorNode => {
47
+ priv.anchorNode = anchorNode;
48
+ _( anchorNode ).set( 'anchoredNode@imports', this.el );
49
+ };
50
+
51
+ priv.importRequest = ( callback, signal = null ) => {
52
+ const request = _HTMLImportsContext.createRequest( { detail: priv.moduleRef && !priv.moduleRef.includes( '#' ) ? priv.moduleRef + '#' : priv.moduleRef, live: signal && true, signal } );
53
+ ( this.el.isConnected ? this.el.parentNode : priv.anchorNode.parentNode )[ config.CONTEXT_API.api.context ].request( request, response => {
54
+ callback( ( response instanceof window.HTMLTemplateElement ? [ ...response.content.children ] : (
55
+ Array.isArray( response ) ? response : response && [ response ]
56
+ ) ) || [] );
57
+ } );
58
+ };
59
+
60
+ priv.hydrate = ( anchorNode, slottedElements ) => {
61
+ // ----------------
62
+ priv.moduleRef = ( this.el.getAttribute( config.import.attr.moduleref ) || '' ).trim();
63
+ priv.setAnchorNode( anchorNode );
64
+ priv.autoRestore( () => {
65
+ slottedElements.forEach( slottedElement => {
66
+ priv.slottedElements.add( slottedElement );
67
+ _( slottedElement ).set( 'slot@imports', this.el );
68
+ } );
69
+ } );
70
+ // ----------------
71
+ priv.hydrationImportRequest = new AbortController;
72
+ priv.importRequest( fragments => {
73
+ if ( priv.originalsRemapped ) { return this.fill( fragments ); }
74
+ const identifiersMap = fragments.map( fragment => ( { el: fragment, fragmentDef: fragment.getAttribute( config.template.attr.fragmentdef ) || '', tagName: fragment.tagName, } ) );
75
+ slottedElements.forEach( slottedElement => {
76
+ const tagName = slottedElement.tagName, fragmentDef = slottedElement.getAttribute( config.template.attr.fragmentdef ) || '';
77
+ const originalsMatch = identifiersMap.filter( fragmentIdentifiers => tagName === fragmentIdentifiers.tagName && fragmentDef === fragmentIdentifiers.fragmentDef );
78
+ if ( originalsMatch.length !== 1 ) return;
79
+ _( slottedElement ).set( 'original@imports', originalsMatch[ 0 ].el );
80
+ } );
81
+ priv.originalsRemapped = true;
82
+ }, priv.hydrationImportRequest.signal );
83
+ };
84
+
85
+ priv.autoRestore = ( callback = null ) => {
86
+ priv.autoRestoreRealtime?.disconnect();
87
+ if ( callback ) callback();
88
+ if ( !priv.slottedElements.size ) {
89
+ priv.anchorNode.replaceWith( this.el );
90
+ return;
91
+ }
92
+ const autoRestoreRealtime = realdom.realtime( window.document ).observe( [ ...priv.slottedElements ], record => {
93
+ record.exits.forEach( outgoingNode => {
94
+ _( outgoingNode ).delete( 'slot@imports' );
95
+ priv.slottedElements.delete( outgoingNode );
96
+ } );
97
+ if ( !priv.slottedElements.size ) {
98
+ autoRestoreRealtime.disconnect();
99
+ // At this point, ignore if this is a removal involving the whole parent node
100
+ if ( !record.target.isConnected ) return;
101
+ priv.anchorNode.replaceWith( this.el );
102
+ }
103
+ }, { subtree: true, timing: 'sync', generation: 'exits' } );
104
+ priv.autoRestoreRealtime = autoRestoreRealtime;
105
+ };
106
+
107
+ priv.connectedCallback = () => {
108
+ // In case this is DOM node relocation or induced reinsertion into the DOM
109
+ if ( priv.slottedElements.size ) throw new Error( `Illegal reinsertion into the DOM; import slot is not empty!` );
110
+ // Totally initialize this instance?
111
+ if ( !priv.anchorNode ) { priv.setAnchorNode( this.createAnchorNode() ); }
112
+ if ( priv.moduleRefRealtime ) return;
113
+ priv.moduleRefRealtime = realdom.realtime( this.el ).attr( config.import.attr.moduleref, ( record, { signal } ) => {
114
+ priv.moduleRef = record.value;
115
+ // Below, we ignore first restore from hydration
116
+ priv.importRequest( fragments => !priv.hydrationImportRequest && this.fill( fragments ), signal );
117
+ }, { live: true, timing: 'sync', lifecycleSignals: true } );
118
+ // Must come after
119
+ priv.hydrationImportRequest?.abort();
120
+ priv.hydrationImportRequest = null;
121
+ };
122
+
123
+ priv.disconnectedCallback = () => {
124
+ priv.hydrationImportRequest?.abort();
125
+ priv.hydrationImportRequest = null;
126
+ if ( priv.anchorNode.isConnected ) return;
127
+ priv.moduleRefRealtime?.disconnect();
128
+ priv.moduleRefRealtime = null;
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Creates the slot's anchor node.
134
+ *
135
+ * @return Element
136
+ */
137
+ createAnchorNode() {
138
+ if ( !config.isomorphic ) { return window.document.createTextNode( '' ) }
139
+ return window.document.createComment( this.el.outerHTML );
140
+ }
141
+
142
+ /**
143
+ * Fills the slot with slottableElements
144
+ *
145
+ * @param Iterable slottableElements
146
+ *
147
+ * @return void
148
+ */
149
+ fill( slottableElements ) {
150
+ if ( Array.isArray( slottableElements ) ) { slottableElements = new Set( slottableElements ) }
151
+ this[ '#' ].autoRestore( () => {
152
+ this[ '#' ].slottedElements.forEach( slottedElement => {
153
+ const slottedElementOriginal = _( slottedElement ).get( 'original@imports' );
154
+ // If still available in source, simply leave unchanged
155
+ // otherwise remove it from slot... to reflect this change
156
+ if ( slottableElements.has( slottedElementOriginal ) ) {
157
+ slottableElements.delete( slottedElementOriginal );
158
+ } else {
159
+ this[ '#' ].slottedElements.delete( slottedElement );
160
+ // This removal will not be caught
161
+ slottedElement.remove();
162
+ }
163
+ } );
164
+ // Make sure anchor node is what's in place...
165
+ // not the import element itslef - but all only when we have slottableElements.size
166
+ if ( this.el.isConnected && slottableElements.size ) {
167
+ this.el.replaceWith( this[ '#' ].anchorNode );
168
+ }
169
+ // Insert slottables now
170
+ slottableElements.forEach( slottableElement => {
171
+ // Clone each slottable element and give it a reference to its original
172
+ const slottableElementClone = slottableElement.cloneNode( true );
173
+ // The folllowing references must be set before adding to DODM
174
+ if ( !slottableElementClone.hasAttribute( config.template.attr.fragmentdef ) ) {
175
+ slottableElementClone.toggleAttribute( config.template.attr.fragmentdef, true );
176
+ }
177
+ _( slottableElementClone ).set( 'original@imports', slottableElement );
178
+ _( slottableElementClone ).set( 'slot@imports', this.el );
179
+ this[ '#' ].slottedElements.add( slottableElementClone );
180
+ this[ '#' ].anchorNode.before( slottableElementClone );
181
+ } );
182
+ } );
183
+ }
184
+
185
+ /**
186
+ * Empty slot.
187
+ *
188
+ * @return void
189
+ */
190
+ empty() { this[ '#' ].slottedElements.forEach( slottedElement => slottedElement.remove() ); }
191
+
192
+ /**
193
+ * Returns the slot's anchorNode.
194
+ *
195
+ * @return array
196
+ */
197
+ get anchorNode() { return this[ '#' ].anchorNode; }
198
+
199
+ /**
200
+ * Returns the slot's module reference, if any.
201
+ *
202
+ * @return string
203
+ */
204
+ get moduleRef() { return this[ '#' ].moduleRef; }
205
+
206
+ /**
207
+ * Returns the slot's slotted elements.
208
+ *
209
+ * @return array
210
+ */
211
+ get slottedElements() { return this[ '#' ].slottedElements; }
212
+ }
213
+ if ( config.import.tagName.includes( '-' ) ) {
214
+ customElements.define( config.import.tagName, HTMLImportElement );
215
+ }
216
+ return HTMLImportElement;
217
+ }