@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.
- package/dist/bindings-api.js +1 -1
- package/dist/bindings-api.js.map +2 -2
- package/dist/context-api.js +1 -1
- package/dist/context-api.js.map +2 -2
- package/dist/data-binding.js +54 -0
- package/dist/data-binding.js.map +7 -0
- package/dist/html-imports.js +1 -1
- package/dist/html-imports.js.map +3 -3
- package/dist/main.js +23 -23
- package/dist/main.js.map +3 -3
- package/dist/namespaced-html.js +2 -0
- package/dist/{html-namespaces.js.map → namespaced-html.js.map} +3 -3
- package/dist/scoped-css.js +1 -1
- package/dist/scoped-css.js.map +2 -2
- package/dist/scoped-js.js +2 -2
- package/dist/scoped-js.js.map +3 -3
- package/package.json +5 -5
- package/src/bindings-api/_HTMLBindingsProvider.js +13 -0
- package/src/bindings-api/index.js +19 -8
- package/src/context-api/index.js +1 -0
- package/src/{html-bindings → data-binding}/index.js +52 -52
- package/src/html-imports/_HTMLImportElement copy.js +217 -0
- package/src/html-imports/_HTMLImportElement.js +27 -18
- package/src/html-imports/_HTMLImportsProvider.js +2 -2
- package/src/html-imports/index.js +27 -45
- package/src/index.js +9 -9
- package/src/{html-namespaces → namespaced-html}/index.js +1 -1
- package/src/scoped-js/index.js +2 -2
- package/test/imports.test.js +6 -4
- package/test/index.js +1 -1
- package/test/scoped-js.test.js +2 -2
- package/dist/html-bindings.js +0 -54
- package/dist/html-bindings.js.map +0 -7
- package/dist/html-bracelets.js +0 -2
- package/dist/html-bracelets.js.map +0 -7
- package/dist/html-namespaces.js +0 -2
- /package/src/{html-bindings → data-binding}/targets.browser.js +0 -0
- /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.
|
|
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
|
|
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.
|
|
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.
|
|
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: {
|
|
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
|
|
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
|
|
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
|
}
|
package/src/context-api/index.js
CHANGED
|
@@ -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: {
|
|
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.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
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.
|
|
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.
|
|
48
|
+
realdom.realtime( window.document ).subtree( `(${ config.discreteBindingsSelector })`, record => {
|
|
51
49
|
cleanup.call( this, ...record.exits );
|
|
52
|
-
|
|
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
|
-
|
|
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( '
|
|
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.$
|
|
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
|
-
|
|
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,
|
|
79
|
-
_( root ).set( '
|
|
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 {
|
|
87
|
-
if ( !
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if ( !
|
|
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( '
|
|
91
|
+
_( root ).delete( 'data-binding' );
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
async function
|
|
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.
|
|
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
|
|
119
|
-
textNode = node.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
123
|
+
anchorNode.remove();
|
|
124
|
+
anchorNode = null;
|
|
126
125
|
}
|
|
127
|
-
return instances.concat( { textNode, template,
|
|
126
|
+
return instances.concat( { textNode, template, anchorNode } );
|
|
128
127
|
}, [] );
|
|
129
128
|
|
|
130
|
-
for ( const { textNode, template,
|
|
131
|
-
const { scope: env,
|
|
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 += `$
|
|
135
|
-
if (
|
|
136
|
-
const compiled = new StatefulAsyncFunction( '$signals__', `$
|
|
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
|
-
|
|
137
|
+
bindings.set( textNode, { compiled, signals, state: await compiled.call( textNode, signals, anchorNode ), } );
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
|
|
142
|
-
async function
|
|
141
|
+
async function mountInlineBindings( config, ...entries ) {
|
|
143
142
|
for ( const node of entries ) {
|
|
144
|
-
const source = parseInlineBindings( config, node.getAttribute( config.attr.
|
|
145
|
-
const { scope: env,
|
|
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
|
-
|
|
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 `$
|
|
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.
|
|
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
|
+
}
|