@microsoft/fast-element 2.0.0-beta.9 → 2.0.1
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/.eslintrc.json +1 -1
- package/CHANGELOG.json +518 -0
- package/CHANGELOG.md +181 -1
- package/README.md +1 -9
- package/api-extractor.context.json +14 -0
- package/api-extractor.di.json +14 -0
- package/dist/context/context.api.json +1068 -0
- package/dist/di/di.api.json +4929 -0
- package/dist/dts/binding/binding.d.ts +49 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/{templating/binding-signal.d.ts → binding/signal.d.ts} +19 -4
- package/dist/dts/{templating/binding-two-way.d.ts → binding/two-way.d.ts} +9 -5
- package/dist/dts/components/attributes.d.ts +7 -1
- package/dist/dts/components/element-controller.d.ts +104 -8
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/fast-definitions.d.ts +6 -0
- package/dist/dts/components/hydration.d.ts +56 -0
- package/dist/dts/components/install-hydration.d.ts +1 -0
- package/dist/dts/context.d.ts +29 -15
- package/dist/dts/di/di.d.ts +0 -5
- package/dist/dts/dom-policy.d.ts +83 -0
- package/dist/dts/dom.d.ts +100 -0
- package/dist/dts/hydration/target-builder.d.ts +63 -0
- package/dist/dts/index.d.ts +33 -26
- package/dist/dts/index.rollup.d.ts +0 -1
- package/dist/dts/index.rollup.debug.d.ts +0 -1
- package/dist/dts/interfaces.d.ts +32 -82
- package/dist/dts/metadata.d.ts +6 -5
- package/dist/dts/observation/arrays.d.ts +1 -1
- package/dist/dts/observation/observable.bench.d.ts +18 -0
- package/dist/dts/observation/observable.d.ts +5 -5
- package/dist/dts/pending-task.d.ts +19 -7
- package/dist/dts/platform.d.ts +11 -2
- package/dist/dts/polyfills.d.ts +0 -8
- package/dist/dts/styles/css-binding-directive.d.ts +60 -0
- package/dist/dts/styles/css.d.ts +9 -7
- package/dist/dts/styles/element-styles.d.ts +1 -14
- package/dist/dts/styles/host.d.ts +2 -5
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/compiler.d.ts +11 -13
- package/dist/dts/templating/{binding.d.ts → html-binding-directive.d.ts} +21 -41
- package/dist/dts/templating/html-directive.d.ts +44 -140
- package/dist/dts/templating/install-hydratable-view-templates.d.ts +1 -0
- package/dist/dts/templating/node-observation.d.ts +11 -1
- package/dist/dts/templating/ref.d.ts +4 -0
- package/dist/dts/templating/render.bench.d.ts +3 -0
- package/dist/dts/templating/render.d.ts +49 -9
- package/dist/dts/templating/repeat-basic-reverse.bench.d.ts +3 -0
- package/dist/dts/templating/repeat-basic-shift.bench.d.ts +3 -0
- package/dist/dts/templating/repeat.d.ts +31 -9
- package/dist/dts/templating/template.d.ts +97 -12
- package/dist/dts/templating/view.d.ts +146 -29
- package/dist/dts/templating/when-basic.bench.d.ts +3 -0
- package/dist/dts/templating/when-conditional.bench.d.ts +3 -0
- package/dist/dts/templating/when-switch.bench.d.ts +3 -0
- package/dist/dts/templating/when.d.ts +3 -1
- package/dist/dts/testing/fakes.d.ts +12 -1
- package/dist/dts/tsdoc-metadata.json +1 -1
- package/dist/dts/utilities.d.ts +55 -1
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/{templating/binding-signal.js → binding/signal.js} +22 -6
- package/dist/esm/{templating/binding-two-way.js → binding/two-way.js} +18 -12
- package/dist/esm/components/attributes.js +19 -6
- package/dist/esm/components/element-controller.js +319 -49
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/fast-definitions.js +12 -4
- package/dist/esm/components/fast-element.js +3 -1
- package/dist/esm/components/hydration.js +104 -0
- package/dist/esm/components/install-hydration.js +3 -0
- package/dist/esm/context.js +26 -4
- package/dist/esm/debug.js +8 -2
- package/dist/esm/di/di.js +9 -12
- package/dist/esm/dom-policy.js +345 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/target-builder.js +175 -0
- package/dist/esm/index.js +34 -25
- package/dist/esm/index.rollup.debug.js +3 -1
- package/dist/esm/index.rollup.js +3 -1
- package/dist/esm/interfaces.js +51 -3
- package/dist/esm/metadata.js +11 -8
- package/dist/esm/observation/arrays.js +1 -1
- package/dist/esm/observation/observable.bench.js +79 -0
- package/dist/esm/observation/observable.js +20 -15
- package/dist/esm/observation/update-queue.js +2 -2
- package/dist/esm/pending-task.js +13 -1
- package/dist/esm/platform.js +12 -2
- package/dist/esm/polyfills.js +3 -61
- package/dist/esm/styles/css-binding-directive.js +76 -0
- package/dist/esm/styles/css.js +14 -7
- package/dist/esm/styles/element-styles.js +0 -33
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +8 -4
- package/dist/esm/templating/compiler.js +37 -44
- package/dist/esm/templating/html-binding-directive.js +218 -0
- package/dist/esm/templating/html-directive.js +25 -152
- package/dist/esm/templating/install-hydratable-view-templates.js +17 -0
- package/dist/esm/templating/node-observation.js +14 -8
- package/dist/esm/templating/ref.js +1 -1
- package/dist/esm/templating/render.bench.js +56 -0
- package/dist/esm/templating/render.js +74 -30
- package/dist/esm/templating/repeat-basic-reverse.bench.js +43 -0
- package/dist/esm/templating/repeat-basic-shift.bench.js +43 -0
- package/dist/esm/templating/repeat.js +116 -17
- package/dist/esm/templating/template.js +135 -60
- package/dist/esm/templating/view.js +254 -34
- package/dist/esm/templating/when-basic.bench.js +36 -0
- package/dist/esm/templating/when-conditional.bench.js +39 -0
- package/dist/esm/templating/when-switch.bench.js +68 -0
- package/dist/esm/templating/when.js +12 -5
- package/dist/esm/testing/fakes.js +32 -1
- package/dist/esm/testing/fixture.js +1 -1
- package/dist/esm/utilities.js +97 -1
- package/dist/fast-element.api.json +9789 -5667
- package/dist/fast-element.d.ts +813 -2392
- package/dist/fast-element.debug.js +2788 -974
- package/dist/fast-element.debug.min.js +3 -1
- package/dist/fast-element.js +2641 -833
- package/dist/fast-element.min.js +3 -1
- package/dist/fast-element.untrimmed.d.ts +662 -314
- package/docs/{api-report.md → api-report.api.md} +238 -151
- package/docs/context/api-report.api.md +69 -0
- package/docs/di/api-report.api.md +315 -0
- package/karma.conf.cjs +2 -1
- package/package.json +59 -47
- package/scripts/run-api-extractor.js +51 -0
- package/scripts/run-benchmarks.js +46 -0
- package/tensile.config.js +12 -0
- package/dist/dts/templating/dom.d.ts +0 -41
- package/dist/esm/templating/binding.js +0 -282
- package/dist/esm/templating/dom.js +0 -49
- package/docs/guide/declaring-templates.md +0 -230
- package/docs/guide/defining-elements.md +0 -214
- package/docs/guide/leveraging-css.md +0 -253
- package/docs/guide/next-steps.md +0 -13
- package/docs/guide/observables-and-state.md +0 -213
- package/docs/guide/using-directives.md +0 -576
- package/docs/guide/working-with-shadow-dom.md +0 -296
package/dist/fast-element.js
CHANGED
|
@@ -1,10 +1,73 @@
|
|
|
1
|
+
let kernelMode;
|
|
2
|
+
const kernelAttr = "fast-kernel";
|
|
3
|
+
try {
|
|
4
|
+
if (document.currentScript) {
|
|
5
|
+
kernelMode = document.currentScript.getAttribute(kernelAttr);
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
const scripts = document.getElementsByTagName("script");
|
|
9
|
+
const currentScript = scripts[scripts.length - 1];
|
|
10
|
+
kernelMode = currentScript.getAttribute(kernelAttr);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
kernelMode = "isolate";
|
|
15
|
+
}
|
|
16
|
+
let KernelServiceId;
|
|
17
|
+
switch (kernelMode) {
|
|
18
|
+
case "share": // share the kernel across major versions
|
|
19
|
+
KernelServiceId = Object.freeze({
|
|
20
|
+
updateQueue: 1,
|
|
21
|
+
observable: 2,
|
|
22
|
+
contextEvent: 3,
|
|
23
|
+
elementRegistry: 4,
|
|
24
|
+
});
|
|
25
|
+
break;
|
|
26
|
+
case "share-v2": // only share the kernel with other v2 instances
|
|
27
|
+
KernelServiceId = Object.freeze({
|
|
28
|
+
updateQueue: 1.2,
|
|
29
|
+
observable: 2.2,
|
|
30
|
+
contextEvent: 3.2,
|
|
31
|
+
elementRegistry: 4.2,
|
|
32
|
+
});
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
// fully isolate the kernel from all other FAST instances
|
|
36
|
+
const postfix = `-${Math.random().toString(36).substring(2, 8)}`;
|
|
37
|
+
KernelServiceId = Object.freeze({
|
|
38
|
+
updateQueue: `1.2${postfix}`,
|
|
39
|
+
observable: `2.2${postfix}`,
|
|
40
|
+
contextEvent: `3.2${postfix}`,
|
|
41
|
+
elementRegistry: `4.2${postfix}`,
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Determines whether or not an object is a function.
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
const isFunction = (object) => typeof object === "function";
|
|
50
|
+
/**
|
|
51
|
+
* Determines whether or not an object is a string.
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
const isString = (object) => typeof object === "string";
|
|
55
|
+
/**
|
|
56
|
+
* A function which does nothing.
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
const noop = () => void 0;
|
|
60
|
+
|
|
61
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
1
62
|
(function ensureGlobalThis() {
|
|
2
63
|
if (typeof globalThis !== "undefined") {
|
|
3
64
|
// We're running in a modern environment.
|
|
4
65
|
return;
|
|
5
66
|
}
|
|
67
|
+
// @ts-ignore
|
|
6
68
|
if (typeof global !== "undefined") {
|
|
7
69
|
// We're running in NodeJS
|
|
70
|
+
// @ts-ignore
|
|
8
71
|
global.globalThis = global;
|
|
9
72
|
}
|
|
10
73
|
else if (typeof self !== "undefined") {
|
|
@@ -22,69 +85,8 @@
|
|
|
22
85
|
result.globalThis = result;
|
|
23
86
|
}
|
|
24
87
|
})();
|
|
25
|
-
// API-only Polyfill for trustedTypes
|
|
26
|
-
if (!globalThis.trustedTypes) {
|
|
27
|
-
globalThis.trustedTypes = {
|
|
28
|
-
createPolicy: (n, r) => r,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
// ensure FAST global - duplicated in platform.ts
|
|
32
|
-
const propConfig$1 = {
|
|
33
|
-
configurable: false,
|
|
34
|
-
enumerable: false,
|
|
35
|
-
writable: false,
|
|
36
|
-
};
|
|
37
|
-
if (globalThis.FAST === void 0) {
|
|
38
|
-
Reflect.defineProperty(globalThis, "FAST", Object.assign({ value: Object.create(null) }, propConfig$1));
|
|
39
|
-
}
|
|
40
|
-
const FAST$1 = globalThis.FAST;
|
|
41
|
-
if (FAST$1.getById === void 0) {
|
|
42
|
-
const storage = Object.create(null);
|
|
43
|
-
Reflect.defineProperty(FAST$1, "getById", Object.assign({ value(id, initialize) {
|
|
44
|
-
let found = storage[id];
|
|
45
|
-
if (found === void 0) {
|
|
46
|
-
found = initialize ? (storage[id] = initialize()) : null;
|
|
47
|
-
}
|
|
48
|
-
return found;
|
|
49
|
-
} }, propConfig$1));
|
|
50
|
-
}
|
|
51
|
-
// duplicated from DOM
|
|
52
|
-
const supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
|
|
53
|
-
"replace" in CSSStyleSheet.prototype;
|
|
54
|
-
function usableStyleTarget(target) {
|
|
55
|
-
return target === document ? document.body : target;
|
|
56
|
-
}
|
|
57
|
-
let id$1 = 0;
|
|
58
|
-
const nextStyleId = () => `fast-${++id$1}`;
|
|
59
|
-
class StyleElementStrategy {
|
|
60
|
-
constructor(styles) {
|
|
61
|
-
this.styles = styles;
|
|
62
|
-
this.styleClass = nextStyleId();
|
|
63
|
-
}
|
|
64
|
-
addStylesTo(target) {
|
|
65
|
-
target = usableStyleTarget(target);
|
|
66
|
-
const styles = this.styles;
|
|
67
|
-
const styleClass = this.styleClass;
|
|
68
|
-
for (let i = 0; i < styles.length; i++) {
|
|
69
|
-
const element = document.createElement("style");
|
|
70
|
-
element.innerHTML = styles[i];
|
|
71
|
-
element.className = styleClass;
|
|
72
|
-
target.append(element);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
removeStylesFrom(target) {
|
|
76
|
-
const styles = target.querySelectorAll(`.${this.styleClass}`);
|
|
77
|
-
target = usableStyleTarget(target);
|
|
78
|
-
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
|
79
|
-
target.removeChild(styles[i]);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (!supportsAdoptedStyleSheets) {
|
|
84
|
-
FAST$1.getById(/* KernelServiceId.styleSheetStrategy */ 5, () => StyleElementStrategy);
|
|
85
|
-
}
|
|
86
88
|
|
|
87
|
-
// ensure FAST global - duplicated
|
|
89
|
+
// ensure FAST global - duplicated debug.ts
|
|
88
90
|
const propConfig = {
|
|
89
91
|
configurable: false,
|
|
90
92
|
enumerable: false,
|
|
@@ -95,7 +97,7 @@ if (globalThis.FAST === void 0) {
|
|
|
95
97
|
}
|
|
96
98
|
/**
|
|
97
99
|
* The FAST global.
|
|
98
|
-
* @
|
|
100
|
+
* @public
|
|
99
101
|
*/
|
|
100
102
|
const FAST = globalThis.FAST;
|
|
101
103
|
if (FAST.getById === void 0) {
|
|
@@ -171,21 +173,462 @@ function createMetadataLocator() {
|
|
|
171
173
|
return metadata;
|
|
172
174
|
};
|
|
173
175
|
}
|
|
174
|
-
|
|
175
176
|
/**
|
|
177
|
+
* Makes a type noop for JSON serialization.
|
|
178
|
+
* @param type - The type to make noop for JSON serialization.
|
|
176
179
|
* @internal
|
|
177
180
|
*/
|
|
178
|
-
|
|
181
|
+
function makeSerializationNoop(type) {
|
|
182
|
+
type.prototype.toJSON = noop;
|
|
183
|
+
}
|
|
184
|
+
|
|
179
185
|
/**
|
|
180
|
-
*
|
|
186
|
+
* The type of HTML aspect to target.
|
|
187
|
+
* @public
|
|
181
188
|
*/
|
|
182
|
-
const
|
|
189
|
+
const DOMAspect = Object.freeze({
|
|
190
|
+
/**
|
|
191
|
+
* Not aspected.
|
|
192
|
+
*/
|
|
193
|
+
none: 0,
|
|
194
|
+
/**
|
|
195
|
+
* An attribute.
|
|
196
|
+
*/
|
|
197
|
+
attribute: 1,
|
|
198
|
+
/**
|
|
199
|
+
* A boolean attribute.
|
|
200
|
+
*/
|
|
201
|
+
booleanAttribute: 2,
|
|
202
|
+
/**
|
|
203
|
+
* A property.
|
|
204
|
+
*/
|
|
205
|
+
property: 3,
|
|
206
|
+
/**
|
|
207
|
+
* Content
|
|
208
|
+
*/
|
|
209
|
+
content: 4,
|
|
210
|
+
/**
|
|
211
|
+
* A token list.
|
|
212
|
+
*/
|
|
213
|
+
tokenList: 5,
|
|
214
|
+
/**
|
|
215
|
+
* An event.
|
|
216
|
+
*/
|
|
217
|
+
event: 6,
|
|
218
|
+
});
|
|
219
|
+
const createHTML$1 = html => html;
|
|
220
|
+
const fastTrustedType = globalThis.trustedTypes
|
|
221
|
+
? globalThis.trustedTypes.createPolicy("fast-html", { createHTML: createHTML$1 })
|
|
222
|
+
: { createHTML: createHTML$1 };
|
|
223
|
+
let defaultPolicy = Object.freeze({
|
|
224
|
+
createHTML(value) {
|
|
225
|
+
return fastTrustedType.createHTML(value);
|
|
226
|
+
},
|
|
227
|
+
protect(tagName, aspect, aspectName, sink) {
|
|
228
|
+
return sink;
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
const fastPolicy = defaultPolicy;
|
|
232
|
+
/**
|
|
233
|
+
* Common DOM APIs.
|
|
234
|
+
* @public
|
|
235
|
+
*/
|
|
236
|
+
const DOM = Object.freeze({
|
|
237
|
+
/**
|
|
238
|
+
* Gets the dom policy used by the templating system.
|
|
239
|
+
*/
|
|
240
|
+
get policy() {
|
|
241
|
+
return defaultPolicy;
|
|
242
|
+
},
|
|
243
|
+
/**
|
|
244
|
+
* Sets the dom policy used by the templating system.
|
|
245
|
+
* @param policy - The policy to set.
|
|
246
|
+
* @remarks
|
|
247
|
+
* This API can only be called once, for security reasons. It should be
|
|
248
|
+
* called by the application developer at the start of their program.
|
|
249
|
+
*/
|
|
250
|
+
setPolicy(value) {
|
|
251
|
+
if (defaultPolicy !== fastPolicy) {
|
|
252
|
+
throw FAST.error(1201 /* Message.onlySetDOMPolicyOnce */);
|
|
253
|
+
}
|
|
254
|
+
defaultPolicy = value;
|
|
255
|
+
},
|
|
256
|
+
/**
|
|
257
|
+
* Sets an attribute value on an element.
|
|
258
|
+
* @param element - The element to set the attribute value on.
|
|
259
|
+
* @param attributeName - The attribute name to set.
|
|
260
|
+
* @param value - The value of the attribute to set.
|
|
261
|
+
* @remarks
|
|
262
|
+
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
263
|
+
* it is set to the provided value using the standard `setAttribute` API.
|
|
264
|
+
*/
|
|
265
|
+
setAttribute(element, attributeName, value) {
|
|
266
|
+
value === null || value === undefined
|
|
267
|
+
? element.removeAttribute(attributeName)
|
|
268
|
+
: element.setAttribute(attributeName, value);
|
|
269
|
+
},
|
|
270
|
+
/**
|
|
271
|
+
* Sets a boolean attribute value.
|
|
272
|
+
* @param element - The element to set the boolean attribute value on.
|
|
273
|
+
* @param attributeName - The attribute name to set.
|
|
274
|
+
* @param value - The value of the attribute to set.
|
|
275
|
+
* @remarks
|
|
276
|
+
* If the value is true, the attribute is added; otherwise it is removed.
|
|
277
|
+
*/
|
|
278
|
+
setBooleanAttribute(element, attributeName, value) {
|
|
279
|
+
value
|
|
280
|
+
? element.setAttribute(attributeName, "")
|
|
281
|
+
: element.removeAttribute(attributeName);
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
function safeURL(tagName, aspect, aspectName, sink) {
|
|
286
|
+
return (target, name, value, ...rest) => {
|
|
287
|
+
if (isString(value)) {
|
|
288
|
+
value = value.replace(/(javascript:|vbscript:|data:)/, "");
|
|
289
|
+
}
|
|
290
|
+
sink(target, name, value, ...rest);
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function block(tagName, aspect, aspectName, sink) {
|
|
294
|
+
throw FAST.error(1209 /* Message.blockedByDOMPolicy */, {
|
|
295
|
+
aspectName,
|
|
296
|
+
tagName: tagName !== null && tagName !== void 0 ? tagName : "text",
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const defaultDOMElementGuards = {
|
|
300
|
+
a: {
|
|
301
|
+
[DOMAspect.attribute]: {
|
|
302
|
+
href: safeURL,
|
|
303
|
+
},
|
|
304
|
+
[DOMAspect.property]: {
|
|
305
|
+
href: safeURL,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
area: {
|
|
309
|
+
[DOMAspect.attribute]: {
|
|
310
|
+
href: safeURL,
|
|
311
|
+
},
|
|
312
|
+
[DOMAspect.property]: {
|
|
313
|
+
href: safeURL,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
button: {
|
|
317
|
+
[DOMAspect.attribute]: {
|
|
318
|
+
formaction: safeURL,
|
|
319
|
+
},
|
|
320
|
+
[DOMAspect.property]: {
|
|
321
|
+
formAction: safeURL,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
embed: {
|
|
325
|
+
[DOMAspect.attribute]: {
|
|
326
|
+
src: block,
|
|
327
|
+
},
|
|
328
|
+
[DOMAspect.property]: {
|
|
329
|
+
src: block,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
form: {
|
|
333
|
+
[DOMAspect.attribute]: {
|
|
334
|
+
action: safeURL,
|
|
335
|
+
},
|
|
336
|
+
[DOMAspect.property]: {
|
|
337
|
+
action: safeURL,
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
frame: {
|
|
341
|
+
[DOMAspect.attribute]: {
|
|
342
|
+
src: safeURL,
|
|
343
|
+
},
|
|
344
|
+
[DOMAspect.property]: {
|
|
345
|
+
src: safeURL,
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
iframe: {
|
|
349
|
+
[DOMAspect.attribute]: {
|
|
350
|
+
src: safeURL,
|
|
351
|
+
},
|
|
352
|
+
[DOMAspect.property]: {
|
|
353
|
+
src: safeURL,
|
|
354
|
+
srcdoc: block,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
input: {
|
|
358
|
+
[DOMAspect.attribute]: {
|
|
359
|
+
formaction: safeURL,
|
|
360
|
+
},
|
|
361
|
+
[DOMAspect.property]: {
|
|
362
|
+
formAction: safeURL,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
link: {
|
|
366
|
+
[DOMAspect.attribute]: {
|
|
367
|
+
href: block,
|
|
368
|
+
},
|
|
369
|
+
[DOMAspect.property]: {
|
|
370
|
+
href: block,
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
object: {
|
|
374
|
+
[DOMAspect.attribute]: {
|
|
375
|
+
codebase: block,
|
|
376
|
+
data: block,
|
|
377
|
+
},
|
|
378
|
+
[DOMAspect.property]: {
|
|
379
|
+
codeBase: block,
|
|
380
|
+
data: block,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
script: {
|
|
384
|
+
[DOMAspect.attribute]: {
|
|
385
|
+
src: block,
|
|
386
|
+
text: block,
|
|
387
|
+
},
|
|
388
|
+
[DOMAspect.property]: {
|
|
389
|
+
src: block,
|
|
390
|
+
text: block,
|
|
391
|
+
innerText: block,
|
|
392
|
+
textContent: block,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
style: {
|
|
396
|
+
[DOMAspect.property]: {
|
|
397
|
+
innerText: block,
|
|
398
|
+
textContent: block,
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
const blockedEvents = {
|
|
403
|
+
onabort: block,
|
|
404
|
+
onauxclick: block,
|
|
405
|
+
onbeforeinput: block,
|
|
406
|
+
onbeforematch: block,
|
|
407
|
+
onblur: block,
|
|
408
|
+
oncancel: block,
|
|
409
|
+
oncanplay: block,
|
|
410
|
+
oncanplaythrough: block,
|
|
411
|
+
onchange: block,
|
|
412
|
+
onclick: block,
|
|
413
|
+
onclose: block,
|
|
414
|
+
oncontextlost: block,
|
|
415
|
+
oncontextmenu: block,
|
|
416
|
+
oncontextrestored: block,
|
|
417
|
+
oncopy: block,
|
|
418
|
+
oncuechange: block,
|
|
419
|
+
oncut: block,
|
|
420
|
+
ondblclick: block,
|
|
421
|
+
ondrag: block,
|
|
422
|
+
ondragend: block,
|
|
423
|
+
ondragenter: block,
|
|
424
|
+
ondragleave: block,
|
|
425
|
+
ondragover: block,
|
|
426
|
+
ondragstart: block,
|
|
427
|
+
ondrop: block,
|
|
428
|
+
ondurationchange: block,
|
|
429
|
+
onemptied: block,
|
|
430
|
+
onended: block,
|
|
431
|
+
onerror: block,
|
|
432
|
+
onfocus: block,
|
|
433
|
+
onformdata: block,
|
|
434
|
+
oninput: block,
|
|
435
|
+
oninvalid: block,
|
|
436
|
+
onkeydown: block,
|
|
437
|
+
onkeypress: block,
|
|
438
|
+
onkeyup: block,
|
|
439
|
+
onload: block,
|
|
440
|
+
onloadeddata: block,
|
|
441
|
+
onloadedmetadata: block,
|
|
442
|
+
onloadstart: block,
|
|
443
|
+
onmousedown: block,
|
|
444
|
+
onmouseenter: block,
|
|
445
|
+
onmouseleave: block,
|
|
446
|
+
onmousemove: block,
|
|
447
|
+
onmouseout: block,
|
|
448
|
+
onmouseover: block,
|
|
449
|
+
onmouseup: block,
|
|
450
|
+
onpaste: block,
|
|
451
|
+
onpause: block,
|
|
452
|
+
onplay: block,
|
|
453
|
+
onplaying: block,
|
|
454
|
+
onprogress: block,
|
|
455
|
+
onratechange: block,
|
|
456
|
+
onreset: block,
|
|
457
|
+
onresize: block,
|
|
458
|
+
onscroll: block,
|
|
459
|
+
onsecuritypolicyviolation: block,
|
|
460
|
+
onseeked: block,
|
|
461
|
+
onseeking: block,
|
|
462
|
+
onselect: block,
|
|
463
|
+
onslotchange: block,
|
|
464
|
+
onstalled: block,
|
|
465
|
+
onsubmit: block,
|
|
466
|
+
onsuspend: block,
|
|
467
|
+
ontimeupdate: block,
|
|
468
|
+
ontoggle: block,
|
|
469
|
+
onvolumechange: block,
|
|
470
|
+
onwaiting: block,
|
|
471
|
+
onwebkitanimationend: block,
|
|
472
|
+
onwebkitanimationiteration: block,
|
|
473
|
+
onwebkitanimationstart: block,
|
|
474
|
+
onwebkittransitionend: block,
|
|
475
|
+
onwheel: block,
|
|
476
|
+
};
|
|
477
|
+
const defaultDOMGuards = {
|
|
478
|
+
elements: defaultDOMElementGuards,
|
|
479
|
+
aspects: {
|
|
480
|
+
[DOMAspect.attribute]: Object.assign({}, blockedEvents),
|
|
481
|
+
[DOMAspect.property]: Object.assign({ innerHTML: block }, blockedEvents),
|
|
482
|
+
[DOMAspect.event]: Object.assign({}, blockedEvents),
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
function createDomSinkGuards(config, defaults) {
|
|
486
|
+
const result = {};
|
|
487
|
+
for (const name in defaults) {
|
|
488
|
+
const overrideValue = config[name];
|
|
489
|
+
const defaultValue = defaults[name];
|
|
490
|
+
switch (overrideValue) {
|
|
491
|
+
case null:
|
|
492
|
+
// remove the default
|
|
493
|
+
break;
|
|
494
|
+
case undefined:
|
|
495
|
+
// keep the default
|
|
496
|
+
result[name] = defaultValue;
|
|
497
|
+
break;
|
|
498
|
+
default:
|
|
499
|
+
// override the default
|
|
500
|
+
result[name] = overrideValue;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// add any new sinks that were not overrides
|
|
505
|
+
for (const name in config) {
|
|
506
|
+
if (!(name in result)) {
|
|
507
|
+
result[name] = config[name];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return Object.freeze(result);
|
|
511
|
+
}
|
|
512
|
+
function createDOMAspectGuards(config, defaults) {
|
|
513
|
+
const result = {};
|
|
514
|
+
for (const aspect in defaults) {
|
|
515
|
+
const overrideValue = config[aspect];
|
|
516
|
+
const defaultValue = defaults[aspect];
|
|
517
|
+
switch (overrideValue) {
|
|
518
|
+
case null:
|
|
519
|
+
// remove the default
|
|
520
|
+
break;
|
|
521
|
+
case undefined:
|
|
522
|
+
// keep the default
|
|
523
|
+
result[aspect] = createDomSinkGuards(defaultValue, {});
|
|
524
|
+
break;
|
|
525
|
+
default:
|
|
526
|
+
// override the default
|
|
527
|
+
result[aspect] = createDomSinkGuards(overrideValue, defaultValue);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// add any new aspect guards that were not overrides
|
|
532
|
+
for (const aspect in config) {
|
|
533
|
+
if (!(aspect in result)) {
|
|
534
|
+
result[aspect] = createDomSinkGuards(config[aspect], {});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return Object.freeze(result);
|
|
538
|
+
}
|
|
539
|
+
function createElementGuards(config, defaults) {
|
|
540
|
+
const result = {};
|
|
541
|
+
for (const tag in defaults) {
|
|
542
|
+
const overrideValue = config[tag];
|
|
543
|
+
const defaultValue = defaults[tag];
|
|
544
|
+
switch (overrideValue) {
|
|
545
|
+
case null:
|
|
546
|
+
// remove the default
|
|
547
|
+
break;
|
|
548
|
+
case undefined:
|
|
549
|
+
// keep the default
|
|
550
|
+
result[tag] = createDOMAspectGuards(overrideValue, {});
|
|
551
|
+
break;
|
|
552
|
+
default:
|
|
553
|
+
// override the default aspects
|
|
554
|
+
result[tag] = createDOMAspectGuards(overrideValue, defaultValue);
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Add any new element guards that were not overrides
|
|
559
|
+
for (const tag in config) {
|
|
560
|
+
if (!(tag in result)) {
|
|
561
|
+
result[tag] = createDOMAspectGuards(config[tag], {});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return Object.freeze(result);
|
|
565
|
+
}
|
|
566
|
+
function createDOMGuards(config, defaults) {
|
|
567
|
+
return Object.freeze({
|
|
568
|
+
elements: config.elements
|
|
569
|
+
? createElementGuards(config.elements, defaults.elements)
|
|
570
|
+
: defaults.elements,
|
|
571
|
+
aspects: config.aspects
|
|
572
|
+
? createDOMAspectGuards(config.aspects, defaults.aspects)
|
|
573
|
+
: defaults.aspects,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function createTrustedType() {
|
|
577
|
+
const createHTML = html => html;
|
|
578
|
+
return globalThis.trustedTypes
|
|
579
|
+
? globalThis.trustedTypes.createPolicy("fast-html", { createHTML })
|
|
580
|
+
: { createHTML };
|
|
581
|
+
}
|
|
582
|
+
function tryGuard(aspectGuards, tagName, aspect, aspectName, sink) {
|
|
583
|
+
const sinkGuards = aspectGuards[aspect];
|
|
584
|
+
if (sinkGuards) {
|
|
585
|
+
const guard = sinkGuards[aspectName];
|
|
586
|
+
if (guard) {
|
|
587
|
+
return guard(tagName, aspect, aspectName, sink);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* A helper for creating DOM policies.
|
|
593
|
+
* @public
|
|
594
|
+
*/
|
|
595
|
+
const DOMPolicy = Object.freeze({
|
|
596
|
+
/**
|
|
597
|
+
* Creates a new DOM Policy object.
|
|
598
|
+
* @param options The options to use in creating the policy.
|
|
599
|
+
* @returns The newly created DOMPolicy.
|
|
600
|
+
*/
|
|
601
|
+
create(options = {}) {
|
|
602
|
+
var _a, _b;
|
|
603
|
+
const trustedType = (_a = options.trustedType) !== null && _a !== void 0 ? _a : createTrustedType();
|
|
604
|
+
const guards = createDOMGuards((_b = options.guards) !== null && _b !== void 0 ? _b : {}, defaultDOMGuards);
|
|
605
|
+
return Object.freeze({
|
|
606
|
+
createHTML(value) {
|
|
607
|
+
return trustedType.createHTML(value);
|
|
608
|
+
},
|
|
609
|
+
protect(tagName, aspect, aspectName, sink) {
|
|
610
|
+
var _a;
|
|
611
|
+
// Check for element-specific guards.
|
|
612
|
+
const key = (tagName !== null && tagName !== void 0 ? tagName : "").toLowerCase();
|
|
613
|
+
const elementGuards = guards.elements[key];
|
|
614
|
+
if (elementGuards) {
|
|
615
|
+
const guard = tryGuard(elementGuards, tagName, aspect, aspectName, sink);
|
|
616
|
+
if (guard) {
|
|
617
|
+
return guard;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Check for guards applicable to all nodes.
|
|
621
|
+
return ((_a = tryGuard(guards.aspects, tagName, aspect, aspectName, sink)) !== null && _a !== void 0 ? _a : sink);
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
},
|
|
625
|
+
});
|
|
183
626
|
|
|
184
627
|
/**
|
|
185
628
|
* The default UpdateQueue.
|
|
186
629
|
* @public
|
|
187
630
|
*/
|
|
188
|
-
const Updates = FAST.getById(
|
|
631
|
+
const Updates = FAST.getById(KernelServiceId.updateQueue, () => {
|
|
189
632
|
const tasks = [];
|
|
190
633
|
const pendingErrors = [];
|
|
191
634
|
const rAF = globalThis.requestAnimationFrame;
|
|
@@ -431,9 +874,9 @@ const SourceLifetime = Object.freeze({
|
|
|
431
874
|
* Common Observable APIs.
|
|
432
875
|
* @public
|
|
433
876
|
*/
|
|
434
|
-
const Observable = FAST.getById(
|
|
877
|
+
const Observable = FAST.getById(KernelServiceId.observable, () => {
|
|
435
878
|
const queueUpdate = Updates.enqueue;
|
|
436
|
-
const volatileRegex = /(:|&&|\|\||if)/;
|
|
879
|
+
const volatileRegex = /(:|&&|\|\||if|\?\.)/;
|
|
437
880
|
const notifierLookup = new WeakMap();
|
|
438
881
|
let watcher = void 0;
|
|
439
882
|
let createArrayObserver = (array) => {
|
|
@@ -476,9 +919,9 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
476
919
|
}
|
|
477
920
|
}
|
|
478
921
|
class ExpressionNotifierImplementation extends SubscriberSet {
|
|
479
|
-
constructor(
|
|
480
|
-
super(
|
|
481
|
-
this.
|
|
922
|
+
constructor(expression, initialSubscriber, isVolatileBinding = false) {
|
|
923
|
+
super(expression, initialSubscriber);
|
|
924
|
+
this.expression = expression;
|
|
482
925
|
this.isVolatileBinding = isVolatileBinding;
|
|
483
926
|
this.needsRefresh = true;
|
|
484
927
|
this.needsQueue = true;
|
|
@@ -518,13 +961,17 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
518
961
|
this.needsRefresh = this.isVolatileBinding;
|
|
519
962
|
let result;
|
|
520
963
|
try {
|
|
521
|
-
result = this.
|
|
964
|
+
result = this.expression(source, context);
|
|
522
965
|
}
|
|
523
966
|
finally {
|
|
524
967
|
watcher = previousWatcher;
|
|
525
968
|
}
|
|
526
969
|
return result;
|
|
527
970
|
}
|
|
971
|
+
// backwards compat with v1 kernel
|
|
972
|
+
disconnect() {
|
|
973
|
+
this.dispose();
|
|
974
|
+
}
|
|
528
975
|
dispose() {
|
|
529
976
|
if (this.last !== null) {
|
|
530
977
|
let current = this.first;
|
|
@@ -586,6 +1033,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
586
1033
|
}
|
|
587
1034
|
}
|
|
588
1035
|
}
|
|
1036
|
+
makeSerializationNoop(ExpressionNotifierImplementation);
|
|
589
1037
|
return Object.freeze({
|
|
590
1038
|
/**
|
|
591
1039
|
* @internal
|
|
@@ -653,20 +1101,20 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
653
1101
|
/**
|
|
654
1102
|
* Creates a {@link ExpressionNotifier} that can watch the
|
|
655
1103
|
* provided {@link Expression} for changes.
|
|
656
|
-
* @param
|
|
1104
|
+
* @param expression - The binding to observe.
|
|
657
1105
|
* @param initialSubscriber - An initial subscriber to changes in the binding value.
|
|
658
1106
|
* @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
|
|
659
1107
|
*/
|
|
660
|
-
binding(
|
|
661
|
-
return new ExpressionNotifierImplementation(
|
|
1108
|
+
binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
|
|
1109
|
+
return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
|
|
662
1110
|
},
|
|
663
1111
|
/**
|
|
664
1112
|
* Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
|
|
665
1113
|
* on every evaluation of the value.
|
|
666
|
-
* @param
|
|
1114
|
+
* @param expression - The binding to inspect.
|
|
667
1115
|
*/
|
|
668
|
-
isVolatileBinding(
|
|
669
|
-
return volatileRegex.test(
|
|
1116
|
+
isVolatileBinding(expression) {
|
|
1117
|
+
return volatileRegex.test(expression.toString());
|
|
670
1118
|
},
|
|
671
1119
|
});
|
|
672
1120
|
});
|
|
@@ -694,7 +1142,7 @@ function volatile(target, name, descriptor) {
|
|
|
694
1142
|
},
|
|
695
1143
|
});
|
|
696
1144
|
}
|
|
697
|
-
const contextEvent = FAST.getById(
|
|
1145
|
+
const contextEvent = FAST.getById(KernelServiceId.contextEvent, () => {
|
|
698
1146
|
let current = null;
|
|
699
1147
|
return {
|
|
700
1148
|
get() {
|
|
@@ -1109,7 +1557,7 @@ let defaultSpliceStrategy = Object.freeze({
|
|
|
1109
1557
|
if (changes === void 0) {
|
|
1110
1558
|
return emptyArray;
|
|
1111
1559
|
}
|
|
1112
|
-
return
|
|
1560
|
+
return project(current, changes);
|
|
1113
1561
|
}
|
|
1114
1562
|
return resetSplices;
|
|
1115
1563
|
},
|
|
@@ -1309,21 +1757,102 @@ function lengthOf(array) {
|
|
|
1309
1757
|
return array.length;
|
|
1310
1758
|
}
|
|
1311
1759
|
|
|
1312
|
-
const styleSheetCache = new Map();
|
|
1313
|
-
let DefaultStyleStrategy;
|
|
1314
|
-
function reduceStyles(styles) {
|
|
1315
|
-
return styles
|
|
1316
|
-
.map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
|
|
1317
|
-
.reduce((prev, curr) => prev.concat(curr), []);
|
|
1318
|
-
}
|
|
1319
1760
|
/**
|
|
1320
|
-
*
|
|
1761
|
+
* Captures a binding expression along with related information and capabilities.
|
|
1762
|
+
*
|
|
1321
1763
|
* @public
|
|
1322
1764
|
*/
|
|
1323
|
-
class
|
|
1765
|
+
class Binding {
|
|
1324
1766
|
/**
|
|
1325
|
-
* Creates
|
|
1326
|
-
* @param
|
|
1767
|
+
* Creates a binding.
|
|
1768
|
+
* @param evaluate - Evaluates the binding.
|
|
1769
|
+
* @param policy - The security policy to associate with this binding.
|
|
1770
|
+
* @param isVolatile - Indicates whether the binding is volatile.
|
|
1771
|
+
*/
|
|
1772
|
+
constructor(evaluate, policy, isVolatile = false) {
|
|
1773
|
+
this.evaluate = evaluate;
|
|
1774
|
+
this.policy = policy;
|
|
1775
|
+
this.isVolatile = isVolatile;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
class OneWayBinding extends Binding {
|
|
1780
|
+
createObserver(subscriber) {
|
|
1781
|
+
return Observable.binding(this.evaluate, subscriber, this.isVolatile);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Creates an standard binding.
|
|
1786
|
+
* @param expression - The binding to refresh when changed.
|
|
1787
|
+
* @param policy - The security policy to associate with th binding.
|
|
1788
|
+
* @param isVolatile - Indicates whether the binding is volatile or not.
|
|
1789
|
+
* @returns A binding configuration.
|
|
1790
|
+
* @public
|
|
1791
|
+
*/
|
|
1792
|
+
function oneWay(expression, policy, isVolatile = Observable.isVolatileBinding(expression)) {
|
|
1793
|
+
return new OneWayBinding(expression, policy, isVolatile);
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Creates an event listener binding.
|
|
1797
|
+
* @param expression - The binding to invoke when the event is raised.
|
|
1798
|
+
* @param options - Event listener options.
|
|
1799
|
+
* @returns A binding configuration.
|
|
1800
|
+
* @public
|
|
1801
|
+
*/
|
|
1802
|
+
function listener(expression, options) {
|
|
1803
|
+
const config = new OneWayBinding(expression);
|
|
1804
|
+
config.options = options;
|
|
1805
|
+
return config;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
class OneTimeBinding extends Binding {
|
|
1809
|
+
createObserver() {
|
|
1810
|
+
return this;
|
|
1811
|
+
}
|
|
1812
|
+
bind(controller) {
|
|
1813
|
+
return this.evaluate(controller.source, controller.context);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
makeSerializationNoop(OneTimeBinding);
|
|
1817
|
+
/**
|
|
1818
|
+
* Creates a one time binding
|
|
1819
|
+
* @param expression - The binding to refresh when signaled.
|
|
1820
|
+
* @param policy - The security policy to associate with th binding.
|
|
1821
|
+
* @returns A binding configuration.
|
|
1822
|
+
* @public
|
|
1823
|
+
*/
|
|
1824
|
+
function oneTime(expression, policy) {
|
|
1825
|
+
return new OneTimeBinding(expression, policy);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Normalizes the input value into a binding.
|
|
1830
|
+
* @param value - The value to create the default binding for.
|
|
1831
|
+
* @returns A binding configuration for the provided value.
|
|
1832
|
+
* @public
|
|
1833
|
+
*/
|
|
1834
|
+
function normalizeBinding$1(value) {
|
|
1835
|
+
return isFunction(value)
|
|
1836
|
+
? oneWay(value)
|
|
1837
|
+
: value instanceof Binding
|
|
1838
|
+
? value
|
|
1839
|
+
: oneTime(() => value);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
let DefaultStyleStrategy;
|
|
1843
|
+
function reduceStyles(styles) {
|
|
1844
|
+
return styles
|
|
1845
|
+
.map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
|
|
1846
|
+
.reduce((prev, curr) => prev.concat(curr), []);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Represents styles that can be applied to a custom element.
|
|
1850
|
+
* @public
|
|
1851
|
+
*/
|
|
1852
|
+
class ElementStyles {
|
|
1853
|
+
/**
|
|
1854
|
+
* Creates an instance of ElementStyles.
|
|
1855
|
+
* @param styles - The styles that will be associated with elements.
|
|
1327
1856
|
*/
|
|
1328
1857
|
constructor(styles) {
|
|
1329
1858
|
this.styles = styles;
|
|
@@ -1400,36 +1929,6 @@ class ElementStyles {
|
|
|
1400
1929
|
*/
|
|
1401
1930
|
ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
|
|
1402
1931
|
"replace" in CSSStyleSheet.prototype;
|
|
1403
|
-
/**
|
|
1404
|
-
* https://wicg.github.io/construct-stylesheets/
|
|
1405
|
-
* https://developers.google.com/web/updates/2019/02/constructable-stylesheets
|
|
1406
|
-
*
|
|
1407
|
-
* @internal
|
|
1408
|
-
*/
|
|
1409
|
-
class AdoptedStyleSheetsStrategy {
|
|
1410
|
-
constructor(styles) {
|
|
1411
|
-
this.sheets = styles.map((x) => {
|
|
1412
|
-
if (x instanceof CSSStyleSheet) {
|
|
1413
|
-
return x;
|
|
1414
|
-
}
|
|
1415
|
-
let sheet = styleSheetCache.get(x);
|
|
1416
|
-
if (sheet === void 0) {
|
|
1417
|
-
sheet = new CSSStyleSheet();
|
|
1418
|
-
sheet.replaceSync(x);
|
|
1419
|
-
styleSheetCache.set(x, sheet);
|
|
1420
|
-
}
|
|
1421
|
-
return sheet;
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
addStylesTo(target) {
|
|
1425
|
-
target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
|
|
1426
|
-
}
|
|
1427
|
-
removeStylesFrom(target) {
|
|
1428
|
-
const sheets = this.sheets;
|
|
1429
|
-
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
|
|
1433
1932
|
|
|
1434
1933
|
const registry$1 = createTypeRegistry();
|
|
1435
1934
|
/**
|
|
@@ -1468,6 +1967,85 @@ function cssDirective() {
|
|
|
1468
1967
|
};
|
|
1469
1968
|
}
|
|
1470
1969
|
|
|
1970
|
+
function handleChange(directive, controller, observer) {
|
|
1971
|
+
controller.source.style.setProperty(directive.targetAspect, observer.bind(controller));
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Enables bindings in CSS.
|
|
1975
|
+
*
|
|
1976
|
+
* @public
|
|
1977
|
+
*/
|
|
1978
|
+
class CSSBindingDirective {
|
|
1979
|
+
/**
|
|
1980
|
+
* Creates an instance of CSSBindingDirective.
|
|
1981
|
+
* @param dataBinding - The binding to use in CSS.
|
|
1982
|
+
* @param targetAspect - The CSS property to target.
|
|
1983
|
+
*/
|
|
1984
|
+
constructor(dataBinding, targetAspect) {
|
|
1985
|
+
this.dataBinding = dataBinding;
|
|
1986
|
+
this.targetAspect = targetAspect;
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Creates a CSS fragment to interpolate into the CSS document.
|
|
1990
|
+
* @returns - the string to interpolate into CSS
|
|
1991
|
+
*/
|
|
1992
|
+
createCSS(add) {
|
|
1993
|
+
add(this);
|
|
1994
|
+
return `var(${this.targetAspect})`;
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Executed when this behavior is attached to a controller.
|
|
1998
|
+
* @param controller - Controls the behavior lifecycle.
|
|
1999
|
+
*/
|
|
2000
|
+
addedCallback(controller) {
|
|
2001
|
+
var _a;
|
|
2002
|
+
const element = controller.source;
|
|
2003
|
+
if (!element.$cssBindings) {
|
|
2004
|
+
element.$cssBindings = new Map();
|
|
2005
|
+
const setAttribute = element.setAttribute;
|
|
2006
|
+
element.setAttribute = (attr, value) => {
|
|
2007
|
+
setAttribute.call(element, attr, value);
|
|
2008
|
+
if (attr === "style") {
|
|
2009
|
+
element.$cssBindings.forEach((v, k) => handleChange(k, v.controller, v.observer));
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
const observer = (_a = controller[this.targetAspect]) !== null && _a !== void 0 ? _a : (controller[this.targetAspect] = this.dataBinding.createObserver(this, this));
|
|
2014
|
+
observer.controller = controller;
|
|
2015
|
+
controller.source.$cssBindings.set(this, { controller, observer });
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Executed when this behavior's host is connected.
|
|
2019
|
+
* @param controller - Controls the behavior lifecycle.
|
|
2020
|
+
*/
|
|
2021
|
+
connectedCallback(controller) {
|
|
2022
|
+
handleChange(this, controller, controller[this.targetAspect]);
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Executed when this behavior is detached from a controller.
|
|
2026
|
+
* @param controller - Controls the behavior lifecycle.
|
|
2027
|
+
*/
|
|
2028
|
+
removedCallback(controller) {
|
|
2029
|
+
if (controller.source.$cssBindings) {
|
|
2030
|
+
controller.source.$cssBindings.delete(this);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Called when a subject this instance has subscribed to changes.
|
|
2035
|
+
* @param subject - The subject of the change.
|
|
2036
|
+
* @param args - The event args detailing the change that occurred.
|
|
2037
|
+
*
|
|
2038
|
+
* @internal
|
|
2039
|
+
*/
|
|
2040
|
+
handleChange(_, observer) {
|
|
2041
|
+
handleChange(this, observer.controller, observer);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
CSSDirective.define(CSSBindingDirective);
|
|
2045
|
+
|
|
2046
|
+
const marker$1 = `${Math.random().toString(36).substring(2, 8)}`;
|
|
2047
|
+
let varId = 0;
|
|
2048
|
+
const nextCSSVariable = () => `--v${marker$1}${++varId}`;
|
|
1471
2049
|
function collectStyles(strings, values) {
|
|
1472
2050
|
const styles = [];
|
|
1473
2051
|
let cssString = "";
|
|
@@ -1478,7 +2056,13 @@ function collectStyles(strings, values) {
|
|
|
1478
2056
|
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
1479
2057
|
cssString += strings[i];
|
|
1480
2058
|
let value = values[i];
|
|
1481
|
-
if (
|
|
2059
|
+
if (isFunction(value)) {
|
|
2060
|
+
value = new CSSBindingDirective(oneWay(value), nextCSSVariable()).createCSS(add);
|
|
2061
|
+
}
|
|
2062
|
+
else if (value instanceof Binding) {
|
|
2063
|
+
value = new CSSBindingDirective(value, nextCSSVariable()).createCSS(add);
|
|
2064
|
+
}
|
|
2065
|
+
else if (CSSDirective.getForInstance(value) !== void 0) {
|
|
1482
2066
|
value = value.createCSS(add);
|
|
1483
2067
|
}
|
|
1484
2068
|
if (value instanceof ElementStyles || value instanceof CSSStyleSheet) {
|
|
@@ -1550,68 +2134,119 @@ css.partial = (strings, ...values) => {
|
|
|
1550
2134
|
const { styles, behaviors } = collectStyles(strings, values);
|
|
1551
2135
|
return new CSSPartial(styles, behaviors);
|
|
1552
2136
|
};
|
|
1553
|
-
/**
|
|
1554
|
-
* @deprecated Use css.partial instead.
|
|
1555
|
-
* @public
|
|
1556
|
-
*/
|
|
1557
|
-
const cssPartial = css.partial;
|
|
1558
2137
|
|
|
2138
|
+
const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
|
|
2139
|
+
const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
|
|
2140
|
+
const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
|
|
2141
|
+
const repeatViewEndMarker = /fe-repeat\$\$end\$\$(\d+)\$\$fe-repeat/;
|
|
2142
|
+
const elementBoundaryStartMarker = /^(?:.{0,1000})fe-eb\$\$start\$\$(.+?)\$\$fe-eb/;
|
|
2143
|
+
const elementBoundaryEndMarker = /fe-eb\$\$end\$\$(.{0,1000})\$\$fe-eb(?:.{0,1000})$/;
|
|
2144
|
+
function isComment$1(node) {
|
|
2145
|
+
return node && node.nodeType === Node.COMMENT_NODE;
|
|
2146
|
+
}
|
|
1559
2147
|
/**
|
|
1560
|
-
*
|
|
1561
|
-
* @
|
|
2148
|
+
* Markup utilities to aid in template hydration.
|
|
2149
|
+
* @internal
|
|
1562
2150
|
*/
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2151
|
+
const HydrationMarkup = Object.freeze({
|
|
2152
|
+
attributeMarkerName: "data-fe-b",
|
|
2153
|
+
attributeBindingSeparator: " ",
|
|
2154
|
+
contentBindingStartMarker(index, uniqueId) {
|
|
2155
|
+
return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
|
|
2156
|
+
},
|
|
2157
|
+
contentBindingEndMarker(index, uniqueId) {
|
|
2158
|
+
return `fe-b$$end$$${index}$$${uniqueId}$$fe-b`;
|
|
2159
|
+
},
|
|
2160
|
+
repeatStartMarker(index) {
|
|
2161
|
+
return `fe-repeat$$start$$${index}$$fe-repeat`;
|
|
2162
|
+
},
|
|
2163
|
+
repeatEndMarker(index) {
|
|
2164
|
+
return `fe-repeat$$end$$${index}$$fe-repeat`;
|
|
2165
|
+
},
|
|
2166
|
+
isContentBindingStartMarker(content) {
|
|
2167
|
+
return bindingStartMarker.test(content);
|
|
2168
|
+
},
|
|
2169
|
+
isContentBindingEndMarker(content) {
|
|
2170
|
+
return bindingEndMarker.test(content);
|
|
2171
|
+
},
|
|
2172
|
+
isRepeatViewStartMarker(content) {
|
|
2173
|
+
return repeatViewStartMarker.test(content);
|
|
2174
|
+
},
|
|
2175
|
+
isRepeatViewEndMarker(content) {
|
|
2176
|
+
return repeatViewEndMarker.test(content);
|
|
2177
|
+
},
|
|
2178
|
+
isElementBoundaryStartMarker(node) {
|
|
2179
|
+
return isComment$1(node) && elementBoundaryStartMarker.test(node.data.trim());
|
|
2180
|
+
},
|
|
2181
|
+
isElementBoundaryEndMarker(node) {
|
|
2182
|
+
return isComment$1(node) && elementBoundaryEndMarker.test(node.data);
|
|
2183
|
+
},
|
|
1569
2184
|
/**
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
2185
|
+
* Returns the indexes of the ViewBehaviorFactories affecting
|
|
2186
|
+
* attributes for the element, or null if no factories were found.
|
|
1572
2187
|
*/
|
|
1573
|
-
|
|
2188
|
+
parseAttributeBinding(node) {
|
|
2189
|
+
const attr = node.getAttribute(this.attributeMarkerName);
|
|
2190
|
+
return attr === null
|
|
2191
|
+
? attr
|
|
2192
|
+
: attr.split(this.attributeBindingSeparator).map(i => parseInt(i));
|
|
2193
|
+
},
|
|
1574
2194
|
/**
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
2195
|
+
* Parses the ViewBehaviorFactory index from string data. Returns
|
|
2196
|
+
* the binding index or null if the index cannot be retrieved.
|
|
1577
2197
|
*/
|
|
1578
|
-
|
|
2198
|
+
parseContentBindingStartMarker(content) {
|
|
2199
|
+
return parseIndexAndIdMarker(bindingStartMarker, content);
|
|
2200
|
+
},
|
|
2201
|
+
parseContentBindingEndMarker(content) {
|
|
2202
|
+
return parseIndexAndIdMarker(bindingEndMarker, content);
|
|
2203
|
+
},
|
|
1579
2204
|
/**
|
|
1580
|
-
*
|
|
1581
|
-
* @param element - The element to set the attribute value on.
|
|
1582
|
-
* @param attributeName - The attribute name to set.
|
|
1583
|
-
* @param value - The value of the attribute to set.
|
|
1584
|
-
* @remarks
|
|
1585
|
-
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
1586
|
-
* it is set to the provided value using the standard `setAttribute` API.
|
|
2205
|
+
* Parses the index of a repeat directive from a content string.
|
|
1587
2206
|
*/
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
2207
|
+
parseRepeatStartMarker(content) {
|
|
2208
|
+
return parseIntMarker(repeatViewStartMarker, content);
|
|
2209
|
+
},
|
|
2210
|
+
parseRepeatEndMarker(content) {
|
|
2211
|
+
return parseIntMarker(repeatViewEndMarker, content);
|
|
1592
2212
|
},
|
|
1593
2213
|
/**
|
|
1594
|
-
*
|
|
1595
|
-
* @param element - The element to set the boolean attribute value on.
|
|
1596
|
-
* @param attributeName - The attribute name to set.
|
|
1597
|
-
* @param value - The value of the attribute to set.
|
|
1598
|
-
* @remarks
|
|
1599
|
-
* If the value is true, the attribute is added; otherwise it is removed.
|
|
2214
|
+
* Parses element Id from element boundary markers
|
|
1600
2215
|
*/
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
2216
|
+
parseElementBoundaryStartMarker(content) {
|
|
2217
|
+
return parseStringMarker(elementBoundaryStartMarker, content.trim());
|
|
2218
|
+
},
|
|
2219
|
+
parseElementBoundaryEndMarker(content) {
|
|
2220
|
+
return parseStringMarker(elementBoundaryEndMarker, content);
|
|
1605
2221
|
},
|
|
1606
2222
|
});
|
|
2223
|
+
function parseIntMarker(regex, content) {
|
|
2224
|
+
const match = regex.exec(content);
|
|
2225
|
+
return match === null ? match : parseInt(match[1]);
|
|
2226
|
+
}
|
|
2227
|
+
function parseStringMarker(regex, content) {
|
|
2228
|
+
const match = regex.exec(content);
|
|
2229
|
+
return match === null ? match : match[1];
|
|
2230
|
+
}
|
|
2231
|
+
function parseIndexAndIdMarker(regex, content) {
|
|
2232
|
+
const match = regex.exec(content);
|
|
2233
|
+
return match === null ? match : [parseInt(match[1]), match[2]];
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* @internal
|
|
2237
|
+
*/
|
|
2238
|
+
const Hydratable = Symbol.for("fe-hydration");
|
|
2239
|
+
function isHydratable(value) {
|
|
2240
|
+
return value[Hydratable] === Hydratable;
|
|
2241
|
+
}
|
|
1607
2242
|
|
|
1608
2243
|
const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
|
|
1609
2244
|
const interpolationStart = `${marker}{`;
|
|
1610
2245
|
const interpolationEnd = `}${marker}`;
|
|
1611
2246
|
const interpolationEndLength = interpolationEnd.length;
|
|
1612
|
-
let id = 0;
|
|
2247
|
+
let id$1 = 0;
|
|
1613
2248
|
/** @internal */
|
|
1614
|
-
const nextId = () => `${marker}-${++id}`;
|
|
2249
|
+
const nextId = () => `${marker}-${++id$1}`;
|
|
1615
2250
|
/**
|
|
1616
2251
|
* Common APIs related to markup generation.
|
|
1617
2252
|
* @public
|
|
@@ -1681,67 +2316,6 @@ const Parser = Object.freeze({
|
|
|
1681
2316
|
},
|
|
1682
2317
|
});
|
|
1683
2318
|
|
|
1684
|
-
/**
|
|
1685
|
-
* Bridges between ViewBehaviors and HostBehaviors, enabling a host to
|
|
1686
|
-
* control ViewBehaviors.
|
|
1687
|
-
* @public
|
|
1688
|
-
*/
|
|
1689
|
-
const ViewBehaviorOrchestrator = Object.freeze({
|
|
1690
|
-
/**
|
|
1691
|
-
* Creates a ViewBehaviorOrchestrator.
|
|
1692
|
-
* @param source - The source to to associate behaviors with.
|
|
1693
|
-
* @returns A ViewBehaviorOrchestrator.
|
|
1694
|
-
*/
|
|
1695
|
-
create(source) {
|
|
1696
|
-
const behaviors = [];
|
|
1697
|
-
const targets = {};
|
|
1698
|
-
let unbindables = null;
|
|
1699
|
-
let isConnected = false;
|
|
1700
|
-
return {
|
|
1701
|
-
source,
|
|
1702
|
-
context: ExecutionContext.default,
|
|
1703
|
-
targets,
|
|
1704
|
-
get isBound() {
|
|
1705
|
-
return isConnected;
|
|
1706
|
-
},
|
|
1707
|
-
addBehaviorFactory(factory, target) {
|
|
1708
|
-
const nodeId = factory.nodeId || (factory.nodeId = nextId());
|
|
1709
|
-
factory.id || (factory.id = nextId());
|
|
1710
|
-
this.addTarget(nodeId, target);
|
|
1711
|
-
this.addBehavior(factory.createBehavior());
|
|
1712
|
-
},
|
|
1713
|
-
addTarget(nodeId, target) {
|
|
1714
|
-
targets[nodeId] = target;
|
|
1715
|
-
},
|
|
1716
|
-
addBehavior(behavior) {
|
|
1717
|
-
behaviors.push(behavior);
|
|
1718
|
-
if (isConnected) {
|
|
1719
|
-
behavior.bind(this);
|
|
1720
|
-
}
|
|
1721
|
-
},
|
|
1722
|
-
onUnbind(unbindable) {
|
|
1723
|
-
if (unbindables === null) {
|
|
1724
|
-
unbindables = [];
|
|
1725
|
-
}
|
|
1726
|
-
unbindables.push(unbindable);
|
|
1727
|
-
},
|
|
1728
|
-
connectedCallback(controller) {
|
|
1729
|
-
if (!isConnected) {
|
|
1730
|
-
isConnected = true;
|
|
1731
|
-
behaviors.forEach(x => x.bind(this));
|
|
1732
|
-
}
|
|
1733
|
-
},
|
|
1734
|
-
disconnectedCallback(controller) {
|
|
1735
|
-
if (isConnected) {
|
|
1736
|
-
isConnected = false;
|
|
1737
|
-
if (unbindables !== null) {
|
|
1738
|
-
unbindables.forEach(x => x.unbind(this));
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
},
|
|
1742
|
-
};
|
|
1743
|
-
},
|
|
1744
|
-
});
|
|
1745
2319
|
const registry = createTypeRegistry();
|
|
1746
2320
|
/**
|
|
1747
2321
|
* Instructs the template engine to apply behavior to a node.
|
|
@@ -1769,67 +2343,6 @@ const HTMLDirective = Object.freeze({
|
|
|
1769
2343
|
registry.register(options);
|
|
1770
2344
|
return type;
|
|
1771
2345
|
},
|
|
1772
|
-
});
|
|
1773
|
-
/**
|
|
1774
|
-
* Decorator: Defines an HTMLDirective.
|
|
1775
|
-
* @param options - Provides options that specify the directive's application.
|
|
1776
|
-
* @public
|
|
1777
|
-
*/
|
|
1778
|
-
function htmlDirective(options) {
|
|
1779
|
-
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
1780
|
-
return function (type) {
|
|
1781
|
-
HTMLDirective.define(type, options);
|
|
1782
|
-
};
|
|
1783
|
-
}
|
|
1784
|
-
/**
|
|
1785
|
-
* Captures a binding expression along with related information and capabilities.
|
|
1786
|
-
*
|
|
1787
|
-
* @public
|
|
1788
|
-
*/
|
|
1789
|
-
class Binding {
|
|
1790
|
-
/**
|
|
1791
|
-
* Creates a binding.
|
|
1792
|
-
* @param evaluate - Evaluates the binding.
|
|
1793
|
-
* @param isVolatile - Indicates whether the binding is volatile.
|
|
1794
|
-
*/
|
|
1795
|
-
constructor(evaluate, isVolatile = false) {
|
|
1796
|
-
this.evaluate = evaluate;
|
|
1797
|
-
this.isVolatile = isVolatile;
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
/**
|
|
1801
|
-
* The type of HTML aspect to target.
|
|
1802
|
-
* @public
|
|
1803
|
-
*/
|
|
1804
|
-
const Aspect = Object.freeze({
|
|
1805
|
-
/**
|
|
1806
|
-
* Not aspected.
|
|
1807
|
-
*/
|
|
1808
|
-
none: 0,
|
|
1809
|
-
/**
|
|
1810
|
-
* An attribute.
|
|
1811
|
-
*/
|
|
1812
|
-
attribute: 1,
|
|
1813
|
-
/**
|
|
1814
|
-
* A boolean attribute.
|
|
1815
|
-
*/
|
|
1816
|
-
booleanAttribute: 2,
|
|
1817
|
-
/**
|
|
1818
|
-
* A property.
|
|
1819
|
-
*/
|
|
1820
|
-
property: 3,
|
|
1821
|
-
/**
|
|
1822
|
-
* Content
|
|
1823
|
-
*/
|
|
1824
|
-
content: 4,
|
|
1825
|
-
/**
|
|
1826
|
-
* A token list.
|
|
1827
|
-
*/
|
|
1828
|
-
tokenList: 5,
|
|
1829
|
-
/**
|
|
1830
|
-
* An event.
|
|
1831
|
-
*/
|
|
1832
|
-
event: 6,
|
|
1833
2346
|
/**
|
|
1834
2347
|
*
|
|
1835
2348
|
* @param directive - The directive to assign the aspect to.
|
|
@@ -1837,48 +2350,46 @@ const Aspect = Object.freeze({
|
|
|
1837
2350
|
* @remarks
|
|
1838
2351
|
* If a falsy value is provided, then the content aspect will be assigned.
|
|
1839
2352
|
*/
|
|
1840
|
-
|
|
2353
|
+
assignAspect(directive, value) {
|
|
1841
2354
|
if (!value) {
|
|
1842
|
-
directive.aspectType =
|
|
2355
|
+
directive.aspectType = DOMAspect.content;
|
|
1843
2356
|
return;
|
|
1844
2357
|
}
|
|
1845
2358
|
directive.sourceAspect = value;
|
|
1846
2359
|
switch (value[0]) {
|
|
1847
2360
|
case ":":
|
|
1848
2361
|
directive.targetAspect = value.substring(1);
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
case "classList":
|
|
1854
|
-
directive.aspectType = Aspect.tokenList;
|
|
1855
|
-
break;
|
|
1856
|
-
default:
|
|
1857
|
-
directive.aspectType = Aspect.property;
|
|
1858
|
-
break;
|
|
1859
|
-
}
|
|
2362
|
+
directive.aspectType =
|
|
2363
|
+
directive.targetAspect === "classList"
|
|
2364
|
+
? DOMAspect.tokenList
|
|
2365
|
+
: DOMAspect.property;
|
|
1860
2366
|
break;
|
|
1861
2367
|
case "?":
|
|
1862
2368
|
directive.targetAspect = value.substring(1);
|
|
1863
|
-
directive.aspectType =
|
|
2369
|
+
directive.aspectType = DOMAspect.booleanAttribute;
|
|
1864
2370
|
break;
|
|
1865
2371
|
case "@":
|
|
1866
2372
|
directive.targetAspect = value.substring(1);
|
|
1867
|
-
directive.aspectType =
|
|
2373
|
+
directive.aspectType = DOMAspect.event;
|
|
1868
2374
|
break;
|
|
1869
2375
|
default:
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
directive.aspectType = Aspect.property;
|
|
1873
|
-
}
|
|
1874
|
-
else {
|
|
1875
|
-
directive.targetAspect = value;
|
|
1876
|
-
directive.aspectType = Aspect.attribute;
|
|
1877
|
-
}
|
|
2376
|
+
directive.targetAspect = value;
|
|
2377
|
+
directive.aspectType = DOMAspect.attribute;
|
|
1878
2378
|
break;
|
|
1879
2379
|
}
|
|
1880
2380
|
},
|
|
1881
2381
|
});
|
|
2382
|
+
/**
|
|
2383
|
+
* Decorator: Defines an HTMLDirective.
|
|
2384
|
+
* @param options - Provides options that specify the directive's application.
|
|
2385
|
+
* @public
|
|
2386
|
+
*/
|
|
2387
|
+
function htmlDirective(options) {
|
|
2388
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
2389
|
+
return function (type) {
|
|
2390
|
+
HTMLDirective.define(type, options);
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
1882
2393
|
/**
|
|
1883
2394
|
* A base class used for attribute directives that don't need internal state.
|
|
1884
2395
|
* @public
|
|
@@ -1890,10 +2401,6 @@ class StatelessAttachedAttributeDirective {
|
|
|
1890
2401
|
*/
|
|
1891
2402
|
constructor(options) {
|
|
1892
2403
|
this.options = options;
|
|
1893
|
-
/**
|
|
1894
|
-
* The unique id of the factory.
|
|
1895
|
-
*/
|
|
1896
|
-
this.id = nextId();
|
|
1897
2404
|
}
|
|
1898
2405
|
/**
|
|
1899
2406
|
* Creates a placeholder string based on the directive's index within the template.
|
|
@@ -1912,327 +2419,200 @@ class StatelessAttachedAttributeDirective {
|
|
|
1912
2419
|
return this;
|
|
1913
2420
|
}
|
|
1914
2421
|
}
|
|
2422
|
+
makeSerializationNoop(StatelessAttachedAttributeDirective);
|
|
1915
2423
|
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2424
|
+
class HydrationTargetElementError extends Error {
|
|
2425
|
+
constructor(
|
|
2426
|
+
/**
|
|
2427
|
+
* The error message
|
|
2428
|
+
*/
|
|
2429
|
+
message,
|
|
2430
|
+
/**
|
|
2431
|
+
* The Compiled View Behavior Factories that belong to the view.
|
|
2432
|
+
*/
|
|
2433
|
+
factories,
|
|
2434
|
+
/**
|
|
2435
|
+
* The node to target factory.
|
|
2436
|
+
*/
|
|
2437
|
+
node) {
|
|
2438
|
+
super(message);
|
|
2439
|
+
this.factories = factories;
|
|
2440
|
+
this.node = node;
|
|
1928
2441
|
}
|
|
1929
2442
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
return this;
|
|
1933
|
-
}
|
|
1934
|
-
bind(controller) {
|
|
1935
|
-
return this.evaluate(controller.source, controller.context);
|
|
1936
|
-
}
|
|
2443
|
+
function isComment(node) {
|
|
2444
|
+
return node.nodeType === Node.COMMENT_NODE;
|
|
1937
2445
|
}
|
|
1938
|
-
function
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
if (view !== void 0 && view.isComposed) {
|
|
1985
|
-
view.isComposed = false;
|
|
1986
|
-
view.remove();
|
|
1987
|
-
if (view.needsBindOnly) {
|
|
1988
|
-
view.needsBindOnly = false;
|
|
2446
|
+
function isText(node) {
|
|
2447
|
+
return node.nodeType === Node.TEXT_NODE;
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Returns a range object inclusive of all nodes including and between the
|
|
2451
|
+
* provided first and last node.
|
|
2452
|
+
* @param first - The first node
|
|
2453
|
+
* @param last - This last node
|
|
2454
|
+
* @returns
|
|
2455
|
+
*/
|
|
2456
|
+
function createRangeForNodes(first, last) {
|
|
2457
|
+
const range = document.createRange();
|
|
2458
|
+
range.setStart(first, 0);
|
|
2459
|
+
// The lastIndex should be inclusive of the end of the lastChild. Obtain offset based
|
|
2460
|
+
// on usageNotes: https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd#usage_notes
|
|
2461
|
+
range.setEnd(last, isComment(last) || isText(last) ? last.data.length : last.childNodes.length);
|
|
2462
|
+
return range;
|
|
2463
|
+
}
|
|
2464
|
+
function isShadowRoot(node) {
|
|
2465
|
+
return node instanceof DocumentFragment && "mode" in node;
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
|
|
2469
|
+
* @param firstNode - The first node of the view.
|
|
2470
|
+
* @param lastNode - The last node of the view.
|
|
2471
|
+
* @param factories - The Compiled View Behavior Factories that belong to the view.
|
|
2472
|
+
* @returns - A {@link ViewBehaviorTargets } object for the factories in the view.
|
|
2473
|
+
*/
|
|
2474
|
+
function buildViewBindingTargets(firstNode, lastNode, factories) {
|
|
2475
|
+
const range = createRangeForNodes(firstNode, lastNode);
|
|
2476
|
+
const treeRoot = range.commonAncestorContainer;
|
|
2477
|
+
const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
|
|
2478
|
+
acceptNode(node) {
|
|
2479
|
+
return range.comparePoint(node, 0) === 0
|
|
2480
|
+
? NodeFilter.FILTER_ACCEPT
|
|
2481
|
+
: NodeFilter.FILTER_REJECT;
|
|
2482
|
+
},
|
|
2483
|
+
});
|
|
2484
|
+
const targets = {};
|
|
2485
|
+
const boundaries = {};
|
|
2486
|
+
let node = (walker.currentNode = firstNode);
|
|
2487
|
+
while (node !== null) {
|
|
2488
|
+
switch (node.nodeType) {
|
|
2489
|
+
case Node.ELEMENT_NODE: {
|
|
2490
|
+
targetElement(node, factories, targets);
|
|
2491
|
+
break;
|
|
1989
2492
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
2493
|
+
case Node.COMMENT_NODE: {
|
|
2494
|
+
targetComment(node, walker, factories, targets, boundaries);
|
|
2495
|
+
break;
|
|
1992
2496
|
}
|
|
1993
2497
|
}
|
|
1994
|
-
|
|
2498
|
+
node = walker.nextNode();
|
|
1995
2499
|
}
|
|
2500
|
+
range.detach();
|
|
2501
|
+
return { targets, boundaries };
|
|
1996
2502
|
}
|
|
1997
|
-
function
|
|
1998
|
-
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
// Add the classes, tracking the version at which they were added.
|
|
2005
|
-
if (value !== null && value !== undefined && value.length) {
|
|
2006
|
-
const names = value.split(/\s+/);
|
|
2007
|
-
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
2008
|
-
const currentName = names[i];
|
|
2009
|
-
if (currentName === "") {
|
|
2010
|
-
continue;
|
|
2503
|
+
function targetElement(node, factories, targets) {
|
|
2504
|
+
// Check for attributes and map any factories.
|
|
2505
|
+
const attrFactoryIds = HydrationMarkup.parseAttributeBinding(node);
|
|
2506
|
+
if (attrFactoryIds !== null) {
|
|
2507
|
+
for (const id of attrFactoryIds) {
|
|
2508
|
+
if (!factories[id]) {
|
|
2509
|
+
throw new HydrationTargetElementError(`HydrationView was unable to successfully target factory on ${node.nodeName} inside ${node.getRootNode().host.nodeName}. This likely indicates a template mismatch between SSR rendering and hydration.`, factories, node);
|
|
2011
2510
|
}
|
|
2012
|
-
|
|
2013
|
-
tokenList.add(currentName);
|
|
2511
|
+
targetFactory(factories[id], node, targets);
|
|
2014
2512
|
}
|
|
2513
|
+
node.removeAttribute(HydrationMarkup.attributeMarkerName);
|
|
2015
2514
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
if (
|
|
2515
|
+
}
|
|
2516
|
+
function targetComment(node, walker, factories, targets, boundaries) {
|
|
2517
|
+
if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
|
|
2518
|
+
skipToElementBoundaryEndMarker(node, walker);
|
|
2019
2519
|
return;
|
|
2020
2520
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
tokenList.remove(name);
|
|
2521
|
+
if (HydrationMarkup.isContentBindingStartMarker(node.data)) {
|
|
2522
|
+
const parsed = HydrationMarkup.parseContentBindingStartMarker(node.data);
|
|
2523
|
+
if (parsed === null) {
|
|
2524
|
+
return;
|
|
2026
2525
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
const
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
* @param dataBinding - The binding configuration to apply.
|
|
2039
|
-
*/
|
|
2040
|
-
constructor(dataBinding) {
|
|
2041
|
-
this.dataBinding = dataBinding;
|
|
2042
|
-
this.updateTarget = null;
|
|
2043
|
-
/**
|
|
2044
|
-
* The unique id of the factory.
|
|
2045
|
-
*/
|
|
2046
|
-
this.id = nextId();
|
|
2047
|
-
/**
|
|
2048
|
-
* The type of aspect to target.
|
|
2049
|
-
*/
|
|
2050
|
-
this.aspectType = Aspect.content;
|
|
2051
|
-
/** @internal */
|
|
2052
|
-
this.bind = this.bindDefault;
|
|
2053
|
-
this.data = `${this.id}-d`;
|
|
2054
|
-
}
|
|
2055
|
-
/**
|
|
2056
|
-
* Creates HTML to be used within a template.
|
|
2057
|
-
* @param add - Can be used to add behavior factories to a template.
|
|
2058
|
-
*/
|
|
2059
|
-
createHTML(add) {
|
|
2060
|
-
return Markup.interpolation(add(this));
|
|
2061
|
-
}
|
|
2062
|
-
/**
|
|
2063
|
-
* Creates a behavior.
|
|
2064
|
-
*/
|
|
2065
|
-
createBehavior() {
|
|
2066
|
-
if (this.updateTarget === null) {
|
|
2067
|
-
if (this.targetAspect === "innerHTML") {
|
|
2068
|
-
this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
|
|
2069
|
-
}
|
|
2070
|
-
switch (this.aspectType) {
|
|
2071
|
-
case 1:
|
|
2072
|
-
this.updateTarget = DOM.setAttribute;
|
|
2073
|
-
break;
|
|
2074
|
-
case 2:
|
|
2075
|
-
this.updateTarget = DOM.setBooleanAttribute;
|
|
2076
|
-
break;
|
|
2077
|
-
case 3:
|
|
2078
|
-
this.updateTarget = setProperty;
|
|
2079
|
-
break;
|
|
2080
|
-
case 4:
|
|
2081
|
-
this.bind = this.bindContent;
|
|
2082
|
-
this.updateTarget = updateContent;
|
|
2083
|
-
break;
|
|
2084
|
-
case 5:
|
|
2085
|
-
this.updateTarget = updateTokenList;
|
|
2526
|
+
const [index, id] = parsed;
|
|
2527
|
+
const factory = factories[index];
|
|
2528
|
+
const nodes = [];
|
|
2529
|
+
let current = walker.nextSibling();
|
|
2530
|
+
node.data = "";
|
|
2531
|
+
const first = current;
|
|
2532
|
+
// Search for the binding end marker that closes the binding.
|
|
2533
|
+
while (current !== null) {
|
|
2534
|
+
if (isComment(current)) {
|
|
2535
|
+
const parsed = HydrationMarkup.parseContentBindingEndMarker(current.data);
|
|
2536
|
+
if (parsed && parsed[1] === id) {
|
|
2086
2537
|
break;
|
|
2087
|
-
|
|
2088
|
-
this.bind = this.bindEvent;
|
|
2089
|
-
this.updateTarget = eventTarget;
|
|
2090
|
-
break;
|
|
2091
|
-
default:
|
|
2092
|
-
throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
|
|
2538
|
+
}
|
|
2093
2539
|
}
|
|
2540
|
+
nodes.push(current);
|
|
2541
|
+
current = walker.nextSibling();
|
|
2094
2542
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
bindDefault(controller) {
|
|
2099
|
-
var _a;
|
|
2100
|
-
const target = controller.targets[this.nodeId];
|
|
2101
|
-
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
2102
|
-
observer.target = target;
|
|
2103
|
-
observer.controller = controller;
|
|
2104
|
-
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
2105
|
-
if (this.updateTarget === updateContent) {
|
|
2106
|
-
controller.onUnbind(this);
|
|
2543
|
+
if (current === null) {
|
|
2544
|
+
const root = node.getRootNode();
|
|
2545
|
+
throw new Error(`Error hydrating Comment node inside "${isShadowRoot(root) ? root.host.nodeName : root.nodeName}".`);
|
|
2107
2546
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
this.bindDefault(controller);
|
|
2112
|
-
controller.onUnbind(this);
|
|
2113
|
-
}
|
|
2114
|
-
/** @internal */
|
|
2115
|
-
bindEvent(controller) {
|
|
2116
|
-
const target = controller.targets[this.nodeId];
|
|
2117
|
-
target[this.data] = controller;
|
|
2118
|
-
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
2119
|
-
}
|
|
2120
|
-
/** @internal */
|
|
2121
|
-
unbind(controller) {
|
|
2122
|
-
const target = controller.targets[this.nodeId];
|
|
2123
|
-
const view = target.$fastView;
|
|
2124
|
-
if (view !== void 0 && view.isComposed) {
|
|
2125
|
-
view.unbind();
|
|
2126
|
-
view.needsBindOnly = true;
|
|
2547
|
+
current.data = "";
|
|
2548
|
+
if (nodes.length === 1 && isText(nodes[0])) {
|
|
2549
|
+
targetFactory(factory, nodes[0], targets);
|
|
2127
2550
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2551
|
+
else {
|
|
2552
|
+
// If current === first, it means there is no content in
|
|
2553
|
+
// the view. This happens when a `when` directive evaluates false,
|
|
2554
|
+
// or whenever a content binding returns null or undefined.
|
|
2555
|
+
// In that case, there will never be any content
|
|
2556
|
+
// to hydrate and Binding can simply create a HTMLView
|
|
2557
|
+
// whenever it needs to.
|
|
2558
|
+
if (current !== first && current.previousSibling !== null) {
|
|
2559
|
+
boundaries[factory.targetNodeId] = {
|
|
2560
|
+
first,
|
|
2561
|
+
last: current.previousSibling,
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
// Binding evaluates to null / undefined or a template.
|
|
2565
|
+
// If binding revaluates to string, it will replace content in target
|
|
2566
|
+
// So we always insert a text node to ensure that
|
|
2567
|
+
// text content binding will be written to this text node instead of comment
|
|
2568
|
+
const dummyTextNode = current.parentNode.insertBefore(document.createTextNode(""), current);
|
|
2569
|
+
targetFactory(factory, dummyTextNode, targets);
|
|
2138
2570
|
}
|
|
2139
2571
|
}
|
|
2140
|
-
/** @internal */
|
|
2141
|
-
handleChange(binding, observer) {
|
|
2142
|
-
const target = observer.target;
|
|
2143
|
-
const controller = observer.controller;
|
|
2144
|
-
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
2145
|
-
}
|
|
2146
2572
|
}
|
|
2147
|
-
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
2148
2573
|
/**
|
|
2149
|
-
*
|
|
2150
|
-
* @param
|
|
2151
|
-
* @param
|
|
2152
|
-
* @returns A binding configuration.
|
|
2153
|
-
* @public
|
|
2154
|
-
*/
|
|
2155
|
-
function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
|
|
2156
|
-
return new OnChangeBinding(binding, isVolatile);
|
|
2157
|
-
}
|
|
2158
|
-
/**
|
|
2159
|
-
* Creates a one time binding
|
|
2160
|
-
* @param binding - The binding to refresh when signaled.
|
|
2161
|
-
* @returns A binding configuration.
|
|
2162
|
-
* @public
|
|
2163
|
-
*/
|
|
2164
|
-
function oneTime(binding) {
|
|
2165
|
-
return new OneTimeBinding(binding);
|
|
2166
|
-
}
|
|
2167
|
-
/**
|
|
2168
|
-
* Creates an event listener binding.
|
|
2169
|
-
* @param binding - The binding to invoke when the event is raised.
|
|
2170
|
-
* @param options - Event listener options.
|
|
2171
|
-
* @returns A binding configuration.
|
|
2172
|
-
* @public
|
|
2574
|
+
* Moves TreeWalker to element boundary end marker
|
|
2575
|
+
* @param node - element boundary start marker node
|
|
2576
|
+
* @param walker - tree walker
|
|
2173
2577
|
*/
|
|
2174
|
-
function
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2578
|
+
function skipToElementBoundaryEndMarker(node, walker) {
|
|
2579
|
+
const id = HydrationMarkup.parseElementBoundaryStartMarker(node.data);
|
|
2580
|
+
let current = walker.nextSibling();
|
|
2581
|
+
while (current !== null) {
|
|
2582
|
+
if (isComment(current)) {
|
|
2583
|
+
const parsed = HydrationMarkup.parseElementBoundaryEndMarker(current.data);
|
|
2584
|
+
if (parsed && parsed === id) {
|
|
2585
|
+
break;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
current = walker.nextSibling();
|
|
2589
|
+
}
|
|
2178
2590
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
function normalizeBinding(value) {
|
|
2186
|
-
return isFunction(value)
|
|
2187
|
-
? bind(value)
|
|
2188
|
-
: value instanceof Binding
|
|
2189
|
-
? value
|
|
2190
|
-
: oneTime(() => value);
|
|
2591
|
+
function targetFactory(factory, node, targets) {
|
|
2592
|
+
if (factory.targetNodeId === undefined) {
|
|
2593
|
+
// Dev error, this shouldn't ever be thrown
|
|
2594
|
+
throw new Error("Factory could not be target to the node");
|
|
2595
|
+
}
|
|
2596
|
+
targets[factory.targetNodeId] = node;
|
|
2191
2597
|
}
|
|
2192
2598
|
|
|
2599
|
+
var _a;
|
|
2193
2600
|
function removeNodeSequence(firstNode, lastNode) {
|
|
2194
2601
|
const parent = firstNode.parentNode;
|
|
2195
2602
|
let current = firstNode;
|
|
2196
2603
|
let next;
|
|
2197
2604
|
while (current !== lastNode) {
|
|
2198
2605
|
next = current.nextSibling;
|
|
2606
|
+
if (!next) {
|
|
2607
|
+
throw new Error(`Unmatched first/last child inside "${lastNode.getRootNode().host.nodeName}".`);
|
|
2608
|
+
}
|
|
2199
2609
|
parent.removeChild(current);
|
|
2200
2610
|
current = next;
|
|
2201
2611
|
}
|
|
2202
2612
|
parent.removeChild(lastNode);
|
|
2203
2613
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
* @public
|
|
2207
|
-
*/
|
|
2208
|
-
class HTMLView {
|
|
2209
|
-
/**
|
|
2210
|
-
* Constructs an instance of HTMLView.
|
|
2211
|
-
* @param fragment - The html fragment that contains the nodes for this view.
|
|
2212
|
-
* @param behaviors - The behaviors to be applied to this view.
|
|
2213
|
-
*/
|
|
2214
|
-
constructor(fragment, factories, targets) {
|
|
2215
|
-
this.fragment = fragment;
|
|
2216
|
-
this.factories = factories;
|
|
2217
|
-
this.targets = targets;
|
|
2218
|
-
this.behaviors = null;
|
|
2219
|
-
this.unbindables = [];
|
|
2220
|
-
/**
|
|
2221
|
-
* The data that the view is bound to.
|
|
2222
|
-
*/
|
|
2223
|
-
this.source = null;
|
|
2224
|
-
/**
|
|
2225
|
-
* Indicates whether the controller is bound.
|
|
2226
|
-
*/
|
|
2227
|
-
this.isBound = false;
|
|
2228
|
-
/**
|
|
2229
|
-
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
2230
|
-
*/
|
|
2231
|
-
this.sourceLifetime = SourceLifetime.unknown;
|
|
2232
|
-
/**
|
|
2233
|
-
* The execution context the view is running within.
|
|
2234
|
-
*/
|
|
2235
|
-
this.context = this;
|
|
2614
|
+
class DefaultExecutionContext {
|
|
2615
|
+
constructor() {
|
|
2236
2616
|
/**
|
|
2237
2617
|
* The index of the current item within a repeat context.
|
|
2238
2618
|
*/
|
|
@@ -2241,8 +2621,6 @@ class HTMLView {
|
|
|
2241
2621
|
* The length of the current collection within a repeat context.
|
|
2242
2622
|
*/
|
|
2243
2623
|
this.length = 0;
|
|
2244
|
-
this.firstChild = fragment.firstChild;
|
|
2245
|
-
this.lastChild = fragment.lastChild;
|
|
2246
2624
|
}
|
|
2247
2625
|
/**
|
|
2248
2626
|
* The current event within an event handler.
|
|
@@ -2297,6 +2675,43 @@ class HTMLView {
|
|
|
2297
2675
|
eventTarget() {
|
|
2298
2676
|
return this.event.target;
|
|
2299
2677
|
}
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* The standard View implementation, which also implements ElementView and SyntheticView.
|
|
2681
|
+
* @public
|
|
2682
|
+
*/
|
|
2683
|
+
class HTMLView extends DefaultExecutionContext {
|
|
2684
|
+
/**
|
|
2685
|
+
* Constructs an instance of HTMLView.
|
|
2686
|
+
* @param fragment - The html fragment that contains the nodes for this view.
|
|
2687
|
+
* @param behaviors - The behaviors to be applied to this view.
|
|
2688
|
+
*/
|
|
2689
|
+
constructor(fragment, factories, targets) {
|
|
2690
|
+
super();
|
|
2691
|
+
this.fragment = fragment;
|
|
2692
|
+
this.factories = factories;
|
|
2693
|
+
this.targets = targets;
|
|
2694
|
+
this.behaviors = null;
|
|
2695
|
+
this.unbindables = [];
|
|
2696
|
+
/**
|
|
2697
|
+
* The data that the view is bound to.
|
|
2698
|
+
*/
|
|
2699
|
+
this.source = null;
|
|
2700
|
+
/**
|
|
2701
|
+
* Indicates whether the controller is bound.
|
|
2702
|
+
*/
|
|
2703
|
+
this.isBound = false;
|
|
2704
|
+
/**
|
|
2705
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
2706
|
+
*/
|
|
2707
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
2708
|
+
/**
|
|
2709
|
+
* The execution context the view is running within.
|
|
2710
|
+
*/
|
|
2711
|
+
this.context = this;
|
|
2712
|
+
this.firstChild = fragment.firstChild;
|
|
2713
|
+
this.lastChild = fragment.lastChild;
|
|
2714
|
+
}
|
|
2300
2715
|
/**
|
|
2301
2716
|
* Appends the view's DOM nodes to the referenced node.
|
|
2302
2717
|
* @param node - The parent node to append the view's DOM nodes to.
|
|
@@ -2388,9 +2803,219 @@ class HTMLView {
|
|
|
2388
2803
|
}
|
|
2389
2804
|
this.isBound = true;
|
|
2390
2805
|
}
|
|
2391
|
-
/**
|
|
2392
|
-
* Unbinds a view's behaviors from its binding source.
|
|
2393
|
-
*/
|
|
2806
|
+
/**
|
|
2807
|
+
* Unbinds a view's behaviors from its binding source.
|
|
2808
|
+
*/
|
|
2809
|
+
unbind() {
|
|
2810
|
+
if (!this.isBound || this.source === null) {
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
this.evaluateUnbindables();
|
|
2814
|
+
this.source = null;
|
|
2815
|
+
this.context = this;
|
|
2816
|
+
this.isBound = false;
|
|
2817
|
+
}
|
|
2818
|
+
evaluateUnbindables() {
|
|
2819
|
+
const unbindables = this.unbindables;
|
|
2820
|
+
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
2821
|
+
unbindables[i].unbind(this);
|
|
2822
|
+
}
|
|
2823
|
+
unbindables.length = 0;
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Efficiently disposes of a contiguous range of synthetic view instances.
|
|
2827
|
+
* @param views - A contiguous range of views to be disposed.
|
|
2828
|
+
*/
|
|
2829
|
+
static disposeContiguousBatch(views) {
|
|
2830
|
+
if (views.length === 0) {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
|
|
2834
|
+
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
2835
|
+
views[i].unbind();
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
makeSerializationNoop(HTMLView);
|
|
2840
|
+
Observable.defineProperty(HTMLView.prototype, "index");
|
|
2841
|
+
Observable.defineProperty(HTMLView.prototype, "length");
|
|
2842
|
+
const HydrationStage = {
|
|
2843
|
+
unhydrated: "unhydrated",
|
|
2844
|
+
hydrating: "hydrating",
|
|
2845
|
+
hydrated: "hydrated",
|
|
2846
|
+
};
|
|
2847
|
+
/** @public */
|
|
2848
|
+
class HydrationBindingError extends Error {
|
|
2849
|
+
constructor(
|
|
2850
|
+
/**
|
|
2851
|
+
* The error message
|
|
2852
|
+
*/
|
|
2853
|
+
message,
|
|
2854
|
+
/**
|
|
2855
|
+
* The factory that was unable to be bound
|
|
2856
|
+
*/
|
|
2857
|
+
factory,
|
|
2858
|
+
/**
|
|
2859
|
+
* A DocumentFragment containing a clone of the
|
|
2860
|
+
* view's Nodes.
|
|
2861
|
+
*/
|
|
2862
|
+
fragment,
|
|
2863
|
+
/**
|
|
2864
|
+
* String representation of the HTML in the template that
|
|
2865
|
+
* threw the binding error.
|
|
2866
|
+
*/
|
|
2867
|
+
templateString) {
|
|
2868
|
+
super(message);
|
|
2869
|
+
this.factory = factory;
|
|
2870
|
+
this.fragment = fragment;
|
|
2871
|
+
this.templateString = templateString;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
class HydrationView extends DefaultExecutionContext {
|
|
2875
|
+
constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
|
|
2876
|
+
super();
|
|
2877
|
+
this.firstChild = firstChild;
|
|
2878
|
+
this.lastChild = lastChild;
|
|
2879
|
+
this.sourceTemplate = sourceTemplate;
|
|
2880
|
+
this.hostBindingTarget = hostBindingTarget;
|
|
2881
|
+
this[_a] = Hydratable;
|
|
2882
|
+
this.context = this;
|
|
2883
|
+
this.source = null;
|
|
2884
|
+
this.isBound = false;
|
|
2885
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
2886
|
+
this.unbindables = [];
|
|
2887
|
+
this.fragment = null;
|
|
2888
|
+
this.behaviors = null;
|
|
2889
|
+
this._hydrationStage = HydrationStage.unhydrated;
|
|
2890
|
+
this._bindingViewBoundaries = {};
|
|
2891
|
+
this._targets = {};
|
|
2892
|
+
this.factories = sourceTemplate.compile().factories;
|
|
2893
|
+
}
|
|
2894
|
+
get hydrationStage() {
|
|
2895
|
+
return this._hydrationStage;
|
|
2896
|
+
}
|
|
2897
|
+
get targets() {
|
|
2898
|
+
return this._targets;
|
|
2899
|
+
}
|
|
2900
|
+
get bindingViewBoundaries() {
|
|
2901
|
+
return this._bindingViewBoundaries;
|
|
2902
|
+
}
|
|
2903
|
+
/**
|
|
2904
|
+
* no-op. Hydrated views are don't need to be moved from a documentFragment
|
|
2905
|
+
* to the target node.
|
|
2906
|
+
*/
|
|
2907
|
+
insertBefore(node) {
|
|
2908
|
+
// No-op in cases where this is called before the view is removed,
|
|
2909
|
+
// because the nodes will already be in the document and just need hydrating.
|
|
2910
|
+
if (this.fragment === null) {
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
if (this.fragment.hasChildNodes()) {
|
|
2914
|
+
node.parentNode.insertBefore(this.fragment, node);
|
|
2915
|
+
}
|
|
2916
|
+
else {
|
|
2917
|
+
const end = this.lastChild;
|
|
2918
|
+
if (node.previousSibling === end)
|
|
2919
|
+
return;
|
|
2920
|
+
const parentNode = node.parentNode;
|
|
2921
|
+
let current = this.firstChild;
|
|
2922
|
+
let next;
|
|
2923
|
+
while (current !== end) {
|
|
2924
|
+
next = current.nextSibling;
|
|
2925
|
+
parentNode.insertBefore(current, node);
|
|
2926
|
+
current = next;
|
|
2927
|
+
}
|
|
2928
|
+
parentNode.insertBefore(end, node);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
/**
|
|
2932
|
+
* Appends the view to a node. In cases where this is called before the
|
|
2933
|
+
* view has been removed, the method will no-op.
|
|
2934
|
+
* @param node - the node to append the view to.
|
|
2935
|
+
*/
|
|
2936
|
+
appendTo(node) {
|
|
2937
|
+
if (this.fragment !== null) {
|
|
2938
|
+
node.appendChild(this.fragment);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
remove() {
|
|
2942
|
+
const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
|
|
2943
|
+
const end = this.lastChild;
|
|
2944
|
+
let current = this.firstChild;
|
|
2945
|
+
let next;
|
|
2946
|
+
while (current !== end) {
|
|
2947
|
+
next = current.nextSibling;
|
|
2948
|
+
if (!next) {
|
|
2949
|
+
throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
|
|
2950
|
+
}
|
|
2951
|
+
fragment.appendChild(current);
|
|
2952
|
+
current = next;
|
|
2953
|
+
}
|
|
2954
|
+
fragment.appendChild(end);
|
|
2955
|
+
}
|
|
2956
|
+
bind(source, context = this) {
|
|
2957
|
+
var _b, _c;
|
|
2958
|
+
if (this.hydrationStage !== HydrationStage.hydrated) {
|
|
2959
|
+
this._hydrationStage = HydrationStage.hydrating;
|
|
2960
|
+
}
|
|
2961
|
+
if (this.source === source) {
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
let behaviors = this.behaviors;
|
|
2965
|
+
if (behaviors === null) {
|
|
2966
|
+
this.source = source;
|
|
2967
|
+
this.context = context;
|
|
2968
|
+
try {
|
|
2969
|
+
const { targets, boundaries } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
|
|
2970
|
+
this._targets = targets;
|
|
2971
|
+
this._bindingViewBoundaries = boundaries;
|
|
2972
|
+
}
|
|
2973
|
+
catch (error) {
|
|
2974
|
+
if (error instanceof HydrationTargetElementError) {
|
|
2975
|
+
let templateString = this.sourceTemplate.html;
|
|
2976
|
+
if (typeof templateString !== "string") {
|
|
2977
|
+
templateString = templateString.innerHTML;
|
|
2978
|
+
}
|
|
2979
|
+
error.templateString = templateString;
|
|
2980
|
+
}
|
|
2981
|
+
throw error;
|
|
2982
|
+
}
|
|
2983
|
+
this.behaviors = behaviors = new Array(this.factories.length);
|
|
2984
|
+
const factories = this.factories;
|
|
2985
|
+
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
2986
|
+
const factory = factories[i];
|
|
2987
|
+
if (factory.targetNodeId === "h" && this.hostBindingTarget) {
|
|
2988
|
+
targetFactory(factory, this.hostBindingTarget, this._targets);
|
|
2989
|
+
}
|
|
2990
|
+
// If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
|
|
2991
|
+
if (factory.targetNodeId in this.targets) {
|
|
2992
|
+
const behavior = factory.createBehavior();
|
|
2993
|
+
behavior.bind(this);
|
|
2994
|
+
behaviors[i] = behavior;
|
|
2995
|
+
}
|
|
2996
|
+
else {
|
|
2997
|
+
let templateString = this.sourceTemplate.html;
|
|
2998
|
+
if (typeof templateString !== "string") {
|
|
2999
|
+
templateString = templateString.innerHTML;
|
|
3000
|
+
}
|
|
3001
|
+
throw new HydrationBindingError(`HydrationView was unable to successfully target bindings inside "${(_c = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode()).host) === null || _c === void 0 ? void 0 : _c.nodeName}".`, factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
else {
|
|
3006
|
+
if (this.source !== null) {
|
|
3007
|
+
this.evaluateUnbindables();
|
|
3008
|
+
}
|
|
3009
|
+
this.isBound = false;
|
|
3010
|
+
this.source = source;
|
|
3011
|
+
this.context = context;
|
|
3012
|
+
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
3013
|
+
behaviors[i].bind(this);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
this.isBound = true;
|
|
3017
|
+
this._hydrationStage = HydrationStage.hydrated;
|
|
3018
|
+
}
|
|
2394
3019
|
unbind() {
|
|
2395
3020
|
if (!this.isBound || this.source === null) {
|
|
2396
3021
|
return;
|
|
@@ -2400,6 +3025,17 @@ class HTMLView {
|
|
|
2400
3025
|
this.context = this;
|
|
2401
3026
|
this.isBound = false;
|
|
2402
3027
|
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
|
|
3030
|
+
* Once a view has been disposed, it cannot be inserted or bound again.
|
|
3031
|
+
*/
|
|
3032
|
+
dispose() {
|
|
3033
|
+
removeNodeSequence(this.firstChild, this.lastChild);
|
|
3034
|
+
this.unbind();
|
|
3035
|
+
}
|
|
3036
|
+
onUnbind(behavior) {
|
|
3037
|
+
this.unbindables.push(behavior);
|
|
3038
|
+
}
|
|
2403
3039
|
evaluateUnbindables() {
|
|
2404
3040
|
const unbindables = this.unbindables;
|
|
2405
3041
|
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
@@ -2407,22 +3043,220 @@ class HTMLView {
|
|
|
2407
3043
|
}
|
|
2408
3044
|
unbindables.length = 0;
|
|
2409
3045
|
}
|
|
3046
|
+
}
|
|
3047
|
+
_a = Hydratable;
|
|
3048
|
+
makeSerializationNoop(HydrationView);
|
|
3049
|
+
|
|
3050
|
+
function isContentTemplate(value) {
|
|
3051
|
+
return value.create !== undefined;
|
|
3052
|
+
}
|
|
3053
|
+
function updateContent(target, aspect, value, controller) {
|
|
3054
|
+
// If there's no actual value, then this equates to the
|
|
3055
|
+
// empty string for the purposes of content bindings.
|
|
3056
|
+
if (value === null || value === undefined) {
|
|
3057
|
+
value = "";
|
|
3058
|
+
}
|
|
3059
|
+
// If the value has a "create" method, then it's a ContentTemplate.
|
|
3060
|
+
if (isContentTemplate(value)) {
|
|
3061
|
+
target.textContent = "";
|
|
3062
|
+
let view = target.$fastView;
|
|
3063
|
+
// If there's no previous view that we might be able to
|
|
3064
|
+
// reuse then create a new view from the template.
|
|
3065
|
+
if (view === void 0) {
|
|
3066
|
+
if (isHydratable(controller) &&
|
|
3067
|
+
isHydratable(value) &&
|
|
3068
|
+
controller.bindingViewBoundaries[this.targetNodeId] !== undefined &&
|
|
3069
|
+
controller.hydrationStage !== HydrationStage.hydrated) {
|
|
3070
|
+
const viewNodes = controller.bindingViewBoundaries[this.targetNodeId];
|
|
3071
|
+
view = value.hydrate(viewNodes.first, viewNodes.last);
|
|
3072
|
+
}
|
|
3073
|
+
else {
|
|
3074
|
+
view = value.create();
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
else {
|
|
3078
|
+
// If there is a previous view, but it wasn't created
|
|
3079
|
+
// from the same template as the new value, then we
|
|
3080
|
+
// need to remove the old view if it's still in the DOM
|
|
3081
|
+
// and create a new view from the template.
|
|
3082
|
+
if (target.$fastTemplate !== value) {
|
|
3083
|
+
if (view.isComposed) {
|
|
3084
|
+
view.remove();
|
|
3085
|
+
view.unbind();
|
|
3086
|
+
}
|
|
3087
|
+
view = value.create();
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
// It's possible that the value is the same as the previous template
|
|
3091
|
+
// and that there's actually no need to compose it.
|
|
3092
|
+
if (!view.isComposed) {
|
|
3093
|
+
view.isComposed = true;
|
|
3094
|
+
view.bind(controller.source, controller.context);
|
|
3095
|
+
view.insertBefore(target);
|
|
3096
|
+
target.$fastView = view;
|
|
3097
|
+
target.$fastTemplate = value;
|
|
3098
|
+
}
|
|
3099
|
+
else if (view.needsBindOnly) {
|
|
3100
|
+
view.needsBindOnly = false;
|
|
3101
|
+
view.bind(controller.source, controller.context);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
else {
|
|
3105
|
+
const view = target.$fastView;
|
|
3106
|
+
// If there is a view and it's currently composed into
|
|
3107
|
+
// the DOM, then we need to remove it.
|
|
3108
|
+
if (view !== void 0 && view.isComposed) {
|
|
3109
|
+
view.isComposed = false;
|
|
3110
|
+
view.remove();
|
|
3111
|
+
if (view.needsBindOnly) {
|
|
3112
|
+
view.needsBindOnly = false;
|
|
3113
|
+
}
|
|
3114
|
+
else {
|
|
3115
|
+
view.unbind();
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
target.textContent = value;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
function updateTokenList(target, aspect, value) {
|
|
3122
|
+
var _a;
|
|
3123
|
+
const lookup = `${this.id}-t`;
|
|
3124
|
+
const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
|
|
3125
|
+
const classVersions = state.cv;
|
|
3126
|
+
let version = state.v;
|
|
3127
|
+
const tokenList = target[aspect];
|
|
3128
|
+
// Add the classes, tracking the version at which they were added.
|
|
3129
|
+
if (value !== null && value !== undefined && value.length) {
|
|
3130
|
+
const names = value.split(/\s+/);
|
|
3131
|
+
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
3132
|
+
const currentName = names[i];
|
|
3133
|
+
if (currentName === "") {
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
3136
|
+
classVersions[currentName] = version;
|
|
3137
|
+
tokenList.add(currentName);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
state.v = version + 1;
|
|
3141
|
+
// If this is the first call to add classes, there's no need to remove old ones.
|
|
3142
|
+
if (version === 0) {
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
// Remove classes from the previous version.
|
|
3146
|
+
version -= 1;
|
|
3147
|
+
for (const name in classVersions) {
|
|
3148
|
+
if (classVersions[name] === version) {
|
|
3149
|
+
tokenList.remove(name);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
const sinkLookup = {
|
|
3154
|
+
[DOMAspect.attribute]: DOM.setAttribute,
|
|
3155
|
+
[DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
|
|
3156
|
+
[DOMAspect.property]: (t, a, v) => (t[a] = v),
|
|
3157
|
+
[DOMAspect.content]: updateContent,
|
|
3158
|
+
[DOMAspect.tokenList]: updateTokenList,
|
|
3159
|
+
[DOMAspect.event]: () => void 0,
|
|
3160
|
+
};
|
|
3161
|
+
/**
|
|
3162
|
+
* A directive that applies bindings.
|
|
3163
|
+
* @public
|
|
3164
|
+
*/
|
|
3165
|
+
class HTMLBindingDirective {
|
|
2410
3166
|
/**
|
|
2411
|
-
*
|
|
2412
|
-
* @param
|
|
3167
|
+
* Creates an instance of HTMLBindingDirective.
|
|
3168
|
+
* @param dataBinding - The binding configuration to apply.
|
|
2413
3169
|
*/
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
3170
|
+
constructor(dataBinding) {
|
|
3171
|
+
this.dataBinding = dataBinding;
|
|
3172
|
+
this.updateTarget = null;
|
|
3173
|
+
/**
|
|
3174
|
+
* The type of aspect to target.
|
|
3175
|
+
*/
|
|
3176
|
+
this.aspectType = DOMAspect.content;
|
|
3177
|
+
}
|
|
3178
|
+
/**
|
|
3179
|
+
* Creates HTML to be used within a template.
|
|
3180
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
3181
|
+
*/
|
|
3182
|
+
createHTML(add) {
|
|
3183
|
+
return Markup.interpolation(add(this));
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* Creates a behavior.
|
|
3187
|
+
*/
|
|
3188
|
+
createBehavior() {
|
|
3189
|
+
var _a;
|
|
3190
|
+
if (this.updateTarget === null) {
|
|
3191
|
+
const sink = sinkLookup[this.aspectType];
|
|
3192
|
+
const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
|
|
3193
|
+
if (!sink) {
|
|
3194
|
+
throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
|
|
3195
|
+
}
|
|
3196
|
+
this.data = `${this.id}-d`;
|
|
3197
|
+
this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
|
|
2417
3198
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
3199
|
+
return this;
|
|
3200
|
+
}
|
|
3201
|
+
/** @internal */
|
|
3202
|
+
bind(controller) {
|
|
3203
|
+
var _a;
|
|
3204
|
+
const target = controller.targets[this.targetNodeId];
|
|
3205
|
+
const isHydrating = isHydratable(controller) &&
|
|
3206
|
+
controller.hydrationStage &&
|
|
3207
|
+
controller.hydrationStage !== HydrationStage.hydrated;
|
|
3208
|
+
switch (this.aspectType) {
|
|
3209
|
+
case DOMAspect.event:
|
|
3210
|
+
target[this.data] = controller;
|
|
3211
|
+
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
3212
|
+
break;
|
|
3213
|
+
case DOMAspect.content:
|
|
3214
|
+
controller.onUnbind(this);
|
|
3215
|
+
// intentional fall through
|
|
3216
|
+
default:
|
|
3217
|
+
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
3218
|
+
observer.target = target;
|
|
3219
|
+
observer.controller = controller;
|
|
3220
|
+
if (isHydrating &&
|
|
3221
|
+
(this.aspectType === DOMAspect.attribute ||
|
|
3222
|
+
this.aspectType === DOMAspect.booleanAttribute)) {
|
|
3223
|
+
observer.bind(controller);
|
|
3224
|
+
// Skip updating target during bind for attributes
|
|
3225
|
+
break;
|
|
3226
|
+
}
|
|
3227
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
3228
|
+
break;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
/** @internal */
|
|
3232
|
+
unbind(controller) {
|
|
3233
|
+
const target = controller.targets[this.targetNodeId];
|
|
3234
|
+
const view = target.$fastView;
|
|
3235
|
+
if (view !== void 0 && view.isComposed) {
|
|
3236
|
+
view.unbind();
|
|
3237
|
+
view.needsBindOnly = true;
|
|
2421
3238
|
}
|
|
2422
3239
|
}
|
|
3240
|
+
/** @internal */
|
|
3241
|
+
handleEvent(event) {
|
|
3242
|
+
const controller = event.currentTarget[this.data];
|
|
3243
|
+
if (controller.isBound) {
|
|
3244
|
+
ExecutionContext.setEvent(event);
|
|
3245
|
+
const result = this.dataBinding.evaluate(controller.source, controller.context);
|
|
3246
|
+
ExecutionContext.setEvent(null);
|
|
3247
|
+
if (result !== true) {
|
|
3248
|
+
event.preventDefault();
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
/** @internal */
|
|
3253
|
+
handleChange(binding, observer) {
|
|
3254
|
+
const target = observer.target;
|
|
3255
|
+
const controller = observer.controller;
|
|
3256
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
3257
|
+
}
|
|
2423
3258
|
}
|
|
2424
|
-
|
|
2425
|
-
Observable.defineProperty(HTMLView.prototype, "length");
|
|
3259
|
+
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
2426
3260
|
|
|
2427
3261
|
const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
|
|
2428
3262
|
const descriptorCache = {};
|
|
@@ -2448,20 +3282,25 @@ const warningHost = new Proxy(document.createElement("div"), {
|
|
|
2448
3282
|
},
|
|
2449
3283
|
});
|
|
2450
3284
|
class CompilationContext {
|
|
2451
|
-
constructor(fragment, directives) {
|
|
3285
|
+
constructor(fragment, directives, policy) {
|
|
2452
3286
|
this.fragment = fragment;
|
|
2453
3287
|
this.directives = directives;
|
|
3288
|
+
this.policy = policy;
|
|
2454
3289
|
this.proto = null;
|
|
2455
3290
|
this.nodeIds = new Set();
|
|
2456
3291
|
this.descriptors = {};
|
|
2457
3292
|
this.factories = [];
|
|
2458
3293
|
}
|
|
2459
|
-
addFactory(factory, parentId, nodeId, targetIndex) {
|
|
3294
|
+
addFactory(factory, parentId, nodeId, targetIndex, tagName) {
|
|
3295
|
+
var _a, _b;
|
|
2460
3296
|
if (!this.nodeIds.has(nodeId)) {
|
|
2461
3297
|
this.nodeIds.add(nodeId);
|
|
2462
3298
|
this.addTargetDescriptor(parentId, nodeId, targetIndex);
|
|
2463
3299
|
}
|
|
2464
|
-
factory.
|
|
3300
|
+
factory.id = (_a = factory.id) !== null && _a !== void 0 ? _a : nextId();
|
|
3301
|
+
factory.targetNodeId = nodeId;
|
|
3302
|
+
factory.targetTagName = tagName;
|
|
3303
|
+
factory.policy = (_b = factory.policy) !== null && _b !== void 0 ? _b : this.policy;
|
|
2465
3304
|
this.factories.push(factory);
|
|
2466
3305
|
}
|
|
2467
3306
|
freeze() {
|
|
@@ -2514,19 +3353,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
|
|
|
2514
3353
|
let result = null;
|
|
2515
3354
|
if (parseResult === null) {
|
|
2516
3355
|
if (includeBasicValues) {
|
|
2517
|
-
result = new HTMLBindingDirective(oneTime(() => attrValue));
|
|
2518
|
-
|
|
3356
|
+
result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
|
|
3357
|
+
HTMLDirective.assignAspect(result, attr.name);
|
|
2519
3358
|
}
|
|
2520
3359
|
}
|
|
2521
3360
|
else {
|
|
2522
3361
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2523
|
-
result = Compiler.aggregate(parseResult);
|
|
3362
|
+
result = Compiler.aggregate(parseResult, context.policy);
|
|
2524
3363
|
}
|
|
2525
3364
|
if (result !== null) {
|
|
2526
3365
|
node.removeAttributeNode(attr);
|
|
2527
3366
|
i--;
|
|
2528
3367
|
ii--;
|
|
2529
|
-
context.addFactory(result, parentId, nodeId, nodeIndex);
|
|
3368
|
+
context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
|
|
2530
3369
|
}
|
|
2531
3370
|
}
|
|
2532
3371
|
}
|
|
@@ -2551,8 +3390,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
|
|
|
2551
3390
|
}
|
|
2552
3391
|
else {
|
|
2553
3392
|
currentNode.textContent = " ";
|
|
2554
|
-
|
|
2555
|
-
context.addFactory(currentPart, parentId, nodeId, nodeIndex);
|
|
3393
|
+
HTMLDirective.assignAspect(currentPart);
|
|
3394
|
+
context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
|
|
2556
3395
|
}
|
|
2557
3396
|
lastNode = currentNode;
|
|
2558
3397
|
}
|
|
@@ -2584,7 +3423,7 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
2584
3423
|
if (parts !== null) {
|
|
2585
3424
|
context.addFactory(
|
|
2586
3425
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2587
|
-
Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
|
|
3426
|
+
Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
|
|
2588
3427
|
}
|
|
2589
3428
|
break;
|
|
2590
3429
|
}
|
|
@@ -2598,45 +3437,28 @@ function isMarker(node, directives) {
|
|
|
2598
3437
|
Parser.parse(node.data, directives) !== null);
|
|
2599
3438
|
}
|
|
2600
3439
|
const templateTag = "TEMPLATE";
|
|
2601
|
-
const policyOptions = { createHTML: html => html };
|
|
2602
|
-
let htmlPolicy = globalThis.trustedTypes
|
|
2603
|
-
? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
|
|
2604
|
-
: policyOptions;
|
|
2605
|
-
const fastHTMLPolicy = htmlPolicy;
|
|
2606
3440
|
/**
|
|
2607
3441
|
* Common APIs related to compilation.
|
|
2608
3442
|
* @public
|
|
2609
3443
|
*/
|
|
2610
3444
|
const Compiler = {
|
|
2611
|
-
/**
|
|
2612
|
-
* Sets the HTML trusted types policy used by the compiler.
|
|
2613
|
-
* @param policy - The policy to set for HTML.
|
|
2614
|
-
* @remarks
|
|
2615
|
-
* This API can only be called once, for security reasons. It should be
|
|
2616
|
-
* called by the application developer at the start of their program.
|
|
2617
|
-
*/
|
|
2618
|
-
setHTMLPolicy(policy) {
|
|
2619
|
-
if (htmlPolicy !== fastHTMLPolicy) {
|
|
2620
|
-
throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
|
|
2621
|
-
}
|
|
2622
|
-
htmlPolicy = policy;
|
|
2623
|
-
},
|
|
2624
3445
|
/**
|
|
2625
3446
|
* Compiles a template and associated directives into a compilation
|
|
2626
3447
|
* result which can be used to create views.
|
|
2627
3448
|
* @param html - The html string or template element to compile.
|
|
2628
|
-
* @param
|
|
3449
|
+
* @param factories - The behavior factories referenced by the template.
|
|
3450
|
+
* @param policy - The security policy to compile the html with.
|
|
2629
3451
|
* @remarks
|
|
2630
3452
|
* The template that is provided for compilation is altered in-place
|
|
2631
3453
|
* and cannot be compiled again. If the original template must be preserved,
|
|
2632
3454
|
* it is recommended that you clone the original and pass the clone to this API.
|
|
2633
3455
|
* @public
|
|
2634
3456
|
*/
|
|
2635
|
-
compile(html,
|
|
3457
|
+
compile(html, factories, policy = DOM.policy) {
|
|
2636
3458
|
let template;
|
|
2637
3459
|
if (isString(html)) {
|
|
2638
3460
|
template = document.createElement(templateTag);
|
|
2639
|
-
template.innerHTML =
|
|
3461
|
+
template.innerHTML = policy.createHTML(html);
|
|
2640
3462
|
const fec = template.content.firstElementChild;
|
|
2641
3463
|
if (fec !== null && fec.tagName === templateTag) {
|
|
2642
3464
|
template = fec;
|
|
@@ -2645,20 +3467,23 @@ const Compiler = {
|
|
|
2645
3467
|
else {
|
|
2646
3468
|
template = html;
|
|
2647
3469
|
}
|
|
3470
|
+
if (!template.content.firstChild && !template.content.lastChild) {
|
|
3471
|
+
template.content.appendChild(document.createComment(""));
|
|
3472
|
+
}
|
|
2648
3473
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
|
|
2649
3474
|
const fragment = document.adoptNode(template.content);
|
|
2650
|
-
const context = new CompilationContext(fragment,
|
|
3475
|
+
const context = new CompilationContext(fragment, factories, policy);
|
|
2651
3476
|
compileAttributes(context, "", template, /* host */ "h", 0, true);
|
|
2652
3477
|
if (
|
|
2653
3478
|
// If the first node in a fragment is a marker, that means it's an unstable first node,
|
|
2654
3479
|
// because something like a when, repeat, etc. could add nodes before the marker.
|
|
2655
3480
|
// To mitigate this, we insert a stable first node. However, if we insert a node,
|
|
2656
3481
|
// that will alter the result of the TreeWalker. So, we also need to offset the target index.
|
|
2657
|
-
isMarker(fragment.firstChild,
|
|
3482
|
+
isMarker(fragment.firstChild, factories) ||
|
|
2658
3483
|
// Or if there is only one node and a directive, it means the template's content
|
|
2659
3484
|
// is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
|
|
2660
3485
|
// the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
|
|
2661
|
-
(fragment.childNodes.length === 1 && Object.keys(
|
|
3486
|
+
(fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
|
|
2662
3487
|
fragment.insertBefore(document.createComment(""), fragment.firstChild);
|
|
2663
3488
|
}
|
|
2664
3489
|
compileChildren(context, fragment, /* root */ "r");
|
|
@@ -2677,23 +3502,24 @@ const Compiler = {
|
|
|
2677
3502
|
* Aggregates an array of strings and directives into a single directive.
|
|
2678
3503
|
* @param parts - A heterogeneous array of static strings interspersed with
|
|
2679
3504
|
* directives.
|
|
3505
|
+
* @param policy - The security policy to use with the aggregated bindings.
|
|
2680
3506
|
* @returns A single inline directive that aggregates the behavior of all the parts.
|
|
2681
3507
|
*/
|
|
2682
|
-
aggregate(parts) {
|
|
3508
|
+
aggregate(parts, policy = DOM.policy) {
|
|
2683
3509
|
if (parts.length === 1) {
|
|
2684
3510
|
return parts[0];
|
|
2685
3511
|
}
|
|
2686
3512
|
let sourceAspect;
|
|
2687
|
-
let binding;
|
|
2688
3513
|
let isVolatile = false;
|
|
3514
|
+
let bindingPolicy = void 0;
|
|
2689
3515
|
const partCount = parts.length;
|
|
2690
3516
|
const finalParts = parts.map((x) => {
|
|
2691
3517
|
if (isString(x)) {
|
|
2692
3518
|
return () => x;
|
|
2693
3519
|
}
|
|
2694
3520
|
sourceAspect = x.sourceAspect || sourceAspect;
|
|
2695
|
-
binding = x.dataBinding || binding;
|
|
2696
3521
|
isVolatile = isVolatile || x.dataBinding.isVolatile;
|
|
3522
|
+
bindingPolicy = bindingPolicy || x.dataBinding.policy;
|
|
2697
3523
|
return x.dataBinding.evaluate;
|
|
2698
3524
|
});
|
|
2699
3525
|
const expression = (scope, context) => {
|
|
@@ -2703,14 +3529,56 @@ const Compiler = {
|
|
|
2703
3529
|
}
|
|
2704
3530
|
return output;
|
|
2705
3531
|
};
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
const directive = new HTMLBindingDirective(binding);
|
|
2709
|
-
Aspect.assign(directive, sourceAspect);
|
|
3532
|
+
const directive = new HTMLBindingDirective(oneWay(expression, bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy, isVolatile));
|
|
3533
|
+
HTMLDirective.assignAspect(directive, sourceAspect);
|
|
2710
3534
|
return directive;
|
|
2711
3535
|
},
|
|
2712
3536
|
};
|
|
2713
3537
|
|
|
3538
|
+
// Much thanks to LitHTML for working this out!
|
|
3539
|
+
const lastAttributeNameRegex =
|
|
3540
|
+
/* eslint-disable-next-line no-control-regex, max-len */
|
|
3541
|
+
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
3542
|
+
const noFactories = Object.create(null);
|
|
3543
|
+
/**
|
|
3544
|
+
* Inlines a template into another template.
|
|
3545
|
+
* @public
|
|
3546
|
+
*/
|
|
3547
|
+
class InlineTemplateDirective {
|
|
3548
|
+
/**
|
|
3549
|
+
* Creates an instance of InlineTemplateDirective.
|
|
3550
|
+
* @param template - The template to inline.
|
|
3551
|
+
*/
|
|
3552
|
+
constructor(html, factories = noFactories) {
|
|
3553
|
+
this.html = html;
|
|
3554
|
+
this.factories = factories;
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Creates HTML to be used within a template.
|
|
3558
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
3559
|
+
*/
|
|
3560
|
+
createHTML(add) {
|
|
3561
|
+
const factories = this.factories;
|
|
3562
|
+
for (const key in factories) {
|
|
3563
|
+
add(factories[key]);
|
|
3564
|
+
}
|
|
3565
|
+
return this.html;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
/**
|
|
3569
|
+
* An empty template partial.
|
|
3570
|
+
*/
|
|
3571
|
+
InlineTemplateDirective.empty = new InlineTemplateDirective("");
|
|
3572
|
+
HTMLDirective.define(InlineTemplateDirective);
|
|
3573
|
+
function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
|
|
3574
|
+
if (definition.aspected) {
|
|
3575
|
+
const match = lastAttributeNameRegex.exec(prevString);
|
|
3576
|
+
if (match !== null) {
|
|
3577
|
+
HTMLDirective.assignAspect(value, match[2]);
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
return value.createHTML(add);
|
|
3581
|
+
}
|
|
2714
3582
|
/**
|
|
2715
3583
|
* A template capable of creating HTMLView instances or rendering directly to DOM.
|
|
2716
3584
|
* @public
|
|
@@ -2720,21 +3588,53 @@ class ViewTemplate {
|
|
|
2720
3588
|
* Creates an instance of ViewTemplate.
|
|
2721
3589
|
* @param html - The html representing what this template will instantiate, including placeholders for directives.
|
|
2722
3590
|
* @param factories - The directives that will be connected to placeholders in the html.
|
|
3591
|
+
* @param policy - The security policy to use when compiling this template.
|
|
2723
3592
|
*/
|
|
2724
|
-
constructor(html, factories) {
|
|
3593
|
+
constructor(html, factories = {}, policy) {
|
|
3594
|
+
this.policy = policy;
|
|
2725
3595
|
this.result = null;
|
|
2726
3596
|
this.html = html;
|
|
2727
3597
|
this.factories = factories;
|
|
2728
3598
|
}
|
|
3599
|
+
/**
|
|
3600
|
+
* @internal
|
|
3601
|
+
*/
|
|
3602
|
+
compile() {
|
|
3603
|
+
if (this.result === null) {
|
|
3604
|
+
this.result = Compiler.compile(this.html, this.factories, this.policy);
|
|
3605
|
+
}
|
|
3606
|
+
return this.result;
|
|
3607
|
+
}
|
|
2729
3608
|
/**
|
|
2730
3609
|
* Creates an HTMLView instance based on this template definition.
|
|
2731
3610
|
* @param hostBindingTarget - The element that host behaviors will be bound to.
|
|
2732
3611
|
*/
|
|
2733
3612
|
create(hostBindingTarget) {
|
|
2734
|
-
|
|
2735
|
-
|
|
3613
|
+
return this.compile().createView(hostBindingTarget);
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Returns a directive that can inline the template.
|
|
3617
|
+
*/
|
|
3618
|
+
inline() {
|
|
3619
|
+
return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
|
|
3620
|
+
}
|
|
3621
|
+
/**
|
|
3622
|
+
* Sets the DOMPolicy for this template.
|
|
3623
|
+
* @param policy - The policy to associated with this template.
|
|
3624
|
+
* @returns The modified template instance.
|
|
3625
|
+
* @remarks
|
|
3626
|
+
* The DOMPolicy can only be set once for a template and cannot be
|
|
3627
|
+
* set after the template is compiled.
|
|
3628
|
+
*/
|
|
3629
|
+
withPolicy(policy) {
|
|
3630
|
+
if (this.result) {
|
|
3631
|
+
throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
|
|
2736
3632
|
}
|
|
2737
|
-
|
|
3633
|
+
if (this.policy) {
|
|
3634
|
+
throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
|
|
3635
|
+
}
|
|
3636
|
+
this.policy = policy;
|
|
3637
|
+
return this;
|
|
2738
3638
|
}
|
|
2739
3639
|
/**
|
|
2740
3640
|
* Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
|
|
@@ -2749,18 +3649,49 @@ class ViewTemplate {
|
|
|
2749
3649
|
view.appendTo(host);
|
|
2750
3650
|
return view;
|
|
2751
3651
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
3652
|
+
/**
|
|
3653
|
+
* Creates a template based on a set of static strings and dynamic values.
|
|
3654
|
+
* @param strings - The static strings to create the template with.
|
|
3655
|
+
* @param values - The dynamic values to create the template with.
|
|
3656
|
+
* @param policy - The DOMPolicy to associated with the template.
|
|
3657
|
+
* @returns A ViewTemplate.
|
|
3658
|
+
* @remarks
|
|
3659
|
+
* This API should not be used directly under normal circumstances because constructing
|
|
3660
|
+
* a template in this way, if not done properly, can open up the application to XSS
|
|
3661
|
+
* attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
|
|
3662
|
+
* and also be sure to manually sanitize all static strings particularly if they can
|
|
3663
|
+
* come from user input.
|
|
3664
|
+
*/
|
|
3665
|
+
static create(strings, values, policy) {
|
|
3666
|
+
let html = "";
|
|
3667
|
+
const factories = Object.create(null);
|
|
3668
|
+
const add = (factory) => {
|
|
3669
|
+
var _a;
|
|
3670
|
+
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
3671
|
+
factories[id] = factory;
|
|
3672
|
+
return id;
|
|
3673
|
+
};
|
|
3674
|
+
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
3675
|
+
const currentString = strings[i];
|
|
3676
|
+
let currentValue = values[i];
|
|
3677
|
+
let definition;
|
|
3678
|
+
html += currentString;
|
|
3679
|
+
if (isFunction(currentValue)) {
|
|
3680
|
+
currentValue = new HTMLBindingDirective(oneWay(currentValue));
|
|
3681
|
+
}
|
|
3682
|
+
else if (currentValue instanceof Binding) {
|
|
3683
|
+
currentValue = new HTMLBindingDirective(currentValue);
|
|
3684
|
+
}
|
|
3685
|
+
else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
|
|
3686
|
+
const staticValue = currentValue;
|
|
3687
|
+
currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
|
|
3688
|
+
}
|
|
3689
|
+
html += createHTML(currentValue, currentString, add, definition);
|
|
3690
|
+
}
|
|
3691
|
+
return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
|
|
2761
3692
|
}
|
|
2762
|
-
return value.createHTML(add);
|
|
2763
3693
|
}
|
|
3694
|
+
makeSerializationNoop(ViewTemplate);
|
|
2764
3695
|
/**
|
|
2765
3696
|
* Transforms a template literal string into a ViewTemplate.
|
|
2766
3697
|
* @param strings - The string fragments that are interpolated with the values.
|
|
@@ -2770,51 +3701,15 @@ function createAspectedHTML(value, prevString, add) {
|
|
|
2770
3701
|
* other template instances, and Directive instances.
|
|
2771
3702
|
* @public
|
|
2772
3703
|
*/
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
const add = (factory) => {
|
|
2777
|
-
var _a;
|
|
2778
|
-
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
2779
|
-
factories[id] = factory;
|
|
2780
|
-
return id;
|
|
2781
|
-
};
|
|
2782
|
-
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
2783
|
-
const currentString = strings[i];
|
|
2784
|
-
const currentValue = values[i];
|
|
2785
|
-
let definition;
|
|
2786
|
-
html += currentString;
|
|
2787
|
-
if (isFunction(currentValue)) {
|
|
2788
|
-
html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
|
|
2789
|
-
}
|
|
2790
|
-
else if (isString(currentValue)) {
|
|
2791
|
-
const match = lastAttributeNameRegex.exec(currentString);
|
|
2792
|
-
if (match !== null) {
|
|
2793
|
-
const directive = new HTMLBindingDirective(oneTime(() => currentValue));
|
|
2794
|
-
Aspect.assign(directive, match[2]);
|
|
2795
|
-
html += directive.createHTML(add);
|
|
2796
|
-
}
|
|
2797
|
-
else {
|
|
2798
|
-
html += currentValue;
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
else if (currentValue instanceof Binding) {
|
|
2802
|
-
html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
|
|
2803
|
-
}
|
|
2804
|
-
else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
|
|
2805
|
-
html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
|
|
2806
|
-
}
|
|
2807
|
-
else {
|
|
2808
|
-
if (definition.aspected) {
|
|
2809
|
-
html += createAspectedHTML(currentValue, currentString, add);
|
|
2810
|
-
}
|
|
2811
|
-
else {
|
|
2812
|
-
html += currentValue.createHTML(add);
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
3704
|
+
const html = ((strings, ...values) => {
|
|
3705
|
+
if (Array.isArray(strings) && Array.isArray(strings.raw)) {
|
|
3706
|
+
return ViewTemplate.create(strings, values);
|
|
2815
3707
|
}
|
|
2816
|
-
|
|
2817
|
-
}
|
|
3708
|
+
throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
|
|
3709
|
+
});
|
|
3710
|
+
html.partial = (html) => {
|
|
3711
|
+
return new InlineTemplateDirective(html);
|
|
3712
|
+
};
|
|
2818
3713
|
|
|
2819
3714
|
/**
|
|
2820
3715
|
* The runtime behavior for template references.
|
|
@@ -2826,7 +3721,7 @@ class RefDirective extends StatelessAttachedAttributeDirective {
|
|
|
2826
3721
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2827
3722
|
*/
|
|
2828
3723
|
bind(controller) {
|
|
2829
|
-
controller.source[this.options] = controller.targets[this.
|
|
3724
|
+
controller.source[this.options] = controller.targets[this.targetNodeId];
|
|
2830
3725
|
}
|
|
2831
3726
|
}
|
|
2832
3727
|
HTMLDirective.define(RefDirective);
|
|
@@ -2837,19 +3732,26 @@ HTMLDirective.define(RefDirective);
|
|
|
2837
3732
|
*/
|
|
2838
3733
|
const ref = (propertyName) => new RefDirective(propertyName);
|
|
2839
3734
|
|
|
3735
|
+
const noTemplate = () => null;
|
|
3736
|
+
function normalizeBinding(value) {
|
|
3737
|
+
return value === undefined ? noTemplate : isFunction(value) ? value : () => value;
|
|
3738
|
+
}
|
|
2840
3739
|
/**
|
|
2841
3740
|
* A directive that enables basic conditional rendering in a template.
|
|
2842
3741
|
* @param condition - The condition to test for rendering.
|
|
2843
3742
|
* @param templateOrTemplateBinding - The template or a binding that gets
|
|
2844
3743
|
* the template to render when the condition is true.
|
|
3744
|
+
* @param elseTemplateOrTemplateBinding - Optional template or binding that that
|
|
3745
|
+
* gets the template to render when the conditional is false.
|
|
2845
3746
|
* @public
|
|
2846
3747
|
*/
|
|
2847
|
-
function when(condition, templateOrTemplateBinding) {
|
|
3748
|
+
function when(condition, templateOrTemplateBinding, elseTemplateOrTemplateBinding) {
|
|
2848
3749
|
const dataBinding = isFunction(condition) ? condition : () => condition;
|
|
2849
|
-
const templateBinding =
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
3750
|
+
const templateBinding = normalizeBinding(templateOrTemplateBinding);
|
|
3751
|
+
const elseBinding = normalizeBinding(elseTemplateOrTemplateBinding);
|
|
3752
|
+
return (source, context) => dataBinding(source, context)
|
|
3753
|
+
? templateBinding(source, context)
|
|
3754
|
+
: elseBinding(source, context);
|
|
2853
3755
|
}
|
|
2854
3756
|
|
|
2855
3757
|
const defaultRepeatOptions = Object.freeze({
|
|
@@ -2868,6 +3770,19 @@ function bindWithPositioning(view, items, index, controller) {
|
|
|
2868
3770
|
view.context.index = index;
|
|
2869
3771
|
view.bind(items[index]);
|
|
2870
3772
|
}
|
|
3773
|
+
function isCommentNode(node) {
|
|
3774
|
+
return node.nodeType === Node.COMMENT_NODE;
|
|
3775
|
+
}
|
|
3776
|
+
class HydrationRepeatError extends Error {
|
|
3777
|
+
constructor(
|
|
3778
|
+
/**
|
|
3779
|
+
* The error message
|
|
3780
|
+
*/
|
|
3781
|
+
message, propertyBag) {
|
|
3782
|
+
super(message);
|
|
3783
|
+
this.propertyBag = propertyBag;
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
2871
3786
|
/**
|
|
2872
3787
|
* A behavior that renders a template for each item in an array.
|
|
2873
3788
|
* @public
|
|
@@ -2884,12 +3799,13 @@ class RepeatBehavior {
|
|
|
2884
3799
|
*/
|
|
2885
3800
|
constructor(directive) {
|
|
2886
3801
|
this.directive = directive;
|
|
2887
|
-
this.views = [];
|
|
2888
3802
|
this.items = null;
|
|
2889
3803
|
this.itemsObserver = null;
|
|
2890
3804
|
this.bindView = bindWithoutPositioning;
|
|
2891
|
-
|
|
2892
|
-
this.
|
|
3805
|
+
/** @internal */
|
|
3806
|
+
this.views = [];
|
|
3807
|
+
this.itemsBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
3808
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
2893
3809
|
if (directive.options.positioning) {
|
|
2894
3810
|
this.bindView = bindWithPositioning;
|
|
2895
3811
|
}
|
|
@@ -2899,12 +3815,19 @@ class RepeatBehavior {
|
|
|
2899
3815
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2900
3816
|
*/
|
|
2901
3817
|
bind(controller) {
|
|
2902
|
-
this.location = controller.targets[this.directive.
|
|
3818
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
2903
3819
|
this.controller = controller;
|
|
2904
3820
|
this.items = this.itemsBindingObserver.bind(controller);
|
|
2905
3821
|
this.template = this.templateBindingObserver.bind(controller);
|
|
2906
3822
|
this.observeItems(true);
|
|
2907
|
-
this.
|
|
3823
|
+
if (isHydratable(this.template) &&
|
|
3824
|
+
isHydratable(controller) &&
|
|
3825
|
+
controller.hydrationStage !== HydrationStage.hydrated) {
|
|
3826
|
+
this.hydrateViews(this.template);
|
|
3827
|
+
}
|
|
3828
|
+
else {
|
|
3829
|
+
this.refreshAllViews();
|
|
3830
|
+
}
|
|
2908
3831
|
controller.onUnbind(this);
|
|
2909
3832
|
}
|
|
2910
3833
|
/**
|
|
@@ -3005,10 +3928,10 @@ class RepeatBehavior {
|
|
|
3005
3928
|
leftoverViews[i].dispose();
|
|
3006
3929
|
}
|
|
3007
3930
|
if (this.directive.options.positioning) {
|
|
3008
|
-
for (let i = 0,
|
|
3931
|
+
for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
|
|
3009
3932
|
const context = views[i].context;
|
|
3010
|
-
context.length =
|
|
3011
|
-
context.index =
|
|
3933
|
+
context.length = viewsLength;
|
|
3934
|
+
context.index = i;
|
|
3012
3935
|
}
|
|
3013
3936
|
}
|
|
3014
3937
|
}
|
|
@@ -3042,6 +3965,18 @@ class RepeatBehavior {
|
|
|
3042
3965
|
for (; i < itemsLength; ++i) {
|
|
3043
3966
|
if (i < viewsLength) {
|
|
3044
3967
|
const view = views[i];
|
|
3968
|
+
if (!view) {
|
|
3969
|
+
const serializer = new XMLSerializer();
|
|
3970
|
+
throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
|
|
3971
|
+
index: i,
|
|
3972
|
+
hydrationStage: this.controller
|
|
3973
|
+
.hydrationStage,
|
|
3974
|
+
itemsLength,
|
|
3975
|
+
viewsState: views.map(v => (v ? "hydrated" : "empty")),
|
|
3976
|
+
viewTemplateString: serializer.serializeToString(template.create().fragment),
|
|
3977
|
+
rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
|
|
3978
|
+
});
|
|
3979
|
+
}
|
|
3045
3980
|
bindView(view, items, i, controller);
|
|
3046
3981
|
}
|
|
3047
3982
|
else {
|
|
@@ -3060,7 +3995,76 @@ class RepeatBehavior {
|
|
|
3060
3995
|
unbindAllViews() {
|
|
3061
3996
|
const views = this.views;
|
|
3062
3997
|
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
3063
|
-
views[i]
|
|
3998
|
+
const view = views[i];
|
|
3999
|
+
if (!view) {
|
|
4000
|
+
const serializer = new XMLSerializer();
|
|
4001
|
+
throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
|
|
4002
|
+
index: i,
|
|
4003
|
+
hydrationStage: this.controller.hydrationStage,
|
|
4004
|
+
viewsState: views.map(v => (v ? "hydrated" : "empty")),
|
|
4005
|
+
rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
|
|
4006
|
+
});
|
|
4007
|
+
}
|
|
4008
|
+
view.unbind();
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
hydrateViews(template) {
|
|
4012
|
+
if (!this.items) {
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
this.views = new Array(this.items.length);
|
|
4016
|
+
let current = this.location.previousSibling;
|
|
4017
|
+
while (current !== null) {
|
|
4018
|
+
if (!isCommentNode(current)) {
|
|
4019
|
+
current = current.previousSibling;
|
|
4020
|
+
continue;
|
|
4021
|
+
}
|
|
4022
|
+
const index = HydrationMarkup.parseRepeatEndMarker(current.data);
|
|
4023
|
+
if (index === null) {
|
|
4024
|
+
current = current.previousSibling;
|
|
4025
|
+
continue;
|
|
4026
|
+
}
|
|
4027
|
+
current.data = "";
|
|
4028
|
+
// end of repeat is the previousSibling of end comment
|
|
4029
|
+
const end = current.previousSibling;
|
|
4030
|
+
if (!end) {
|
|
4031
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": end should never be null.`);
|
|
4032
|
+
}
|
|
4033
|
+
// find start marker
|
|
4034
|
+
let start = end;
|
|
4035
|
+
// How many unmatched end markers we've encountered
|
|
4036
|
+
let unmatchedEndMarkers = 0;
|
|
4037
|
+
while (start !== null) {
|
|
4038
|
+
if (isCommentNode(start)) {
|
|
4039
|
+
if (HydrationMarkup.isRepeatViewEndMarker(start.data)) {
|
|
4040
|
+
unmatchedEndMarkers++;
|
|
4041
|
+
}
|
|
4042
|
+
else if (HydrationMarkup.isRepeatViewStartMarker(start.data)) {
|
|
4043
|
+
if (unmatchedEndMarkers) {
|
|
4044
|
+
unmatchedEndMarkers--;
|
|
4045
|
+
}
|
|
4046
|
+
else {
|
|
4047
|
+
if (HydrationMarkup.parseRepeatStartMarker(start.data) !==
|
|
4048
|
+
index) {
|
|
4049
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host
|
|
4050
|
+
.nodeName}": Mismatched start and end markers.`);
|
|
4051
|
+
}
|
|
4052
|
+
start.data = "";
|
|
4053
|
+
current = start.previousSibling;
|
|
4054
|
+
// start of repeat content is the nextSibling of start comment
|
|
4055
|
+
start = start.nextSibling;
|
|
4056
|
+
const view = template.hydrate(start, end);
|
|
4057
|
+
this.views[index] = view;
|
|
4058
|
+
this.bindView(view, this.items, index, this.controller);
|
|
4059
|
+
break;
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
start = start.previousSibling;
|
|
4064
|
+
}
|
|
4065
|
+
if (!start) {
|
|
4066
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": start should never be null.`);
|
|
4067
|
+
}
|
|
3064
4068
|
}
|
|
3065
4069
|
}
|
|
3066
4070
|
}
|
|
@@ -3079,10 +4083,6 @@ class RepeatDirective {
|
|
|
3079
4083
|
this.dataBinding = dataBinding;
|
|
3080
4084
|
this.templateBinding = templateBinding;
|
|
3081
4085
|
this.options = options;
|
|
3082
|
-
/**
|
|
3083
|
-
* The unique id of the factory.
|
|
3084
|
-
*/
|
|
3085
|
-
this.id = nextId();
|
|
3086
4086
|
ArrayObserver.enable();
|
|
3087
4087
|
}
|
|
3088
4088
|
/**
|
|
@@ -3110,8 +4110,8 @@ HTMLDirective.define(RepeatDirective);
|
|
|
3110
4110
|
* @public
|
|
3111
4111
|
*/
|
|
3112
4112
|
function repeat(items, template, options = defaultRepeatOptions) {
|
|
3113
|
-
const dataBinding = normalizeBinding(items);
|
|
3114
|
-
const templateBinding = normalizeBinding(template);
|
|
4113
|
+
const dataBinding = normalizeBinding$1(items);
|
|
4114
|
+
const templateBinding = normalizeBinding$1(template);
|
|
3115
4115
|
return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
|
|
3116
4116
|
}
|
|
3117
4117
|
|
|
@@ -3131,9 +4131,15 @@ const elements = (selector) => selector
|
|
|
3131
4131
|
* Internally used by the SlottedDirective and the ChildrenDirective.
|
|
3132
4132
|
*/
|
|
3133
4133
|
class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
4134
|
+
/**
|
|
4135
|
+
* The unique id of the factory.
|
|
4136
|
+
*/
|
|
4137
|
+
get id() {
|
|
4138
|
+
return this._id;
|
|
4139
|
+
}
|
|
4140
|
+
set id(value) {
|
|
4141
|
+
this._id = value;
|
|
4142
|
+
this._controllerProperty = `${value}-c`;
|
|
3137
4143
|
}
|
|
3138
4144
|
/**
|
|
3139
4145
|
* Bind this behavior to the source.
|
|
@@ -3142,8 +4148,8 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3142
4148
|
* @param targets - The targets that behaviors in a view can attach to.
|
|
3143
4149
|
*/
|
|
3144
4150
|
bind(controller) {
|
|
3145
|
-
const target = controller.targets[this.
|
|
3146
|
-
target[this.
|
|
4151
|
+
const target = controller.targets[this.targetNodeId];
|
|
4152
|
+
target[this._controllerProperty] = controller;
|
|
3147
4153
|
this.updateTarget(controller.source, this.computeNodes(target));
|
|
3148
4154
|
this.observe(target);
|
|
3149
4155
|
controller.onUnbind(this);
|
|
@@ -3155,10 +4161,10 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3155
4161
|
* @param targets - The targets that behaviors in a view can attach to.
|
|
3156
4162
|
*/
|
|
3157
4163
|
unbind(controller) {
|
|
3158
|
-
const target = controller.targets[this.
|
|
4164
|
+
const target = controller.targets[this.targetNodeId];
|
|
3159
4165
|
this.updateTarget(controller.source, emptyArray);
|
|
3160
4166
|
this.disconnect(target);
|
|
3161
|
-
target[this.
|
|
4167
|
+
target[this._controllerProperty] = null;
|
|
3162
4168
|
}
|
|
3163
4169
|
/**
|
|
3164
4170
|
* Gets the data source for the target.
|
|
@@ -3166,7 +4172,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3166
4172
|
* @returns The source.
|
|
3167
4173
|
*/
|
|
3168
4174
|
getSource(target) {
|
|
3169
|
-
return target[this.
|
|
4175
|
+
return target[this._controllerProperty].source;
|
|
3170
4176
|
}
|
|
3171
4177
|
/**
|
|
3172
4178
|
* Updates the source property with the computed nodes.
|
|
@@ -3250,7 +4256,7 @@ class ChildrenDirective extends NodeObservationDirective {
|
|
|
3250
4256
|
*/
|
|
3251
4257
|
constructor(options) {
|
|
3252
4258
|
super(options);
|
|
3253
|
-
this.observerProperty =
|
|
4259
|
+
this.observerProperty = Symbol();
|
|
3254
4260
|
this.handleEvent = (mutations, observer) => {
|
|
3255
4261
|
const target = observer.target;
|
|
3256
4262
|
this.updateTarget(this.getSource(target), this.computeNodes(target));
|
|
@@ -3262,8 +4268,12 @@ class ChildrenDirective extends NodeObservationDirective {
|
|
|
3262
4268
|
* @param target - The target to observe.
|
|
3263
4269
|
*/
|
|
3264
4270
|
observe(target) {
|
|
3265
|
-
|
|
3266
|
-
|
|
4271
|
+
let observer = target[this.observerProperty];
|
|
4272
|
+
if (!observer) {
|
|
4273
|
+
observer = new MutationObserver(this.handleEvent);
|
|
4274
|
+
observer.toJSON = noop;
|
|
4275
|
+
target[this.observerProperty] = observer;
|
|
4276
|
+
}
|
|
3267
4277
|
observer.target = target;
|
|
3268
4278
|
observer.observe(target, this.options);
|
|
3269
4279
|
}
|
|
@@ -3326,13 +4336,26 @@ const booleanConverter = {
|
|
|
3326
4336
|
return value ? "true" : "false";
|
|
3327
4337
|
},
|
|
3328
4338
|
fromView(value) {
|
|
3329
|
-
return value === null ||
|
|
4339
|
+
return !(value === null ||
|
|
3330
4340
|
value === void 0 ||
|
|
3331
4341
|
value === "false" ||
|
|
3332
4342
|
value === false ||
|
|
3333
|
-
value === 0
|
|
3334
|
-
|
|
3335
|
-
|
|
4343
|
+
value === 0);
|
|
4344
|
+
},
|
|
4345
|
+
};
|
|
4346
|
+
/**
|
|
4347
|
+
* A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`,
|
|
4348
|
+
* and `void` values are converted to `null`.
|
|
4349
|
+
* @public
|
|
4350
|
+
*/
|
|
4351
|
+
const nullableBooleanConverter = {
|
|
4352
|
+
toView(value) {
|
|
4353
|
+
return typeof value === "boolean" ? value.toString() : "";
|
|
4354
|
+
},
|
|
4355
|
+
fromView(value) {
|
|
4356
|
+
return [null, undefined, void 0].includes(value)
|
|
4357
|
+
? null
|
|
4358
|
+
: booleanConverter.fromView(value);
|
|
3336
4359
|
},
|
|
3337
4360
|
};
|
|
3338
4361
|
function toNumber(value) {
|
|
@@ -3389,7 +4412,7 @@ class AttributeDefinition {
|
|
|
3389
4412
|
/**
|
|
3390
4413
|
* Sets the value of the attribute/property on the source element.
|
|
3391
4414
|
* @param source - The source element to access.
|
|
3392
|
-
* @param
|
|
4415
|
+
* @param newValue - The value to set the attribute/property to.
|
|
3393
4416
|
*/
|
|
3394
4417
|
setValue(source, newValue) {
|
|
3395
4418
|
const oldValue = source[this.fieldName];
|
|
@@ -3500,7 +4523,8 @@ function attr(configOrTarget, prop) {
|
|
|
3500
4523
|
|
|
3501
4524
|
const defaultShadowOptions = { mode: "open" };
|
|
3502
4525
|
const defaultElementOptions = {};
|
|
3503
|
-
const
|
|
4526
|
+
const fastElementBaseTypes = new Set();
|
|
4527
|
+
const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
|
|
3504
4528
|
/**
|
|
3505
4529
|
* Defines metadata for a FASTElement.
|
|
3506
4530
|
* @public
|
|
@@ -3548,51 +4572,564 @@ class FASTElementDefinition {
|
|
|
3548
4572
|
this.styles = ElementStyles.normalize(nameOrConfig.styles);
|
|
3549
4573
|
fastElementRegistry.register(this);
|
|
3550
4574
|
}
|
|
3551
|
-
/**
|
|
3552
|
-
* Indicates if this element has been defined in at least one registry.
|
|
3553
|
-
*/
|
|
3554
|
-
get isDefined() {
|
|
3555
|
-
return this.platformDefined;
|
|
4575
|
+
/**
|
|
4576
|
+
* Indicates if this element has been defined in at least one registry.
|
|
4577
|
+
*/
|
|
4578
|
+
get isDefined() {
|
|
4579
|
+
return this.platformDefined;
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* Defines a custom element based on this definition.
|
|
4583
|
+
* @param registry - The element registry to define the element in.
|
|
4584
|
+
* @remarks
|
|
4585
|
+
* This operation is idempotent per registry.
|
|
4586
|
+
*/
|
|
4587
|
+
define(registry = this.registry) {
|
|
4588
|
+
const type = this.type;
|
|
4589
|
+
if (!registry.get(this.name)) {
|
|
4590
|
+
this.platformDefined = true;
|
|
4591
|
+
registry.define(this.name, type, this.elementOptions);
|
|
4592
|
+
}
|
|
4593
|
+
return this;
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* Creates an instance of FASTElementDefinition.
|
|
4597
|
+
* @param type - The type this definition is being created for.
|
|
4598
|
+
* @param nameOrDef - The name of the element to define or a config object
|
|
4599
|
+
* that describes the element to define.
|
|
4600
|
+
*/
|
|
4601
|
+
static compose(type, nameOrDef) {
|
|
4602
|
+
if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
|
|
4603
|
+
return new FASTElementDefinition(class extends type {
|
|
4604
|
+
}, nameOrDef);
|
|
4605
|
+
}
|
|
4606
|
+
return new FASTElementDefinition(type, nameOrDef);
|
|
4607
|
+
}
|
|
4608
|
+
/**
|
|
4609
|
+
* Registers a FASTElement base type.
|
|
4610
|
+
* @param type - The type to register as a base type.
|
|
4611
|
+
* @internal
|
|
4612
|
+
*/
|
|
4613
|
+
static registerBaseType(type) {
|
|
4614
|
+
fastElementBaseTypes.add(type);
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
/**
|
|
4618
|
+
* Gets the element definition associated with the specified type.
|
|
4619
|
+
* @param type - The custom element type to retrieve the definition for.
|
|
4620
|
+
*/
|
|
4621
|
+
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
4622
|
+
/**
|
|
4623
|
+
* Gets the element definition associated with the instance.
|
|
4624
|
+
* @param instance - The custom element instance to retrieve the definition for.
|
|
4625
|
+
*/
|
|
4626
|
+
FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
|
|
4627
|
+
|
|
4628
|
+
/**
|
|
4629
|
+
* A Behavior that enables advanced rendering.
|
|
4630
|
+
* @public
|
|
4631
|
+
*/
|
|
4632
|
+
class RenderBehavior {
|
|
4633
|
+
/**
|
|
4634
|
+
* Creates an instance of RenderBehavior.
|
|
4635
|
+
* @param directive - The render directive that created this behavior.
|
|
4636
|
+
*/
|
|
4637
|
+
constructor(directive) {
|
|
4638
|
+
this.directive = directive;
|
|
4639
|
+
this.location = null;
|
|
4640
|
+
this.controller = null;
|
|
4641
|
+
this.view = null;
|
|
4642
|
+
this.data = null;
|
|
4643
|
+
this.dataBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
4644
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
4645
|
+
}
|
|
4646
|
+
/**
|
|
4647
|
+
* Bind this behavior.
|
|
4648
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
4649
|
+
*/
|
|
4650
|
+
bind(controller) {
|
|
4651
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
4652
|
+
this.controller = controller;
|
|
4653
|
+
this.data = this.dataBindingObserver.bind(controller);
|
|
4654
|
+
this.template = this.templateBindingObserver.bind(controller);
|
|
4655
|
+
controller.onUnbind(this);
|
|
4656
|
+
if (isHydratable(this.template) &&
|
|
4657
|
+
isHydratable(controller) &&
|
|
4658
|
+
controller.hydrationStage !== HydrationStage.hydrated &&
|
|
4659
|
+
!this.view) {
|
|
4660
|
+
const viewNodes = controller.bindingViewBoundaries[this.directive.targetNodeId];
|
|
4661
|
+
if (viewNodes) {
|
|
4662
|
+
this.view = this.template.hydrate(viewNodes.first, viewNodes.last);
|
|
4663
|
+
this.bindView(this.view);
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
else {
|
|
4667
|
+
this.refreshView();
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
/**
|
|
4671
|
+
* Unbinds this behavior.
|
|
4672
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
4673
|
+
*/
|
|
4674
|
+
unbind(controller) {
|
|
4675
|
+
const view = this.view;
|
|
4676
|
+
if (view !== null && view.isComposed) {
|
|
4677
|
+
view.unbind();
|
|
4678
|
+
view.needsBindOnly = true;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
/** @internal */
|
|
4682
|
+
handleChange(source, observer) {
|
|
4683
|
+
if (observer === this.dataBindingObserver) {
|
|
4684
|
+
this.data = this.dataBindingObserver.bind(this.controller);
|
|
4685
|
+
}
|
|
4686
|
+
if (this.directive.templateBindingDependsOnData ||
|
|
4687
|
+
observer === this.templateBindingObserver) {
|
|
4688
|
+
this.template = this.templateBindingObserver.bind(this.controller);
|
|
4689
|
+
}
|
|
4690
|
+
this.refreshView();
|
|
4691
|
+
}
|
|
4692
|
+
bindView(view) {
|
|
4693
|
+
// It's possible that the value is the same as the previous template
|
|
4694
|
+
// and that there's actually no need to compose it.
|
|
4695
|
+
if (!view.isComposed) {
|
|
4696
|
+
view.isComposed = true;
|
|
4697
|
+
view.bind(this.data);
|
|
4698
|
+
view.insertBefore(this.location);
|
|
4699
|
+
view.$fastTemplate = this.template;
|
|
4700
|
+
}
|
|
4701
|
+
else if (view.needsBindOnly) {
|
|
4702
|
+
view.needsBindOnly = false;
|
|
4703
|
+
view.bind(this.data);
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
refreshView() {
|
|
4707
|
+
let view = this.view;
|
|
4708
|
+
const template = this.template;
|
|
4709
|
+
if (view === null) {
|
|
4710
|
+
this.view = view = template.create();
|
|
4711
|
+
this.view.context.parent = this.controller.source;
|
|
4712
|
+
this.view.context.parentContext = this.controller.context;
|
|
4713
|
+
}
|
|
4714
|
+
else {
|
|
4715
|
+
// If there is a previous view, but it wasn't created
|
|
4716
|
+
// from the same template as the new value, then we
|
|
4717
|
+
// need to remove the old view if it's still in the DOM
|
|
4718
|
+
// and create a new view from the template.
|
|
4719
|
+
if (view.$fastTemplate !== template) {
|
|
4720
|
+
if (view.isComposed) {
|
|
4721
|
+
view.remove();
|
|
4722
|
+
view.unbind();
|
|
4723
|
+
}
|
|
4724
|
+
this.view = view = template.create();
|
|
4725
|
+
this.view.context.parent = this.controller.source;
|
|
4726
|
+
this.view.context.parentContext = this.controller.context;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
this.bindView(view);
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
/**
|
|
4733
|
+
* A Directive that enables use of the RenderBehavior.
|
|
4734
|
+
* @public
|
|
4735
|
+
*/
|
|
4736
|
+
class RenderDirective {
|
|
4737
|
+
/**
|
|
4738
|
+
* Creates an instance of RenderDirective.
|
|
4739
|
+
* @param dataBinding - A binding expression that returns the data to render.
|
|
4740
|
+
* @param templateBinding - A binding expression that returns the template to use to render the data.
|
|
4741
|
+
*/
|
|
4742
|
+
constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
|
|
4743
|
+
this.dataBinding = dataBinding;
|
|
4744
|
+
this.templateBinding = templateBinding;
|
|
4745
|
+
this.templateBindingDependsOnData = templateBindingDependsOnData;
|
|
4746
|
+
}
|
|
4747
|
+
/**
|
|
4748
|
+
* Creates HTML to be used within a template.
|
|
4749
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
4750
|
+
*/
|
|
4751
|
+
createHTML(add) {
|
|
4752
|
+
return Markup.comment(add(this));
|
|
4753
|
+
}
|
|
4754
|
+
/**
|
|
4755
|
+
* Creates a behavior.
|
|
4756
|
+
* @param targets - The targets available for behaviors to be attached to.
|
|
4757
|
+
*/
|
|
4758
|
+
createBehavior() {
|
|
4759
|
+
return new RenderBehavior(this);
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
HTMLDirective.define(RenderDirective);
|
|
4763
|
+
function isElementRenderOptions(object) {
|
|
4764
|
+
return !!object.element || !!object.tagName;
|
|
4765
|
+
}
|
|
4766
|
+
const typeToInstructionLookup = new Map();
|
|
4767
|
+
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
4768
|
+
const defaultAttributes = { ":model": (x) => x };
|
|
4769
|
+
const brand = Symbol("RenderInstruction");
|
|
4770
|
+
const defaultViewName = "default-view";
|
|
4771
|
+
const nullTemplate = html `
|
|
4772
|
+
|
|
4773
|
+
`;
|
|
4774
|
+
function instructionToTemplate(def) {
|
|
4775
|
+
if (def === void 0) {
|
|
4776
|
+
return nullTemplate;
|
|
4777
|
+
}
|
|
4778
|
+
return def.template;
|
|
4779
|
+
}
|
|
4780
|
+
function createElementTemplate(tagName, options) {
|
|
4781
|
+
const markup = [];
|
|
4782
|
+
const values = [];
|
|
4783
|
+
const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
|
|
4784
|
+
markup.push(`<${tagName}`);
|
|
4785
|
+
if (attributes) {
|
|
4786
|
+
const attrNames = Object.getOwnPropertyNames(attributes);
|
|
4787
|
+
for (let i = 0, ii = attrNames.length; i < ii; ++i) {
|
|
4788
|
+
const name = attrNames[i];
|
|
4789
|
+
if (i === 0) {
|
|
4790
|
+
markup[0] = `${markup[0]} ${name}="`;
|
|
4791
|
+
}
|
|
4792
|
+
else {
|
|
4793
|
+
markup.push(`" ${name}="`);
|
|
4794
|
+
}
|
|
4795
|
+
values.push(attributes[name]);
|
|
4796
|
+
}
|
|
4797
|
+
markup.push(`"`);
|
|
4798
|
+
}
|
|
4799
|
+
if (directives) {
|
|
4800
|
+
markup[markup.length - 1] += " ";
|
|
4801
|
+
for (let i = 0, ii = directives.length; i < ii; ++i) {
|
|
4802
|
+
const directive = directives[i];
|
|
4803
|
+
markup.push(i > 0 ? "" : " ");
|
|
4804
|
+
values.push(directive);
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
markup[markup.length - 1] += ">";
|
|
4808
|
+
if (content && isFunction(content.create)) {
|
|
4809
|
+
values.push(content);
|
|
4810
|
+
markup.push(`</${tagName}>`);
|
|
4811
|
+
}
|
|
4812
|
+
else {
|
|
4813
|
+
const lastIndex = markup.length - 1;
|
|
4814
|
+
markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
|
|
4815
|
+
}
|
|
4816
|
+
return ViewTemplate.create(markup, values, policy);
|
|
4817
|
+
}
|
|
4818
|
+
function create(options) {
|
|
4819
|
+
var _a;
|
|
4820
|
+
const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
|
|
4821
|
+
let template;
|
|
4822
|
+
if (isElementRenderOptions(options)) {
|
|
4823
|
+
let tagName = options.tagName;
|
|
4824
|
+
if (!tagName) {
|
|
4825
|
+
const def = FASTElementDefinition.getByType(options.element);
|
|
4826
|
+
if (def) {
|
|
4827
|
+
tagName = def.name;
|
|
4828
|
+
}
|
|
4829
|
+
else {
|
|
4830
|
+
throw new Error("Invalid element for model rendering.");
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
if (!options.attributes) {
|
|
4834
|
+
options.attributes = defaultAttributes;
|
|
4835
|
+
}
|
|
4836
|
+
template = createElementTemplate(tagName, options);
|
|
4837
|
+
}
|
|
4838
|
+
else {
|
|
4839
|
+
template = options.template;
|
|
4840
|
+
}
|
|
4841
|
+
return {
|
|
4842
|
+
brand,
|
|
4843
|
+
type: options.type,
|
|
4844
|
+
name,
|
|
4845
|
+
template,
|
|
4846
|
+
};
|
|
4847
|
+
}
|
|
4848
|
+
function instanceOf(object) {
|
|
4849
|
+
return object && object.brand === brand;
|
|
4850
|
+
}
|
|
4851
|
+
function register(optionsOrInstruction) {
|
|
4852
|
+
let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
|
|
4853
|
+
if (lookup === void 0) {
|
|
4854
|
+
typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
|
|
4855
|
+
}
|
|
4856
|
+
const instruction = instanceOf(optionsOrInstruction)
|
|
4857
|
+
? optionsOrInstruction
|
|
4858
|
+
: create(optionsOrInstruction);
|
|
4859
|
+
return (lookup[instruction.name] = instruction);
|
|
4860
|
+
}
|
|
4861
|
+
function getByType(type, name) {
|
|
4862
|
+
const entries = typeToInstructionLookup.get(type);
|
|
4863
|
+
if (entries === void 0) {
|
|
4864
|
+
return void 0;
|
|
4865
|
+
}
|
|
4866
|
+
return entries[name !== null && name !== void 0 ? name : defaultViewName];
|
|
4867
|
+
}
|
|
4868
|
+
function getForInstance(object, name) {
|
|
4869
|
+
if (object) {
|
|
4870
|
+
return getByType(object.constructor, name);
|
|
4871
|
+
}
|
|
4872
|
+
return void 0;
|
|
4873
|
+
}
|
|
4874
|
+
/**
|
|
4875
|
+
* Provides APIs for creating and interacting with render instructions.
|
|
4876
|
+
* @public
|
|
4877
|
+
*/
|
|
4878
|
+
Object.freeze({
|
|
4879
|
+
/**
|
|
4880
|
+
* Checks whether the provided object is a RenderInstruction.
|
|
4881
|
+
* @param object - The object to check.
|
|
4882
|
+
* @returns true if the object is a RenderInstruction; false otherwise
|
|
4883
|
+
*/
|
|
4884
|
+
instanceOf,
|
|
4885
|
+
/**
|
|
4886
|
+
* Creates a RenderInstruction for a set of options.
|
|
4887
|
+
* @param options - The options to use when creating the RenderInstruction.
|
|
4888
|
+
* @remarks
|
|
4889
|
+
* This API should be used with caution. When providing attributes or content,
|
|
4890
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
4891
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
4892
|
+
* content and attribute values particularly if they can come from user input.
|
|
4893
|
+
*/
|
|
4894
|
+
create,
|
|
4895
|
+
/**
|
|
4896
|
+
* Creates a template based on a tag name.
|
|
4897
|
+
* @param tagName - The tag name to use when creating the template.
|
|
4898
|
+
* @param attributes - The attributes to apply to the element.
|
|
4899
|
+
* @param content - The content to insert into the element.
|
|
4900
|
+
* @param policy - The DOMPolicy to create the template with.
|
|
4901
|
+
* @returns A template based on the provided specifications.
|
|
4902
|
+
* @remarks
|
|
4903
|
+
* This API should be used with caution. When providing attributes or content,
|
|
4904
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
4905
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
4906
|
+
* content and attribute values particularly if they can come from user input.
|
|
4907
|
+
*/
|
|
4908
|
+
createElementTemplate,
|
|
4909
|
+
/**
|
|
4910
|
+
* Creates and registers an instruction.
|
|
4911
|
+
* @param options The options to use when creating the RenderInstruction.
|
|
4912
|
+
* @remarks
|
|
4913
|
+
* A previously created RenderInstruction can also be registered.
|
|
4914
|
+
*/
|
|
4915
|
+
register,
|
|
4916
|
+
/**
|
|
4917
|
+
* Finds a previously registered RenderInstruction by type and optionally by name.
|
|
4918
|
+
* @param type - The type to retrieve the RenderInstruction for.
|
|
4919
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
4920
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
4921
|
+
*/
|
|
4922
|
+
getByType,
|
|
4923
|
+
/**
|
|
4924
|
+
* Finds a previously registered RenderInstruction for the instance's type and optionally by name.
|
|
4925
|
+
* @param object - The instance to retrieve the RenderInstruction for.
|
|
4926
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
4927
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
4928
|
+
*/
|
|
4929
|
+
getForInstance,
|
|
4930
|
+
});
|
|
4931
|
+
/**
|
|
4932
|
+
* @internal
|
|
4933
|
+
*/
|
|
4934
|
+
class NodeTemplate {
|
|
4935
|
+
constructor(node) {
|
|
4936
|
+
this.node = node;
|
|
4937
|
+
node.$fastTemplate = this;
|
|
4938
|
+
}
|
|
4939
|
+
get context() {
|
|
4940
|
+
// HACK
|
|
4941
|
+
return this;
|
|
4942
|
+
}
|
|
4943
|
+
bind(source) { }
|
|
4944
|
+
unbind() { }
|
|
4945
|
+
insertBefore(refNode) {
|
|
4946
|
+
refNode.parentNode.insertBefore(this.node, refNode);
|
|
4947
|
+
}
|
|
4948
|
+
remove() {
|
|
4949
|
+
this.node.parentNode.removeChild(this.node);
|
|
4950
|
+
}
|
|
4951
|
+
create() {
|
|
4952
|
+
return this;
|
|
4953
|
+
}
|
|
4954
|
+
hydrate(first, last) {
|
|
4955
|
+
return this;
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
/**
|
|
4959
|
+
* Creates a RenderDirective for use in advanced rendering scenarios.
|
|
4960
|
+
* @param value - The binding expression that returns the data to be rendered. The expression
|
|
4961
|
+
* can also return a Node to render directly.
|
|
4962
|
+
* @param template - A template to render the data with
|
|
4963
|
+
* or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
|
|
4964
|
+
* Expressions can also be provided to dynamically determine either the template or the name.
|
|
4965
|
+
* @returns A RenderDirective suitable for use in a template.
|
|
4966
|
+
* @remarks
|
|
4967
|
+
* If no binding is provided, then a default binding that returns the source is created.
|
|
4968
|
+
* If no template is provided, then a binding is created that will use registered
|
|
4969
|
+
* RenderInstructions to determine the view.
|
|
4970
|
+
* If the template binding returns a string, then it will be used to look up a
|
|
4971
|
+
* RenderInstruction to determine the view.
|
|
4972
|
+
* @public
|
|
4973
|
+
*/
|
|
4974
|
+
function render(value, template) {
|
|
4975
|
+
let dataBinding;
|
|
4976
|
+
if (value === void 0) {
|
|
4977
|
+
dataBinding = oneTime((source) => source);
|
|
4978
|
+
}
|
|
4979
|
+
else {
|
|
4980
|
+
dataBinding = normalizeBinding$1(value);
|
|
4981
|
+
}
|
|
4982
|
+
let templateBinding;
|
|
4983
|
+
let templateBindingDependsOnData = false;
|
|
4984
|
+
if (template === void 0) {
|
|
4985
|
+
templateBindingDependsOnData = true;
|
|
4986
|
+
templateBinding = oneTime((s, c) => {
|
|
4987
|
+
var _a;
|
|
4988
|
+
const data = dataBinding.evaluate(s, c);
|
|
4989
|
+
if (data instanceof Node) {
|
|
4990
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
4991
|
+
}
|
|
4992
|
+
return instructionToTemplate(getForInstance(data));
|
|
4993
|
+
});
|
|
4994
|
+
}
|
|
4995
|
+
else if (isFunction(template)) {
|
|
4996
|
+
templateBinding = oneWay((s, c) => {
|
|
4997
|
+
var _a;
|
|
4998
|
+
let result = template(s, c);
|
|
4999
|
+
if (isString(result)) {
|
|
5000
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
5001
|
+
}
|
|
5002
|
+
else if (result instanceof Node) {
|
|
5003
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
5004
|
+
}
|
|
5005
|
+
return result;
|
|
5006
|
+
}, void 0, true);
|
|
5007
|
+
}
|
|
5008
|
+
else if (isString(template)) {
|
|
5009
|
+
templateBindingDependsOnData = true;
|
|
5010
|
+
templateBinding = oneTime((s, c) => {
|
|
5011
|
+
var _a;
|
|
5012
|
+
const data = dataBinding.evaluate(s, c);
|
|
5013
|
+
if (data instanceof Node) {
|
|
5014
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
5015
|
+
}
|
|
5016
|
+
return instructionToTemplate(getForInstance(data, template));
|
|
5017
|
+
});
|
|
5018
|
+
}
|
|
5019
|
+
else if (template instanceof Binding) {
|
|
5020
|
+
const evaluateTemplate = template.evaluate;
|
|
5021
|
+
template.evaluate = (s, c) => {
|
|
5022
|
+
var _a;
|
|
5023
|
+
let result = evaluateTemplate(s, c);
|
|
5024
|
+
if (isString(result)) {
|
|
5025
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
5026
|
+
}
|
|
5027
|
+
else if (result instanceof Node) {
|
|
5028
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
5029
|
+
}
|
|
5030
|
+
return result;
|
|
5031
|
+
};
|
|
5032
|
+
templateBinding = template;
|
|
5033
|
+
}
|
|
5034
|
+
else {
|
|
5035
|
+
templateBinding = oneTime((s, c) => template);
|
|
3556
5036
|
}
|
|
5037
|
+
return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
|
|
5038
|
+
}
|
|
5039
|
+
|
|
5040
|
+
/**
|
|
5041
|
+
* An extension of MutationObserver that supports unobserving nodes.
|
|
5042
|
+
* @internal
|
|
5043
|
+
*/
|
|
5044
|
+
class UnobservableMutationObserver extends MutationObserver {
|
|
3557
5045
|
/**
|
|
3558
|
-
*
|
|
3559
|
-
* @param
|
|
3560
|
-
* @remarks
|
|
3561
|
-
* This operation is idempotent per registry.
|
|
5046
|
+
* Creates an instance of UnobservableMutationObserver.
|
|
5047
|
+
* @param callback - The callback to invoke when observed nodes are changed.
|
|
3562
5048
|
*/
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
this.platformDefined = true;
|
|
3567
|
-
registry.define(this.name, type, this.elementOptions);
|
|
5049
|
+
constructor(callback) {
|
|
5050
|
+
function handler(mutations) {
|
|
5051
|
+
this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
|
|
3568
5052
|
}
|
|
3569
|
-
|
|
5053
|
+
super(handler);
|
|
5054
|
+
this.callback = callback;
|
|
5055
|
+
this.observedNodes = new Set();
|
|
3570
5056
|
}
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
if (found) {
|
|
3580
|
-
return new FASTElementDefinition(class extends type {
|
|
3581
|
-
}, nameOrDef);
|
|
5057
|
+
observe(target, options) {
|
|
5058
|
+
this.observedNodes.add(target);
|
|
5059
|
+
super.observe(target, options);
|
|
5060
|
+
}
|
|
5061
|
+
unobserve(target) {
|
|
5062
|
+
this.observedNodes.delete(target);
|
|
5063
|
+
if (this.observedNodes.size < 1) {
|
|
5064
|
+
this.disconnect();
|
|
3582
5065
|
}
|
|
3583
|
-
return new FASTElementDefinition(type, nameOrDef);
|
|
3584
5066
|
}
|
|
3585
5067
|
}
|
|
3586
5068
|
/**
|
|
3587
|
-
*
|
|
3588
|
-
*
|
|
3589
|
-
|
|
3590
|
-
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
3591
|
-
/**
|
|
3592
|
-
* Gets the element definition associated with the instance.
|
|
3593
|
-
* @param instance - The custom element instance to retrieve the definition for.
|
|
5069
|
+
* Bridges between ViewBehaviors and HostBehaviors, enabling a host to
|
|
5070
|
+
* control ViewBehaviors.
|
|
5071
|
+
* @public
|
|
3594
5072
|
*/
|
|
3595
|
-
|
|
5073
|
+
Object.freeze({
|
|
5074
|
+
/**
|
|
5075
|
+
* Creates a ViewBehaviorOrchestrator.
|
|
5076
|
+
* @param source - The source to to associate behaviors with.
|
|
5077
|
+
* @returns A ViewBehaviorOrchestrator.
|
|
5078
|
+
*/
|
|
5079
|
+
create(source) {
|
|
5080
|
+
const behaviors = [];
|
|
5081
|
+
const targets = {};
|
|
5082
|
+
let unbindables = null;
|
|
5083
|
+
let isConnected = false;
|
|
5084
|
+
return {
|
|
5085
|
+
source,
|
|
5086
|
+
context: ExecutionContext.default,
|
|
5087
|
+
targets,
|
|
5088
|
+
get isBound() {
|
|
5089
|
+
return isConnected;
|
|
5090
|
+
},
|
|
5091
|
+
addBehaviorFactory(factory, target) {
|
|
5092
|
+
var _a, _b, _c, _d;
|
|
5093
|
+
const compiled = factory;
|
|
5094
|
+
compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
|
|
5095
|
+
compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
|
|
5096
|
+
compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
|
|
5097
|
+
compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
|
|
5098
|
+
this.addTarget(compiled.targetNodeId, target);
|
|
5099
|
+
this.addBehavior(compiled.createBehavior());
|
|
5100
|
+
},
|
|
5101
|
+
addTarget(nodeId, target) {
|
|
5102
|
+
targets[nodeId] = target;
|
|
5103
|
+
},
|
|
5104
|
+
addBehavior(behavior) {
|
|
5105
|
+
behaviors.push(behavior);
|
|
5106
|
+
if (isConnected) {
|
|
5107
|
+
behavior.bind(this);
|
|
5108
|
+
}
|
|
5109
|
+
},
|
|
5110
|
+
onUnbind(unbindable) {
|
|
5111
|
+
if (unbindables === null) {
|
|
5112
|
+
unbindables = [];
|
|
5113
|
+
}
|
|
5114
|
+
unbindables.push(unbindable);
|
|
5115
|
+
},
|
|
5116
|
+
connectedCallback(controller) {
|
|
5117
|
+
if (!isConnected) {
|
|
5118
|
+
isConnected = true;
|
|
5119
|
+
behaviors.forEach(x => x.bind(this));
|
|
5120
|
+
}
|
|
5121
|
+
},
|
|
5122
|
+
disconnectedCallback(controller) {
|
|
5123
|
+
if (isConnected) {
|
|
5124
|
+
isConnected = false;
|
|
5125
|
+
if (unbindables !== null) {
|
|
5126
|
+
unbindables.forEach(x => x.unbind(this));
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
},
|
|
5130
|
+
};
|
|
5131
|
+
},
|
|
5132
|
+
});
|
|
3596
5133
|
|
|
3597
5134
|
const defaultEventOptions = {
|
|
3598
5135
|
bubbles: true,
|
|
@@ -3605,6 +5142,7 @@ function getShadowRoot(element) {
|
|
|
3605
5142
|
var _a, _b;
|
|
3606
5143
|
return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
|
|
3607
5144
|
}
|
|
5145
|
+
let elementControllerStrategy;
|
|
3608
5146
|
/**
|
|
3609
5147
|
* Controls the lifecycle and rendering of a `FASTElement`.
|
|
3610
5148
|
* @public
|
|
@@ -3623,8 +5161,19 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3623
5161
|
this.needsInitialization = true;
|
|
3624
5162
|
this.hasExistingShadowRoot = false;
|
|
3625
5163
|
this._template = null;
|
|
3626
|
-
this.
|
|
5164
|
+
this.stage = 3 /* Stages.disconnected */;
|
|
5165
|
+
/**
|
|
5166
|
+
* A guard against connecting behaviors multiple times
|
|
5167
|
+
* during connect in scenarios where a behavior adds
|
|
5168
|
+
* another behavior during it's connectedCallback
|
|
5169
|
+
*/
|
|
5170
|
+
this.guardBehaviorConnection = false;
|
|
3627
5171
|
this.behaviors = null;
|
|
5172
|
+
/**
|
|
5173
|
+
* Tracks whether behaviors are connected so that
|
|
5174
|
+
* behaviors cant be connected multiple times
|
|
5175
|
+
*/
|
|
5176
|
+
this.behaviorsConnected = false;
|
|
3628
5177
|
this._mainStyles = null;
|
|
3629
5178
|
/**
|
|
3630
5179
|
* This allows Observable.getNotifier(...) to return the Controller
|
|
@@ -3679,11 +5228,28 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3679
5228
|
*/
|
|
3680
5229
|
get isConnected() {
|
|
3681
5230
|
Observable.track(this, isConnectedPropertyName);
|
|
3682
|
-
return this.
|
|
5231
|
+
return this.stage === 1 /* Stages.connected */;
|
|
3683
5232
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
5233
|
+
/**
|
|
5234
|
+
* The context the expression is evaluated against.
|
|
5235
|
+
*/
|
|
5236
|
+
get context() {
|
|
5237
|
+
var _a, _b;
|
|
5238
|
+
return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
|
|
5239
|
+
}
|
|
5240
|
+
/**
|
|
5241
|
+
* Indicates whether the controller is bound.
|
|
5242
|
+
*/
|
|
5243
|
+
get isBound() {
|
|
5244
|
+
var _a, _b;
|
|
5245
|
+
return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
|
|
5246
|
+
}
|
|
5247
|
+
/**
|
|
5248
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
5249
|
+
*/
|
|
5250
|
+
get sourceLifetime() {
|
|
5251
|
+
var _a;
|
|
5252
|
+
return (_a = this.view) === null || _a === void 0 ? void 0 : _a.sourceLifetime;
|
|
3687
5253
|
}
|
|
3688
5254
|
/**
|
|
3689
5255
|
* Gets/sets the template used to render the component.
|
|
@@ -3747,6 +5313,14 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3747
5313
|
this.addStyles(value);
|
|
3748
5314
|
}
|
|
3749
5315
|
}
|
|
5316
|
+
/**
|
|
5317
|
+
* Registers an unbind handler with the controller.
|
|
5318
|
+
* @param behavior - An object to call when the controller unbinds.
|
|
5319
|
+
*/
|
|
5320
|
+
onUnbind(behavior) {
|
|
5321
|
+
var _a;
|
|
5322
|
+
(_a = this.view) === null || _a === void 0 ? void 0 : _a.onUnbind(behavior);
|
|
5323
|
+
}
|
|
3750
5324
|
/**
|
|
3751
5325
|
* Adds the behavior to the component.
|
|
3752
5326
|
* @param behavior - The behavior to add.
|
|
@@ -3758,7 +5332,9 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3758
5332
|
if (count === 0) {
|
|
3759
5333
|
targetBehaviors.set(behavior, 1);
|
|
3760
5334
|
behavior.addedCallback && behavior.addedCallback(this);
|
|
3761
|
-
if (behavior.connectedCallback &&
|
|
5335
|
+
if (behavior.connectedCallback &&
|
|
5336
|
+
!this.guardBehaviorConnection &&
|
|
5337
|
+
(this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
|
|
3762
5338
|
behavior.connectedCallback(this);
|
|
3763
5339
|
}
|
|
3764
5340
|
}
|
|
@@ -3782,7 +5358,7 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3782
5358
|
}
|
|
3783
5359
|
if (count === 1 || force) {
|
|
3784
5360
|
targetBehaviors.delete(behavior);
|
|
3785
|
-
if (behavior.disconnectedCallback && this.
|
|
5361
|
+
if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
|
|
3786
5362
|
behavior.disconnectedCallback(this);
|
|
3787
5363
|
}
|
|
3788
5364
|
behavior.removedCallback && behavior.removedCallback(this);
|
|
@@ -3801,13 +5377,13 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3801
5377
|
return;
|
|
3802
5378
|
}
|
|
3803
5379
|
const source = this.source;
|
|
3804
|
-
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3805
5380
|
if (styles instanceof HTMLElement) {
|
|
5381
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
|
|
3806
5382
|
target.append(styles);
|
|
3807
5383
|
}
|
|
3808
|
-
else if (!styles.isAttachedTo(
|
|
5384
|
+
else if (!styles.isAttachedTo(source)) {
|
|
3809
5385
|
const sourceBehaviors = styles.behaviors;
|
|
3810
|
-
styles.addStylesTo(
|
|
5386
|
+
styles.addStylesTo(source);
|
|
3811
5387
|
if (sourceBehaviors !== null) {
|
|
3812
5388
|
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3813
5389
|
this.addBehavior(sourceBehaviors[i]);
|
|
@@ -3825,16 +5401,16 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3825
5401
|
return;
|
|
3826
5402
|
}
|
|
3827
5403
|
const source = this.source;
|
|
3828
|
-
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3829
5404
|
if (styles instanceof HTMLElement) {
|
|
5405
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
|
|
3830
5406
|
target.removeChild(styles);
|
|
3831
5407
|
}
|
|
3832
|
-
else if (styles.isAttachedTo(
|
|
5408
|
+
else if (styles.isAttachedTo(source)) {
|
|
3833
5409
|
const sourceBehaviors = styles.behaviors;
|
|
3834
|
-
styles.removeStylesFrom(
|
|
5410
|
+
styles.removeStylesFrom(source);
|
|
3835
5411
|
if (sourceBehaviors !== null) {
|
|
3836
5412
|
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3837
|
-
this.
|
|
5413
|
+
this.removeBehavior(sourceBehaviors[i]);
|
|
3838
5414
|
}
|
|
3839
5415
|
}
|
|
3840
5416
|
}
|
|
@@ -3843,40 +5419,73 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3843
5419
|
* Runs connected lifecycle behavior on the associated element.
|
|
3844
5420
|
*/
|
|
3845
5421
|
connect() {
|
|
3846
|
-
if (this.
|
|
5422
|
+
if (this.stage !== 3 /* Stages.disconnected */) {
|
|
3847
5423
|
return;
|
|
3848
5424
|
}
|
|
5425
|
+
this.stage = 0 /* Stages.connecting */;
|
|
5426
|
+
this.bindObservables();
|
|
5427
|
+
this.connectBehaviors();
|
|
3849
5428
|
if (this.needsInitialization) {
|
|
3850
|
-
this.
|
|
5429
|
+
this.renderTemplate(this.template);
|
|
5430
|
+
this.addStyles(this.mainStyles);
|
|
5431
|
+
this.needsInitialization = false;
|
|
3851
5432
|
}
|
|
3852
5433
|
else if (this.view !== null) {
|
|
3853
5434
|
this.view.bind(this.source);
|
|
3854
5435
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
5436
|
+
this.stage = 1 /* Stages.connected */;
|
|
5437
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
5438
|
+
}
|
|
5439
|
+
bindObservables() {
|
|
5440
|
+
if (this.boundObservables !== null) {
|
|
5441
|
+
const element = this.source;
|
|
5442
|
+
const boundObservables = this.boundObservables;
|
|
5443
|
+
const propertyNames = Object.keys(boundObservables);
|
|
5444
|
+
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
5445
|
+
const propertyName = propertyNames[i];
|
|
5446
|
+
element[propertyName] = boundObservables[propertyName];
|
|
5447
|
+
}
|
|
5448
|
+
this.boundObservables = null;
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
connectBehaviors() {
|
|
5452
|
+
if (this.behaviorsConnected === false) {
|
|
5453
|
+
const behaviors = this.behaviors;
|
|
5454
|
+
if (behaviors !== null) {
|
|
5455
|
+
this.guardBehaviorConnection = true;
|
|
5456
|
+
for (const key of behaviors.keys()) {
|
|
5457
|
+
key.connectedCallback && key.connectedCallback(this);
|
|
5458
|
+
}
|
|
5459
|
+
this.guardBehaviorConnection = false;
|
|
5460
|
+
}
|
|
5461
|
+
this.behaviorsConnected = true;
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5464
|
+
disconnectBehaviors() {
|
|
5465
|
+
if (this.behaviorsConnected === true) {
|
|
5466
|
+
const behaviors = this.behaviors;
|
|
5467
|
+
if (behaviors !== null) {
|
|
5468
|
+
for (const key of behaviors.keys()) {
|
|
5469
|
+
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
5470
|
+
}
|
|
3859
5471
|
}
|
|
5472
|
+
this.behaviorsConnected = false;
|
|
3860
5473
|
}
|
|
3861
|
-
this.setIsConnected(true);
|
|
3862
5474
|
}
|
|
3863
5475
|
/**
|
|
3864
5476
|
* Runs disconnected lifecycle behavior on the associated element.
|
|
3865
5477
|
*/
|
|
3866
5478
|
disconnect() {
|
|
3867
|
-
if (
|
|
5479
|
+
if (this.stage !== 1 /* Stages.connected */) {
|
|
3868
5480
|
return;
|
|
3869
5481
|
}
|
|
3870
|
-
this.
|
|
5482
|
+
this.stage = 2 /* Stages.disconnecting */;
|
|
5483
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
3871
5484
|
if (this.view !== null) {
|
|
3872
5485
|
this.view.unbind();
|
|
3873
5486
|
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
for (const key of behaviors.keys()) {
|
|
3877
|
-
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
5487
|
+
this.disconnectBehaviors();
|
|
5488
|
+
this.stage = 3 /* Stages.disconnected */;
|
|
3880
5489
|
}
|
|
3881
5490
|
/**
|
|
3882
5491
|
* Runs the attribute changed callback for the associated element.
|
|
@@ -3899,27 +5508,11 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3899
5508
|
* Only emits events if connected.
|
|
3900
5509
|
*/
|
|
3901
5510
|
emit(type, detail, options) {
|
|
3902
|
-
if (this.
|
|
5511
|
+
if (this.stage === 1 /* Stages.connected */) {
|
|
3903
5512
|
return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
|
|
3904
5513
|
}
|
|
3905
5514
|
return false;
|
|
3906
5515
|
}
|
|
3907
|
-
finishInitialization() {
|
|
3908
|
-
const element = this.source;
|
|
3909
|
-
const boundObservables = this.boundObservables;
|
|
3910
|
-
// If we have any observables that were bound, re-apply their values.
|
|
3911
|
-
if (boundObservables !== null) {
|
|
3912
|
-
const propertyNames = Object.keys(boundObservables);
|
|
3913
|
-
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
3914
|
-
const propertyName = propertyNames[i];
|
|
3915
|
-
element[propertyName] = boundObservables[propertyName];
|
|
3916
|
-
}
|
|
3917
|
-
this.boundObservables = null;
|
|
3918
|
-
}
|
|
3919
|
-
this.renderTemplate(this.template);
|
|
3920
|
-
this.addStyles(this.mainStyles);
|
|
3921
|
-
this.needsInitialization = false;
|
|
3922
|
-
}
|
|
3923
5516
|
renderTemplate(template) {
|
|
3924
5517
|
var _a;
|
|
3925
5518
|
// When getting the host to render to, we start by looking
|
|
@@ -3963,13 +5556,224 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3963
5556
|
if (definition === void 0) {
|
|
3964
5557
|
throw FAST.error(1401 /* Message.missingElementDefinition */);
|
|
3965
5558
|
}
|
|
3966
|
-
return (element.$fastController = new
|
|
5559
|
+
return (element.$fastController = new elementControllerStrategy(element, definition));
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Sets the strategy that ElementController.forCustomElement uses to construct
|
|
5563
|
+
* ElementController instances for an element.
|
|
5564
|
+
* @param strategy - The strategy to use.
|
|
5565
|
+
*/
|
|
5566
|
+
static setStrategy(strategy) {
|
|
5567
|
+
elementControllerStrategy = strategy;
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
makeSerializationNoop(ElementController);
|
|
5571
|
+
// Set default strategy for ElementController
|
|
5572
|
+
ElementController.setStrategy(ElementController);
|
|
5573
|
+
/**
|
|
5574
|
+
* Converts a styleTarget into the operative target. When the provided target is an Element
|
|
5575
|
+
* that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
|
|
5576
|
+
* it will return the root node for the element.
|
|
5577
|
+
* @param target
|
|
5578
|
+
* @returns
|
|
5579
|
+
*/
|
|
5580
|
+
function normalizeStyleTarget(target) {
|
|
5581
|
+
var _a;
|
|
5582
|
+
if ("adoptedStyleSheets" in target) {
|
|
5583
|
+
return target;
|
|
5584
|
+
}
|
|
5585
|
+
else {
|
|
5586
|
+
return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
// Default StyleStrategy implementations are defined in this module because they
|
|
5590
|
+
// require access to element shadowRoots, and we don't want to leak shadowRoot
|
|
5591
|
+
// objects out of this module.
|
|
5592
|
+
/**
|
|
5593
|
+
* https://wicg.github.io/construct-stylesheets/
|
|
5594
|
+
* https://developers.google.com/web/updates/2019/02/constructable-stylesheets
|
|
5595
|
+
*
|
|
5596
|
+
* @internal
|
|
5597
|
+
*/
|
|
5598
|
+
class AdoptedStyleSheetsStrategy {
|
|
5599
|
+
constructor(styles) {
|
|
5600
|
+
const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
|
|
5601
|
+
this.sheets = styles.map((x) => {
|
|
5602
|
+
if (x instanceof CSSStyleSheet) {
|
|
5603
|
+
return x;
|
|
5604
|
+
}
|
|
5605
|
+
let sheet = styleSheetCache.get(x);
|
|
5606
|
+
if (sheet === void 0) {
|
|
5607
|
+
sheet = new CSSStyleSheet();
|
|
5608
|
+
sheet.replaceSync(x);
|
|
5609
|
+
styleSheetCache.set(x, sheet);
|
|
5610
|
+
}
|
|
5611
|
+
return sheet;
|
|
5612
|
+
});
|
|
5613
|
+
}
|
|
5614
|
+
addStylesTo(target) {
|
|
5615
|
+
addAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
|
|
5616
|
+
}
|
|
5617
|
+
removeStylesFrom(target) {
|
|
5618
|
+
removeAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
|
|
5622
|
+
let id = 0;
|
|
5623
|
+
const nextStyleId = () => `fast-${++id}`;
|
|
5624
|
+
function usableStyleTarget(target) {
|
|
5625
|
+
return target === document ? document.body : target;
|
|
5626
|
+
}
|
|
5627
|
+
/**
|
|
5628
|
+
* @internal
|
|
5629
|
+
*/
|
|
5630
|
+
class StyleElementStrategy {
|
|
5631
|
+
constructor(styles) {
|
|
5632
|
+
this.styles = styles;
|
|
5633
|
+
this.styleClass = nextStyleId();
|
|
5634
|
+
}
|
|
5635
|
+
addStylesTo(target) {
|
|
5636
|
+
target = usableStyleTarget(normalizeStyleTarget(target));
|
|
5637
|
+
const styles = this.styles;
|
|
5638
|
+
const styleClass = this.styleClass;
|
|
5639
|
+
for (let i = 0; i < styles.length; i++) {
|
|
5640
|
+
const element = document.createElement("style");
|
|
5641
|
+
element.innerHTML = styles[i];
|
|
5642
|
+
element.className = styleClass;
|
|
5643
|
+
target.append(element);
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
removeStylesFrom(target) {
|
|
5647
|
+
target = usableStyleTarget(normalizeStyleTarget(target));
|
|
5648
|
+
const styles = target.querySelectorAll(`.${this.styleClass}`);
|
|
5649
|
+
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
|
5650
|
+
target.removeChild(styles[i]);
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
let addAdoptedStyleSheets = (target, sheets) => {
|
|
5655
|
+
target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...sheets];
|
|
5656
|
+
};
|
|
5657
|
+
let removeAdoptedStyleSheets = (target, sheets) => {
|
|
5658
|
+
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
|
|
5659
|
+
};
|
|
5660
|
+
if (ElementStyles.supportsAdoptedStyleSheets) {
|
|
5661
|
+
try {
|
|
5662
|
+
// Test if browser implementation uses FrozenArray.
|
|
5663
|
+
// If not, use push / splice to alter the stylesheets
|
|
5664
|
+
// in place. This circumvents a bug in Safari 16.4 where
|
|
5665
|
+
// periodically, assigning the array would previously
|
|
5666
|
+
// cause sheets to be removed.
|
|
5667
|
+
document.adoptedStyleSheets.push();
|
|
5668
|
+
document.adoptedStyleSheets.splice();
|
|
5669
|
+
addAdoptedStyleSheets = (target, sheets) => {
|
|
5670
|
+
target.adoptedStyleSheets.push(...sheets);
|
|
5671
|
+
};
|
|
5672
|
+
removeAdoptedStyleSheets = (target, sheets) => {
|
|
5673
|
+
for (const sheet of sheets) {
|
|
5674
|
+
const index = target.adoptedStyleSheets.indexOf(sheet);
|
|
5675
|
+
if (index !== -1) {
|
|
5676
|
+
target.adoptedStyleSheets.splice(index, 1);
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
};
|
|
5680
|
+
}
|
|
5681
|
+
catch (e) {
|
|
5682
|
+
// Do nothing if an error is thrown, the default
|
|
5683
|
+
// case handles FrozenArray.
|
|
5684
|
+
}
|
|
5685
|
+
ElementStyles.setDefaultStrategy(AdoptedStyleSheetsStrategy);
|
|
5686
|
+
}
|
|
5687
|
+
else {
|
|
5688
|
+
ElementStyles.setDefaultStrategy(StyleElementStrategy);
|
|
5689
|
+
}
|
|
5690
|
+
const deferHydrationAttribute = "defer-hydration";
|
|
5691
|
+
const needsHydrationAttribute = "needs-hydration";
|
|
5692
|
+
/**
|
|
5693
|
+
* An ElementController capable of hydrating FAST elements from
|
|
5694
|
+
* Declarative Shadow DOM.
|
|
5695
|
+
*
|
|
5696
|
+
* @beta
|
|
5697
|
+
*/
|
|
5698
|
+
class HydratableElementController extends ElementController {
|
|
5699
|
+
static hydrationObserverHandler(records) {
|
|
5700
|
+
for (const record of records) {
|
|
5701
|
+
HydratableElementController.hydrationObserver.unobserve(record.target);
|
|
5702
|
+
record.target.$fastController.connect();
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
connect() {
|
|
5706
|
+
var _a, _b;
|
|
5707
|
+
// Initialize needsHydration on first connect
|
|
5708
|
+
if (this.needsHydration === undefined) {
|
|
5709
|
+
this.needsHydration =
|
|
5710
|
+
this.source.getAttribute(needsHydrationAttribute) !== null;
|
|
5711
|
+
}
|
|
5712
|
+
// If the `defer-hydration` attribute exists on the source,
|
|
5713
|
+
// wait for it to be removed before continuing connection behavior.
|
|
5714
|
+
if (this.source.hasAttribute(deferHydrationAttribute)) {
|
|
5715
|
+
HydratableElementController.hydrationObserver.observe(this.source, {
|
|
5716
|
+
attributeFilter: [deferHydrationAttribute],
|
|
5717
|
+
});
|
|
5718
|
+
return;
|
|
5719
|
+
}
|
|
5720
|
+
// If the controller does not need to be hydrated, defer connection behavior
|
|
5721
|
+
// to the base-class. This case handles element re-connection and initial connection
|
|
5722
|
+
// of elements that did not get declarative shadow-dom emitted, as well as if an extending
|
|
5723
|
+
// class
|
|
5724
|
+
if (!this.needsHydration) {
|
|
5725
|
+
super.connect();
|
|
5726
|
+
return;
|
|
5727
|
+
}
|
|
5728
|
+
if (this.stage !== 3 /* Stages.disconnected */) {
|
|
5729
|
+
return;
|
|
5730
|
+
}
|
|
5731
|
+
this.stage = 0 /* Stages.connecting */;
|
|
5732
|
+
this.bindObservables();
|
|
5733
|
+
this.connectBehaviors();
|
|
5734
|
+
const element = this.source;
|
|
5735
|
+
const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
|
|
5736
|
+
if (this.template) {
|
|
5737
|
+
if (isHydratable(this.template)) {
|
|
5738
|
+
let firstChild = host.firstChild;
|
|
5739
|
+
let lastChild = host.lastChild;
|
|
5740
|
+
if (element.shadowRoot === null) {
|
|
5741
|
+
// handle element boundary markers when shadowRoot is not present
|
|
5742
|
+
if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
|
|
5743
|
+
firstChild.data = "";
|
|
5744
|
+
firstChild = firstChild.nextSibling;
|
|
5745
|
+
}
|
|
5746
|
+
if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
|
|
5747
|
+
lastChild.data = "";
|
|
5748
|
+
lastChild = lastChild.previousSibling;
|
|
5749
|
+
}
|
|
5750
|
+
}
|
|
5751
|
+
this.view = this.template.hydrate(firstChild, lastChild, element);
|
|
5752
|
+
(_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
|
|
5753
|
+
}
|
|
5754
|
+
else {
|
|
5755
|
+
this.renderTemplate(this.template);
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
this.addStyles(this.mainStyles);
|
|
5759
|
+
this.stage = 1 /* Stages.connected */;
|
|
5760
|
+
this.source.removeAttribute(needsHydrationAttribute);
|
|
5761
|
+
this.needsInitialization = this.needsHydration = false;
|
|
5762
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
5763
|
+
}
|
|
5764
|
+
disconnect() {
|
|
5765
|
+
super.disconnect();
|
|
5766
|
+
HydratableElementController.hydrationObserver.unobserve(this.source);
|
|
5767
|
+
}
|
|
5768
|
+
static install() {
|
|
5769
|
+
ElementController.setStrategy(HydratableElementController);
|
|
3967
5770
|
}
|
|
3968
5771
|
}
|
|
5772
|
+
HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
|
|
3969
5773
|
|
|
3970
5774
|
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
3971
5775
|
function createFASTElement(BaseType) {
|
|
3972
|
-
|
|
5776
|
+
const type = class extends BaseType {
|
|
3973
5777
|
constructor() {
|
|
3974
5778
|
/* eslint-disable-next-line */
|
|
3975
5779
|
super();
|
|
@@ -3988,6 +5792,8 @@ function createFASTElement(BaseType) {
|
|
|
3988
5792
|
this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
|
|
3989
5793
|
}
|
|
3990
5794
|
};
|
|
5795
|
+
FASTElementDefinition.registerBaseType(type);
|
|
5796
|
+
return type;
|
|
3991
5797
|
}
|
|
3992
5798
|
function compose(type, nameOrDef) {
|
|
3993
5799
|
if (isFunction(type)) {
|
|
@@ -4042,4 +5848,6 @@ function customElement(nameOrDef) {
|
|
|
4042
5848
|
};
|
|
4043
5849
|
}
|
|
4044
5850
|
|
|
4045
|
-
|
|
5851
|
+
DOM.setPolicy(DOMPolicy.create());
|
|
5852
|
+
|
|
5853
|
+
export { ArrayObserver, AttributeConfiguration, AttributeDefinition, Binding, CSSBindingDirective, CSSDirective, ChildrenDirective, Compiler, DOM, DOMAspect, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, HydratableElementController, HydrationBindingError, InlineTemplateDirective, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RenderBehavior, RenderDirective, RepeatBehavior, RepeatDirective, SlottedDirective, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewTemplate, attr, booleanConverter, children, css, cssDirective, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding$1 as normalizeBinding, nullableBooleanConverter, nullableNumberConverter, observable, oneTime, oneWay, ref, render, repeat, slotted, volatile, when };
|