@webqit/oohtml 5.0.7 → 5.0.8
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 +78 -32
- package/dist/bindings-api.js.map +3 -3
- package/dist/context-api.js +67 -25
- package/dist/context-api.js.map +3 -3
- package/dist/data-binding.js +120 -81
- package/dist/data-binding.js.map +4 -4
- package/dist/html-imports.js +70 -27
- package/dist/html-imports.js.map +3 -3
- package/dist/main.js +937 -282
- package/dist/main.js.map +3 -3
- package/dist/main.lite.js +115 -49
- package/dist/main.lite.js.map +3 -3
- package/dist/namespaced-html.js +69 -27
- package/dist/namespaced-html.js.map +3 -3
- package/dist/scoped-css.js +70 -28
- package/dist/scoped-css.js.map +3 -3
- package/dist/scoped-js.js +109 -68
- package/dist/scoped-js.js.map +4 -4
- package/package.json +3 -3
- package/src/bindings-api/index.js +80 -75
- package/src/context-api/DOMContext.js +2 -1
- package/src/data-binding/index.js +1 -1
- package/src/html-imports/_HTMLImportElement.js +4 -2
- package/src/namespaced-html/index.js +3 -2
- package/src/scoped-css/index.js +2 -1
- package/src/scoped-js/index.js +71 -68
- package/test/bindings-api.test.js +3 -1
- package/test/imports.test.js +8 -0
- package/test/modules.test.js +6 -4
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"wicg-proposal"
|
|
15
15
|
],
|
|
16
16
|
"homepage": "https://webqit.io/tooling/oohtml",
|
|
17
|
-
"version": "5.0.
|
|
17
|
+
"version": "5.0.8",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"version:next": "npm version prerelease --preid=next"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@webqit/realdom": "^2.1.
|
|
42
|
+
"@webqit/realdom": "^2.1.36",
|
|
43
43
|
"@webqit/use-live": "^0.5.49",
|
|
44
44
|
"@webqit/util": "^0.8.16"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@webqit/oohtml-ssr": "^2.2.
|
|
47
|
+
"@webqit/oohtml-ssr": "^2.2.4",
|
|
48
48
|
"chai": "^4.3.4",
|
|
49
49
|
"coveralls": "^3.1.1",
|
|
50
50
|
"esbuild": "^0.14.43",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
+
import { isElement } from '@webqit/realdom';
|
|
5
6
|
import DOMBindingsContext from './DOMBindingsContext.js';
|
|
6
7
|
import { _wq, _init, _splitOuter } from '../util.js';
|
|
7
8
|
|
|
@@ -10,13 +11,13 @@ import { _wq, _init, _splitOuter } from '../util.js';
|
|
|
10
11
|
*
|
|
11
12
|
* @param Object $config
|
|
12
13
|
*/
|
|
13
|
-
export default function init(
|
|
14
|
-
const { config, window } = _init.call(
|
|
14
|
+
export default function init($config = {}) {
|
|
15
|
+
const { config, window } = _init.call(this, 'bindings-api', $config, {
|
|
15
16
|
attr: { bindingsreflection: 'bindings' },
|
|
16
17
|
api: { bind: 'bind', bindings: 'bindings', },
|
|
17
|
-
}
|
|
18
|
+
});
|
|
18
19
|
window.webqit.DOMBindingsContext = DOMBindingsContext;
|
|
19
|
-
exposeAPIs.call(
|
|
20
|
+
exposeAPIs.call(window, config);
|
|
20
21
|
realtime.call(window, config);
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -26,38 +27,38 @@ export default function init( $config = {} ) {
|
|
|
26
27
|
* The internal bindings object
|
|
27
28
|
* within elements and the document object.
|
|
28
29
|
*/
|
|
29
|
-
function getBindings(
|
|
30
|
+
function getBindings(config, node) {
|
|
30
31
|
const window = this, { webqit: { Observer, oohtml: { configs: { CONTEXT_API: ctxConfig } } } } = window;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Observer.observe(
|
|
35
|
-
if (
|
|
36
|
-
const bindingsParse = parseBindingsAttr(
|
|
32
|
+
if (!_wq(node).has('bindings')) {
|
|
33
|
+
const bindingsObj = Object.create(null);
|
|
34
|
+
_wq(node).set('bindings', bindingsObj);
|
|
35
|
+
Observer.observe(bindingsObj, mutations => {
|
|
36
|
+
if (isElement(node)) {
|
|
37
|
+
const bindingsParse = parseBindingsAttr(node.getAttribute(config.attr.bindingsreflection) || '');
|
|
37
38
|
const bindingsParseBefore = new Map(bindingsParse);
|
|
38
|
-
for (
|
|
39
|
-
if (
|
|
40
|
-
if (
|
|
41
|
-
else bindingsParse.set(
|
|
39
|
+
for (const m of mutations) {
|
|
40
|
+
if (m.detail?.publish !== false) {
|
|
41
|
+
if (m.type === 'delete') bindingsParse.delete(m.key);
|
|
42
|
+
else bindingsParse.set(m.key, undefined);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
if (
|
|
45
|
-
node.setAttribute(
|
|
46
|
-
} else if (
|
|
45
|
+
if (bindingsParse.size && bindingsParse.size !== bindingsParseBefore.size) {
|
|
46
|
+
node.setAttribute(config.attr.bindingsreflection, `{ ${[...bindingsParse.entries()].map(([key, value]) => value === undefined ? key : `${key}: ${value}`).join(', ')} }`);
|
|
47
|
+
} else if (!bindingsParse.size) node.toggleAttribute(config.attr.bindingsreflection, false);
|
|
47
48
|
} else {
|
|
48
|
-
const contextsApi = node[
|
|
49
|
-
for (
|
|
50
|
-
if (
|
|
51
|
-
const ctx = contextsApi.find(
|
|
52
|
-
if (
|
|
53
|
-
} else if (
|
|
54
|
-
contextsApi.attach(
|
|
49
|
+
const contextsApi = node[ctxConfig.api.contexts];
|
|
50
|
+
for (const m of mutations) {
|
|
51
|
+
if (m.type === 'delete') {
|
|
52
|
+
const ctx = contextsApi.find(DOMBindingsContext.kind, m.key);
|
|
53
|
+
if (ctx) contextsApi.detach(ctx);
|
|
54
|
+
} else if (!contextsApi.find(DOMBindingsContext.kind, m.key)) {
|
|
55
|
+
contextsApi.attach(new DOMBindingsContext(m.key));
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return _wq(node).get('bindings');
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/**
|
|
@@ -67,22 +68,26 @@ function getBindings( config, node ) {
|
|
|
67
68
|
*
|
|
68
69
|
* @return Void
|
|
69
70
|
*/
|
|
70
|
-
function exposeAPIs(
|
|
71
|
-
|
|
71
|
+
function exposeAPIs(config) {
|
|
72
|
+
const window = this, { webqit: { Observer } } = window;
|
|
72
73
|
// The Bindings APIs
|
|
73
|
-
[
|
|
74
|
+
[window.Document.prototype, window.Element.prototype, window.ShadowRoot.prototype].forEach(prototype => {
|
|
74
75
|
// No-conflict assertions
|
|
75
|
-
const type = prototype === window.Document.prototype ? 'Document' : (
|
|
76
|
-
if (
|
|
77
|
-
if (
|
|
76
|
+
const type = prototype === window.Document.prototype ? 'Document' : (prototype === window.ShadowRoot.prototype ? 'ShadowRoot' : 'Element');
|
|
77
|
+
if (config.api.bind in prototype) { throw new Error(`The ${type} prototype already has a "${config.api.bind}" API!`); }
|
|
78
|
+
if (config.api.bindings in prototype) { throw new Error(`The ${type} prototype already has a "${config.api.bindings}" API!`); }
|
|
78
79
|
// Definitions
|
|
79
|
-
Object.defineProperty(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
Object.defineProperty(prototype, config.api.bind, {
|
|
81
|
+
value: function (bindings, options = {}) {
|
|
82
|
+
return applyBindings.call(window, config, this, bindings, options);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
Object.defineProperty(prototype, config.api.bindings, {
|
|
86
|
+
get: function () {
|
|
87
|
+
return Observer.proxy(getBindings.call(window, config, this));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
/**
|
|
@@ -95,15 +100,15 @@ function exposeAPIs( config ) {
|
|
|
95
100
|
*
|
|
96
101
|
* @return Void
|
|
97
102
|
*/
|
|
98
|
-
function applyBindings(
|
|
103
|
+
function applyBindings(config, target, bindings, { merge, diff, publish, namespace } = {}) {
|
|
99
104
|
const window = this, { webqit: { Observer } } = window;
|
|
100
|
-
const bindingsObj = getBindings.call(
|
|
105
|
+
const bindingsObj = getBindings.call(this, config, target);
|
|
101
106
|
const $params = { diff, namespace, detail: { publish } };
|
|
102
|
-
const exitingKeys = merge ? [] : Object.keys(
|
|
103
|
-
return Observer.batch(
|
|
104
|
-
if (
|
|
105
|
-
return Observer.set(
|
|
106
|
-
}, $params
|
|
107
|
+
const exitingKeys = merge ? [] : Object.keys(bindingsObj).filter(key => !(key in bindings));
|
|
108
|
+
return Observer.batch(bindingsObj, () => {
|
|
109
|
+
if (exitingKeys.length) { Observer.deleteProperties(bindingsObj, exitingKeys, $params); }
|
|
110
|
+
return Observer.set(bindingsObj, bindings, $params);
|
|
111
|
+
}, $params);
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
/**
|
|
@@ -119,47 +124,47 @@ function realtime(config) {
|
|
|
119
124
|
// ------------
|
|
120
125
|
const attachBindingsContext = (host, key) => {
|
|
121
126
|
const contextsApi = host[configs.CONTEXT_API.api.contexts];
|
|
122
|
-
if (
|
|
123
|
-
contextsApi.attach(
|
|
127
|
+
if (!contextsApi.find(DOMBindingsContext.kind, key)) {
|
|
128
|
+
contextsApi.attach(new DOMBindingsContext(key));
|
|
124
129
|
}
|
|
125
130
|
};
|
|
126
131
|
const detachBindingsContext = (host, key) => {
|
|
127
132
|
let ctx, contextsApi = host[configs.CONTEXT_API.api.contexts];
|
|
128
|
-
while(
|
|
133
|
+
while (ctx = contextsApi.find(DOMBindingsContext.kind, key)) contextsApi.detach(ctx);
|
|
129
134
|
};
|
|
130
135
|
// ------------
|
|
131
|
-
realdom.realtime(window.document).query(
|
|
132
|
-
record.exits.forEach(
|
|
136
|
+
realdom.realtime(window.document).query(`[${window.CSS.escape(config.attr.bindingsreflection)}]`, record => {
|
|
137
|
+
record.exits.forEach(entry => detachBindingsContext(entry));
|
|
133
138
|
record.entrants.forEach(entry => {
|
|
134
|
-
const bindingsParse = parseBindingsAttr(
|
|
135
|
-
const newData = [
|
|
136
|
-
if (
|
|
137
|
-
for (
|
|
138
|
-
attachBindingsContext(
|
|
139
|
+
const bindingsParse = parseBindingsAttr(entry.getAttribute(config.attr.bindingsreflection) || '');
|
|
140
|
+
const newData = [...bindingsParse.entries()].filter(([k, v]) => v !== undefined);
|
|
141
|
+
if (newData.length) entry[config.api.bind](Object.fromEntries(newData), { merge: true, publish: false });
|
|
142
|
+
for (const [key] of bindingsParse) {
|
|
143
|
+
attachBindingsContext(entry, key);
|
|
139
144
|
}
|
|
140
|
-
}
|
|
145
|
+
});
|
|
141
146
|
}, { id: 'bindings:dom', live: true, subtree: 'cross-roots', timing: 'sync', eventDetails: true });
|
|
142
|
-
|
|
143
|
-
const bindingsObj = getBindings.call(
|
|
144
|
-
const bindingsParse = parseBindingsAttr(
|
|
145
|
-
const oldBindings = parseBindingsAttr(
|
|
146
|
-
for (
|
|
147
|
-
if (
|
|
148
|
-
if (
|
|
149
|
-
attachBindingsContext(
|
|
150
|
-
} else if (
|
|
151
|
-
if (
|
|
152
|
-
detachBindingsContext(
|
|
153
|
-
} else if (
|
|
154
|
-
Observer.set(
|
|
147
|
+
realdom.realtime(window.document, 'attr').observe(config.attr.bindingsreflection, record => {
|
|
148
|
+
const bindingsObj = getBindings.call(window, config, record.target);
|
|
149
|
+
const bindingsParse = parseBindingsAttr(record.value || '');
|
|
150
|
+
const oldBindings = parseBindingsAttr(record.oldValue || '');
|
|
151
|
+
for (const key of new Set([...bindingsParse.keys(), ...oldBindings.keys()])) {
|
|
152
|
+
if (!oldBindings.has(key)) {
|
|
153
|
+
if (bindingsParse.get(key) !== undefined) Observer.set(bindingsObj, key, bindingsParse.get(key), { detail: { publish: false } });
|
|
154
|
+
attachBindingsContext(record.target, key);
|
|
155
|
+
} else if (!bindingsParse.has(key)) {
|
|
156
|
+
if (oldBindings.get(key) !== undefined) Observer.deleteProperty(bindingsObj, key, { detail: { publish: false } });
|
|
157
|
+
detachBindingsContext(record.target, key);
|
|
158
|
+
} else if (bindingsParse.get(key) !== oldBindings.get(key)) {
|
|
159
|
+
Observer.set(bindingsObj, key, bindingsParse.get(key), { detail: { publish: false } });
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
|
-
|
|
162
|
+
}, { id: 'bindings:attr', subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true });
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
const parseBindingsAttr = str => {
|
|
161
166
|
str = str.trim();
|
|
162
|
-
return new Map(_splitOuter(
|
|
163
|
-
return _splitOuter(
|
|
167
|
+
return new Map(_splitOuter(str.slice(1, -1), ',').filter(s => s.trim()).map(_str => {
|
|
168
|
+
return _splitOuter(_str, ':').map(s => s.trim());
|
|
164
169
|
}));
|
|
165
170
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
+
import { isDocument, isShadowRoot } from '@webqit/realdom';
|
|
5
6
|
import DOMContexts from './DOMContexts.js';
|
|
6
7
|
import { env } from '../util.js';
|
|
7
8
|
|
|
@@ -36,7 +37,7 @@ export default class DOMContext {
|
|
|
36
37
|
/**
|
|
37
38
|
* @name
|
|
38
39
|
*/
|
|
39
|
-
get name() { return
|
|
40
|
+
get name() { return isDocument(this.host) || isShadowRoot(this.host) ? Infinity : this.host.getAttribute( this.configs.CONTEXT_API.attr.contextname ); }
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* @subscribed()
|
|
@@ -225,7 +225,7 @@ function compileInlineBindings( config, str ) {
|
|
|
225
225
|
$existing__.delete( $key___ );
|
|
226
226
|
$exec__($itemNode__, '${ config.BINDINGS_API.api.bind }', $itemBinding__ );
|
|
227
227
|
} else {
|
|
228
|
-
$itemNode__ = ( Array.isArray( $import__.value ) ? $import__.value[ 0 ] : ( $import__.value
|
|
228
|
+
$itemNode__ = ( Array.isArray( $import__.value ) ? $import__.value[ 0 ] : ( $import__.value?.nodeName === 'TEMPLATE' ? $import__.value.content.firstElementChild : $import__.value ) ).cloneNode( true );
|
|
229
229
|
$itemNode__.setAttribute( "${ config.attr.itemIndex }", $key___ );
|
|
230
230
|
$exec__($itemNode__, '${ config.BINDINGS_API.api.bind }', $itemBinding__ );
|
|
231
231
|
$exec__(this, 'appendChild', $itemNode__ );
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
+
import { isNodeInterface } from '@webqit/realdom';
|
|
5
6
|
import HTMLImportsContext from './HTMLImportsContext.js';
|
|
6
7
|
import { _wq, env } from '../util.js';
|
|
7
8
|
|
|
@@ -26,7 +27,7 @@ export default function () {
|
|
|
26
27
|
* @returns
|
|
27
28
|
*/
|
|
28
29
|
static instance(node) {
|
|
29
|
-
if (configs.HTML_IMPORTS.elements.import.includes('-') && (node
|
|
30
|
+
if (configs.HTML_IMPORTS.elements.import.includes('-') && (node.nodeName === this.nodeName)) return node;
|
|
30
31
|
return _wq(node).get('import::instance') || new this(node);
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -57,7 +58,7 @@ export default function () {
|
|
|
57
58
|
const moduleRef = priv.moduleRef.includes('#') ? priv.moduleRef : `${priv.moduleRef}#`/* for live children */;
|
|
58
59
|
const request = { ...HTMLImportsContext.createRequest(moduleRef), live: signal && true, signal, diff: !moduleRef.endsWith('#') };
|
|
59
60
|
parentNode[configs.CONTEXT_API.api.contexts].request(request, response => {
|
|
60
|
-
callback((response
|
|
61
|
+
callback((isNodeInterface(response, 'HTMLTemplateElement') ? [...response.content.children] : (
|
|
61
62
|
Array.isArray(response) ? response : response && [response]
|
|
62
63
|
)) || []);
|
|
63
64
|
});
|
|
@@ -88,6 +89,7 @@ export default function () {
|
|
|
88
89
|
priv.slottedElements.add(slottedElement);
|
|
89
90
|
});
|
|
90
91
|
priv.originalsRemapped = true;
|
|
92
|
+
priv.autoRestore();
|
|
91
93
|
});
|
|
92
94
|
};
|
|
93
95
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
+
import { isDocument, isShadowRoot } from '@webqit/realdom';
|
|
5
6
|
import DOMNamingContext from './DOMNamingContext.js';
|
|
6
7
|
import { _wq, _init, _splitOuter, _fromHash, _toHash, getInternalAttrInteraction, internalAttrInteraction } from '../util.js';
|
|
7
8
|
|
|
@@ -122,7 +123,7 @@ export function getOwnNamespaceObject(node) {
|
|
|
122
123
|
if (!_wq(node).has('namespace')) {
|
|
123
124
|
const namespaceObj = Object.create(null);
|
|
124
125
|
_wq(node).set('namespace', namespaceObj);
|
|
125
|
-
const isDocumentRoot =
|
|
126
|
+
const isDocumentRoot = isDocument(node) || isShadowRoot(node);
|
|
126
127
|
Object.defineProperty(namespaceObj, Symbol.toStringTag, {
|
|
127
128
|
get() {
|
|
128
129
|
return isDocumentRoot ? 'RootNamespaceRegistry' : 'NamespaceRegistry';
|
|
@@ -140,7 +141,7 @@ export function getOwnNamespaceObject(node) {
|
|
|
140
141
|
*/
|
|
141
142
|
export function getOwnerNamespaceObject(node, forID = false) {
|
|
142
143
|
const window = this, { webqit: { oohtml: { configs: { NAMESPACED_HTML: config } } } } = window;
|
|
143
|
-
const isDocumentRoot =
|
|
144
|
+
const isDocumentRoot = isDocument(node) || isShadowRoot(node);
|
|
144
145
|
return getOwnNamespaceObject.call(window, isDocumentRoot ? node : ((forID ? node.parentNode : node)?.closest/*can be documentFragment when Shadow DOM*/?.(config.namespaceSelector) || node.getRootNode()));
|
|
145
146
|
}
|
|
146
147
|
|
package/src/scoped-css/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
+
import { isElement } from '@webqit/realdom';
|
|
5
6
|
import { rewriteSelector, getOwnerNamespaceObject, getNamespaceUUID } from '../namespaced-html/index.js';
|
|
6
7
|
import { _init, _toHash } from '../util.js';
|
|
7
8
|
|
|
@@ -76,7 +77,7 @@ function realtime( config ) {
|
|
|
76
77
|
const scopeSelector = style.scoped && ( supportsHAS ? `:has(> style[rand-${ sourceHash }])` : `[rand-${ sourceHash }]` );
|
|
77
78
|
const supportsScope = style.scoped && window.CSSScopeRule && false/* Disabled for buggy behaviour: rewriting selectorText within an @scope block invalidates the scoping */;
|
|
78
79
|
const scopeRoot = style.scoped && style.parentNode || style.getRootNode();
|
|
79
|
-
if ( scopeRoot
|
|
80
|
+
if ( isElement(scopeRoot) ) {
|
|
80
81
|
scopeRoot[ config.api.styleSheets ].push( style );
|
|
81
82
|
if ( !inBrowser ) return;
|
|
82
83
|
( supportsHAS ? style : scopeRoot ).toggleAttribute( `rand-${ sourceHash }`, true );
|
package/src/scoped-js/index.js
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
|
+
import { isElement } from '@webqit/realdom';
|
|
1
2
|
import { resolveParams } from '@webqit/use-live/params';
|
|
2
3
|
import { _wq, _init, _toHash, _fromHash } from '../util.js';
|
|
3
4
|
|
|
4
5
|
export default function init({ advanced = {}, ...$config }) {
|
|
5
|
-
const { config, window } = _init.call(
|
|
6
|
+
const { config, window } = _init.call(this, 'scoped-js', $config, {
|
|
6
7
|
script: { retention: 'retain', mimeTypes: 'module|text/javascript|application/javascript', timing: 'auto' },
|
|
7
8
|
api: { scripts: 'scripts' },
|
|
8
9
|
advanced: resolveParams(advanced),
|
|
9
|
-
}
|
|
10
|
-
const customTypes = Array.isArray(
|
|
11
|
-
config.scriptSelector =
|
|
10
|
+
});
|
|
11
|
+
const customTypes = Array.isArray(config.script.mimeTypes) ? config.script.mimeTypes : config.script.mimeTypes.split('|').filter(t => t);
|
|
12
|
+
config.scriptSelector = customTypes.map(t => `script[type="${window.CSS.escape(t)}"]:not([oohtmlignore])`).concat(`script:not([type])`).join(',');
|
|
12
13
|
window.webqit.oohtml.Script = {
|
|
13
|
-
compileCache: [
|
|
14
|
-
execute: execute.bind(
|
|
14
|
+
compileCache: [new Map, new Map,],
|
|
15
|
+
execute: execute.bind(window, config),
|
|
15
16
|
};
|
|
16
|
-
exposeAPIs.call(
|
|
17
|
-
realtime.call(
|
|
17
|
+
exposeAPIs.call(window, config);
|
|
18
|
+
realtime.call(window, config);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function exposeAPIs(
|
|
21
|
+
function exposeAPIs(config) {
|
|
21
22
|
const window = this, { webqit: { nextKeyword, matchPrologDirective } } = window;
|
|
22
23
|
const scriptsMap = new Map;
|
|
23
|
-
if (
|
|
24
|
-
[
|
|
25
|
-
Object.defineProperty(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if (config.api.scripts in window.Element.prototype) { throw new Error(`The "Element" class already has a "${config.api.scripts}" property!`); }
|
|
25
|
+
[window.ShadowRoot.prototype, window.Element.prototype].forEach(proto => {
|
|
26
|
+
Object.defineProperty(proto, config.api.scripts, {
|
|
27
|
+
get: function () {
|
|
28
|
+
if (!scriptsMap.has(this)) { scriptsMap.set(this, []); }
|
|
29
|
+
return scriptsMap.get(this);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
Object.defineProperties(window.HTMLScriptElement.prototype, {
|
|
31
34
|
scoped: {
|
|
32
35
|
configurable: true,
|
|
33
|
-
get() { return this.hasAttribute(
|
|
34
|
-
set(
|
|
36
|
+
get() { return this.hasAttribute('scoped'); },
|
|
37
|
+
set(value) { this.toggleAttribute('scoped', value); },
|
|
35
38
|
},
|
|
36
39
|
live: {
|
|
37
40
|
configurable: true,
|
|
@@ -41,19 +44,19 @@ function exposeAPIs( config ) {
|
|
|
41
44
|
return matchPrologDirective(scriptContents, true);
|
|
42
45
|
},
|
|
43
46
|
},
|
|
44
|
-
}
|
|
47
|
+
});
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
// Script runner
|
|
48
|
-
async function execute(
|
|
51
|
+
async function execute(config, execHash) {
|
|
49
52
|
const window = this, { realdom } = window.webqit;
|
|
50
|
-
const exec = _fromHash(
|
|
51
|
-
if (
|
|
53
|
+
const exec = _fromHash(execHash);
|
|
54
|
+
if (!exec) throw new Error(`Argument must be a valid exec hash.`);
|
|
52
55
|
const { script, compiledScript, thisContext } = exec;
|
|
53
56
|
// Honour retention flag
|
|
54
|
-
if (
|
|
57
|
+
if (config.script.retention === 'dispose') {
|
|
55
58
|
script.remove();
|
|
56
|
-
} else if (
|
|
59
|
+
} else if (config.script.retention === 'hidden') {
|
|
57
60
|
script.textContent = `"source hidden"`;
|
|
58
61
|
} else {
|
|
59
62
|
setTimeout(async () => {
|
|
@@ -62,73 +65,73 @@ async function execute( config, execHash ) {
|
|
|
62
65
|
}
|
|
63
66
|
// Execute and save state
|
|
64
67
|
const varScope = script.scoped ? thisContext : script.getRootNode();
|
|
65
|
-
if (
|
|
66
|
-
_wq(
|
|
68
|
+
if (!_wq(varScope).has('scriptEnv')) {
|
|
69
|
+
_wq(varScope).set('scriptEnv', Object.create(null));
|
|
67
70
|
}
|
|
68
|
-
const liveProgramHandle = await (
|
|
69
|
-
if (
|
|
70
|
-
realdom.realtime(
|
|
71
|
-
if (
|
|
72
|
-
if (
|
|
73
|
-
}, { id: 'scoped-js:script-exits', subtree: 'cross-roots', timing: 'sync', generation: 'exits' }
|
|
71
|
+
const liveProgramHandle = await (await compiledScript.bind(thisContext, _wq(varScope).get('scriptEnv'))).execute();
|
|
72
|
+
if (script.live) { Object.defineProperty(script, 'liveProgramHandle', { value: liveProgramHandle }); }
|
|
73
|
+
realdom.realtime(window.document).observe(script, () => {
|
|
74
|
+
if (script.live) { liveProgramHandle.abort(); }
|
|
75
|
+
if (isElement(thisContext)) { thisContext[config.api.scripts]?.splice(thisContext[config.api.scripts].indexOf(script, 1)); }
|
|
76
|
+
}, { id: 'scoped-js:script-exits', subtree: 'cross-roots', timing: 'sync', generation: 'exits' });
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
function realtime(
|
|
77
|
-
const inBrowser = Object.getOwnPropertyDescriptor(
|
|
79
|
+
function realtime(config) {
|
|
80
|
+
const inBrowser = Object.getOwnPropertyDescriptor(globalThis, 'window')?.get?.toString().includes('[native code]') ?? false;
|
|
78
81
|
const window = this, { webqit: { oohtml, realdom } } = window;
|
|
79
|
-
if (
|
|
82
|
+
if (!window.HTMLScriptElement.supports) { window.HTMLScriptElement.supports = type => ['text/javascript', 'application/javascript'].includes(type); }
|
|
80
83
|
const handled = new WeakSet;
|
|
81
|
-
realdom.realtime(
|
|
82
|
-
record.entrants.forEach(
|
|
83
|
-
if (
|
|
84
|
+
realdom.realtime(window.document).query(config.scriptSelector, record => {
|
|
85
|
+
record.entrants.forEach(script => {
|
|
86
|
+
if (handled.has(script) || script.hasAttribute('oohtmlno') || (!inBrowser && !script.hasAttribute('ssr'))) return;
|
|
84
87
|
// Do compilation
|
|
85
|
-
const compiledScript = compileScript.call(
|
|
86
|
-
if (
|
|
87
|
-
handled.add(
|
|
88
|
+
const compiledScript = compileScript.call(window, config, script);
|
|
89
|
+
if (!compiledScript) return;
|
|
90
|
+
handled.add(script);
|
|
88
91
|
// Run now!!!
|
|
89
|
-
const thisContext = script.scoped ? script.parentNode || record.target : (
|
|
90
|
-
if (
|
|
91
|
-
const execHash = _toHash(
|
|
92
|
-
const manualHandling = record.type === 'query' || (
|
|
93
|
-
if (
|
|
94
|
-
script.textContent = `webqit.oohtml.Script.execute( '${
|
|
92
|
+
const thisContext = script.scoped ? script.parentNode || record.target : (script.type === 'module' ? undefined : window);
|
|
93
|
+
if (script.scoped) { thisContext[config.api.scripts].push(script); }
|
|
94
|
+
const execHash = _toHash({ script, compiledScript, thisContext });
|
|
95
|
+
const manualHandling = record.type === 'query' || (script.type && !window.HTMLScriptElement.supports(script.type)) || script.getAttribute('data-handling') === 'manual';
|
|
96
|
+
if (manualHandling || config.script.timing === 'manual') { oohtml.Script.execute(execHash); } else {
|
|
97
|
+
script.textContent = `webqit.oohtml.Script.execute( '${execHash}' );`;
|
|
95
98
|
}
|
|
96
|
-
}
|
|
97
|
-
}, { id: 'scoped-js:script-entries', live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants', eventDetails: true }
|
|
99
|
+
});
|
|
100
|
+
}, { id: 'scoped-js:script-entries', live: true, subtree: 'cross-roots', timing: 'intercept', generation: 'entrants', eventDetails: true });
|
|
98
101
|
// ---
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
function compileScript(
|
|
104
|
+
function compileScript(config, script) {
|
|
102
105
|
const window = this, { webqit: { oohtml, LiveScript, AsyncLiveScript, LiveModule } } = window;
|
|
103
|
-
|
|
106
|
+
|
|
104
107
|
let textContent = script.textContent.trim();
|
|
105
|
-
if (
|
|
106
|
-
textContent = textContent.slice(
|
|
107
|
-
Object.defineProperty(
|
|
108
|
+
if (textContent.startsWith('/*@oohtml*/if(false){') && textContent.endsWith('}/*@oohtml*/')) {
|
|
109
|
+
textContent = textContent.slice(21, -12);
|
|
110
|
+
Object.defineProperty(script, 'oohtml__textContent', { value: textContent });
|
|
108
111
|
}
|
|
109
|
-
if (
|
|
112
|
+
if (!textContent.trim().length) return;
|
|
110
113
|
|
|
111
|
-
const sourceHash = _toHash(
|
|
112
|
-
const compileCache = oohtml.Script.compileCache[
|
|
114
|
+
const sourceHash = _toHash(textContent);
|
|
115
|
+
const compileCache = oohtml.Script.compileCache[script.live ? 0 : 1];
|
|
113
116
|
let compiledScript;
|
|
114
|
-
if (
|
|
117
|
+
if (!(compiledScript = compileCache.get(sourceHash))) {
|
|
115
118
|
const { parserParams, compilerParams, runtimeParams } = config.advanced;
|
|
116
|
-
compiledScript = new (
|
|
119
|
+
compiledScript = new (script.type === 'module' ? LiveModule : (LiveScript || AsyncLiveScript))(textContent, {
|
|
117
120
|
liveMode: script.live,
|
|
118
|
-
exportNamespace: `#${
|
|
119
|
-
fileName: `${
|
|
121
|
+
exportNamespace: `#${script.id}`,
|
|
122
|
+
fileName: `${window.document.url?.split('#')?.[0] || ''}#${script.id}`,
|
|
120
123
|
parserParams,
|
|
121
124
|
compilerParams,
|
|
122
125
|
runtimeParams,
|
|
123
|
-
}
|
|
124
|
-
compileCache.set(
|
|
126
|
+
});
|
|
127
|
+
compileCache.set(sourceHash, compiledScript);
|
|
125
128
|
}
|
|
126
129
|
return compiledScript;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
export function idleCompiler(
|
|
132
|
+
export function idleCompiler(node) {
|
|
130
133
|
const window = this, { webqit: { oohtml: { configs: { SCOPED_JS: config } } } } = window;
|
|
131
|
-
[
|
|
132
|
-
compileScript.call(
|
|
133
|
-
}
|
|
134
|
+
[...(node?.querySelectorAll(config.scriptSelector) || [])].forEach(script => {
|
|
135
|
+
compileScript.call(window, config, script);
|
|
136
|
+
});
|
|
134
137
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
-
import { createDocument } from './index.js';
|
|
6
|
+
import { createDocument, delay } from './index.js';
|
|
7
7
|
|
|
8
8
|
describe(`Bindings API`, function() {
|
|
9
9
|
|
|
@@ -19,7 +19,9 @@ describe(`Bindings API`, function() {
|
|
|
19
19
|
} );
|
|
20
20
|
|
|
21
21
|
it ( `Bindings objects should be observable...`, async function() {
|
|
22
|
+
await delay( 200 );
|
|
22
23
|
const { webqit: { Observer } } = window;
|
|
24
|
+
|
|
23
25
|
let idReceived = null;
|
|
24
26
|
Observer.observe( document.bindings, records => {
|
|
25
27
|
idReceived = records[ 0 ].key;
|
package/test/imports.test.js
CHANGED
|
@@ -31,6 +31,8 @@ describe(`HTML Imports`, function() {
|
|
|
31
31
|
const templateEl = document.querySelector( 'template' );
|
|
32
32
|
let added = document.createElement( 'div' );
|
|
33
33
|
templateEl.content.appendChild( added );
|
|
34
|
+
|
|
35
|
+
await delay( 0 );
|
|
34
36
|
console.log('\n\n\n\n', document.body.outerHTML);
|
|
35
37
|
expect( document.body.children ).to.have.length( 3 );
|
|
36
38
|
} );
|
|
@@ -203,11 +205,17 @@ describe(`HTML Imports`, function() {
|
|
|
203
205
|
|
|
204
206
|
const routingElement = document.body.firstElementChild;
|
|
205
207
|
expect( routingElement.firstElementChild.nodeName ).to.eq( 'TEXTAREA' );
|
|
208
|
+
|
|
206
209
|
routingElement.setAttribute( 'importscontext', 'temp0/temp1/temp2' );
|
|
210
|
+
await delay( 0 );
|
|
207
211
|
expect( routingElement.firstElementChild.nodeName ).to.eq( 'SELECT' );
|
|
212
|
+
|
|
208
213
|
routingElement.removeChild( routingElement.firstElementChild );
|
|
214
|
+
await delay( 0 );
|
|
209
215
|
expect( routingElement.firstElementChild.nodeName ).to.eq( 'IMPORT' );
|
|
216
|
+
|
|
210
217
|
routingElement.setAttribute( 'importscontext', 'temp0' );
|
|
218
|
+
await delay( 0 );
|
|
211
219
|
expect( routingElement.firstElementChild.nodeName ).to.eq( 'INPUT' );
|
|
212
220
|
} );
|
|
213
221
|
|