@microsoft/fast-element 2.0.0-beta.8 → 2.0.0
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 +512 -0
- package/CHANGELOG.md +180 -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 +6 -0
- 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} +22 -42
- 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 +149 -26
- 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 +16 -1
- 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 +259 -32
- 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 +9804 -5622
- package/dist/fast-element.d.ts +813 -2386
- package/dist/fast-element.debug.js +2797 -974
- package/dist/fast-element.debug.min.js +3 -1
- package/dist/fast-element.js +2642 -825
- package/dist/fast-element.min.js +3 -1
- package/dist/fast-element.untrimmed.d.ts +669 -315
- package/docs/{api-report.md → api-report.api.md} +243 -158
- 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,317 +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
|
|
2574
|
+
* Moves TreeWalker to element boundary end marker
|
|
2575
|
+
* @param node - element boundary start marker node
|
|
2576
|
+
* @param walker - tree walker
|
|
2154
2577
|
*/
|
|
2155
|
-
function
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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
|
|
2173
|
-
*/
|
|
2174
|
-
function listener(binding, options) {
|
|
2175
|
-
const config = new OnChangeBinding(binding, false);
|
|
2176
|
-
config.options = options;
|
|
2177
|
-
return config;
|
|
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
|
-
this.isBound = false;
|
|
2225
|
-
this.selfContained = false;
|
|
2614
|
+
class DefaultExecutionContext {
|
|
2615
|
+
constructor() {
|
|
2226
2616
|
/**
|
|
2227
2617
|
* The index of the current item within a repeat context.
|
|
2228
2618
|
*/
|
|
@@ -2231,14 +2621,6 @@ class HTMLView {
|
|
|
2231
2621
|
* The length of the current collection within a repeat context.
|
|
2232
2622
|
*/
|
|
2233
2623
|
this.length = 0;
|
|
2234
|
-
this.firstChild = fragment.firstChild;
|
|
2235
|
-
this.lastChild = fragment.lastChild;
|
|
2236
|
-
}
|
|
2237
|
-
/**
|
|
2238
|
-
* The execution context the view is running within.
|
|
2239
|
-
*/
|
|
2240
|
-
get context() {
|
|
2241
|
-
return this;
|
|
2242
2624
|
}
|
|
2243
2625
|
/**
|
|
2244
2626
|
* The current event within an event handler.
|
|
@@ -2293,6 +2675,43 @@ class HTMLView {
|
|
|
2293
2675
|
eventTarget() {
|
|
2294
2676
|
return this.event.target;
|
|
2295
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
|
+
}
|
|
2296
2715
|
/**
|
|
2297
2716
|
* Appends the view's DOM nodes to the referenced node.
|
|
2298
2717
|
* @param node - The parent node to append the view's DOM nodes to.
|
|
@@ -2355,13 +2774,14 @@ class HTMLView {
|
|
|
2355
2774
|
* @param source - The binding source for the view's binding behaviors.
|
|
2356
2775
|
* @param context - The execution context to run the behaviors within.
|
|
2357
2776
|
*/
|
|
2358
|
-
bind(source) {
|
|
2777
|
+
bind(source, context = this) {
|
|
2359
2778
|
if (this.source === source) {
|
|
2360
2779
|
return;
|
|
2361
2780
|
}
|
|
2362
2781
|
let behaviors = this.behaviors;
|
|
2363
2782
|
if (behaviors === null) {
|
|
2364
2783
|
this.source = source;
|
|
2784
|
+
this.context = context;
|
|
2365
2785
|
this.behaviors = behaviors = new Array(this.factories.length);
|
|
2366
2786
|
const factories = this.factories;
|
|
2367
2787
|
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
@@ -2376,23 +2796,246 @@ class HTMLView {
|
|
|
2376
2796
|
}
|
|
2377
2797
|
this.isBound = false;
|
|
2378
2798
|
this.source = source;
|
|
2799
|
+
this.context = context;
|
|
2800
|
+
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
2801
|
+
behaviors[i].bind(this);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
this.isBound = true;
|
|
2805
|
+
}
|
|
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;
|
|
2379
3012
|
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
2380
3013
|
behaviors[i].bind(this);
|
|
2381
3014
|
}
|
|
2382
3015
|
}
|
|
2383
3016
|
this.isBound = true;
|
|
3017
|
+
this._hydrationStage = HydrationStage.hydrated;
|
|
2384
3018
|
}
|
|
2385
|
-
/**
|
|
2386
|
-
* Unbinds a view's behaviors from its binding source.
|
|
2387
|
-
*/
|
|
2388
3019
|
unbind() {
|
|
2389
3020
|
if (!this.isBound || this.source === null) {
|
|
2390
3021
|
return;
|
|
2391
3022
|
}
|
|
2392
3023
|
this.evaluateUnbindables();
|
|
2393
3024
|
this.source = null;
|
|
3025
|
+
this.context = this;
|
|
2394
3026
|
this.isBound = false;
|
|
2395
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
|
+
}
|
|
2396
3039
|
evaluateUnbindables() {
|
|
2397
3040
|
const unbindables = this.unbindables;
|
|
2398
3041
|
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
@@ -2400,22 +3043,220 @@ class HTMLView {
|
|
|
2400
3043
|
}
|
|
2401
3044
|
unbindables.length = 0;
|
|
2402
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 {
|
|
2403
3166
|
/**
|
|
2404
|
-
*
|
|
2405
|
-
* @param
|
|
3167
|
+
* Creates an instance of HTMLBindingDirective.
|
|
3168
|
+
* @param dataBinding - The binding configuration to apply.
|
|
2406
3169
|
*/
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
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);
|
|
2410
3198
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
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;
|
|
3238
|
+
}
|
|
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
|
+
}
|
|
2414
3250
|
}
|
|
2415
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
|
+
}
|
|
2416
3258
|
}
|
|
2417
|
-
|
|
2418
|
-
Observable.defineProperty(HTMLView.prototype, "length");
|
|
3259
|
+
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
2419
3260
|
|
|
2420
3261
|
const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
|
|
2421
3262
|
const descriptorCache = {};
|
|
@@ -2441,20 +3282,25 @@ const warningHost = new Proxy(document.createElement("div"), {
|
|
|
2441
3282
|
},
|
|
2442
3283
|
});
|
|
2443
3284
|
class CompilationContext {
|
|
2444
|
-
constructor(fragment, directives) {
|
|
3285
|
+
constructor(fragment, directives, policy) {
|
|
2445
3286
|
this.fragment = fragment;
|
|
2446
3287
|
this.directives = directives;
|
|
3288
|
+
this.policy = policy;
|
|
2447
3289
|
this.proto = null;
|
|
2448
3290
|
this.nodeIds = new Set();
|
|
2449
3291
|
this.descriptors = {};
|
|
2450
3292
|
this.factories = [];
|
|
2451
3293
|
}
|
|
2452
|
-
addFactory(factory, parentId, nodeId, targetIndex) {
|
|
3294
|
+
addFactory(factory, parentId, nodeId, targetIndex, tagName) {
|
|
3295
|
+
var _a, _b;
|
|
2453
3296
|
if (!this.nodeIds.has(nodeId)) {
|
|
2454
3297
|
this.nodeIds.add(nodeId);
|
|
2455
3298
|
this.addTargetDescriptor(parentId, nodeId, targetIndex);
|
|
2456
3299
|
}
|
|
2457
|
-
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;
|
|
2458
3304
|
this.factories.push(factory);
|
|
2459
3305
|
}
|
|
2460
3306
|
freeze() {
|
|
@@ -2507,19 +3353,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
|
|
|
2507
3353
|
let result = null;
|
|
2508
3354
|
if (parseResult === null) {
|
|
2509
3355
|
if (includeBasicValues) {
|
|
2510
|
-
result = new HTMLBindingDirective(oneTime(() => attrValue));
|
|
2511
|
-
|
|
3356
|
+
result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
|
|
3357
|
+
HTMLDirective.assignAspect(result, attr.name);
|
|
2512
3358
|
}
|
|
2513
3359
|
}
|
|
2514
3360
|
else {
|
|
2515
3361
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2516
|
-
result = Compiler.aggregate(parseResult);
|
|
3362
|
+
result = Compiler.aggregate(parseResult, context.policy);
|
|
2517
3363
|
}
|
|
2518
3364
|
if (result !== null) {
|
|
2519
3365
|
node.removeAttributeNode(attr);
|
|
2520
3366
|
i--;
|
|
2521
3367
|
ii--;
|
|
2522
|
-
context.addFactory(result, parentId, nodeId, nodeIndex);
|
|
3368
|
+
context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
|
|
2523
3369
|
}
|
|
2524
3370
|
}
|
|
2525
3371
|
}
|
|
@@ -2544,8 +3390,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
|
|
|
2544
3390
|
}
|
|
2545
3391
|
else {
|
|
2546
3392
|
currentNode.textContent = " ";
|
|
2547
|
-
|
|
2548
|
-
context.addFactory(currentPart, parentId, nodeId, nodeIndex);
|
|
3393
|
+
HTMLDirective.assignAspect(currentPart);
|
|
3394
|
+
context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
|
|
2549
3395
|
}
|
|
2550
3396
|
lastNode = currentNode;
|
|
2551
3397
|
}
|
|
@@ -2577,7 +3423,7 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
2577
3423
|
if (parts !== null) {
|
|
2578
3424
|
context.addFactory(
|
|
2579
3425
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2580
|
-
Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
|
|
3426
|
+
Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
|
|
2581
3427
|
}
|
|
2582
3428
|
break;
|
|
2583
3429
|
}
|
|
@@ -2591,45 +3437,28 @@ function isMarker(node, directives) {
|
|
|
2591
3437
|
Parser.parse(node.data, directives) !== null);
|
|
2592
3438
|
}
|
|
2593
3439
|
const templateTag = "TEMPLATE";
|
|
2594
|
-
const policyOptions = { createHTML: html => html };
|
|
2595
|
-
let htmlPolicy = globalThis.trustedTypes
|
|
2596
|
-
? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
|
|
2597
|
-
: policyOptions;
|
|
2598
|
-
const fastHTMLPolicy = htmlPolicy;
|
|
2599
3440
|
/**
|
|
2600
3441
|
* Common APIs related to compilation.
|
|
2601
3442
|
* @public
|
|
2602
3443
|
*/
|
|
2603
3444
|
const Compiler = {
|
|
2604
|
-
/**
|
|
2605
|
-
* Sets the HTML trusted types policy used by the compiler.
|
|
2606
|
-
* @param policy - The policy to set for HTML.
|
|
2607
|
-
* @remarks
|
|
2608
|
-
* This API can only be called once, for security reasons. It should be
|
|
2609
|
-
* called by the application developer at the start of their program.
|
|
2610
|
-
*/
|
|
2611
|
-
setHTMLPolicy(policy) {
|
|
2612
|
-
if (htmlPolicy !== fastHTMLPolicy) {
|
|
2613
|
-
throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
|
|
2614
|
-
}
|
|
2615
|
-
htmlPolicy = policy;
|
|
2616
|
-
},
|
|
2617
3445
|
/**
|
|
2618
3446
|
* Compiles a template and associated directives into a compilation
|
|
2619
3447
|
* result which can be used to create views.
|
|
2620
3448
|
* @param html - The html string or template element to compile.
|
|
2621
|
-
* @param
|
|
3449
|
+
* @param factories - The behavior factories referenced by the template.
|
|
3450
|
+
* @param policy - The security policy to compile the html with.
|
|
2622
3451
|
* @remarks
|
|
2623
3452
|
* The template that is provided for compilation is altered in-place
|
|
2624
3453
|
* and cannot be compiled again. If the original template must be preserved,
|
|
2625
3454
|
* it is recommended that you clone the original and pass the clone to this API.
|
|
2626
3455
|
* @public
|
|
2627
3456
|
*/
|
|
2628
|
-
compile(html,
|
|
3457
|
+
compile(html, factories, policy = DOM.policy) {
|
|
2629
3458
|
let template;
|
|
2630
3459
|
if (isString(html)) {
|
|
2631
3460
|
template = document.createElement(templateTag);
|
|
2632
|
-
template.innerHTML =
|
|
3461
|
+
template.innerHTML = policy.createHTML(html);
|
|
2633
3462
|
const fec = template.content.firstElementChild;
|
|
2634
3463
|
if (fec !== null && fec.tagName === templateTag) {
|
|
2635
3464
|
template = fec;
|
|
@@ -2638,20 +3467,23 @@ const Compiler = {
|
|
|
2638
3467
|
else {
|
|
2639
3468
|
template = html;
|
|
2640
3469
|
}
|
|
3470
|
+
if (!template.content.firstChild && !template.content.lastChild) {
|
|
3471
|
+
template.content.appendChild(document.createComment(""));
|
|
3472
|
+
}
|
|
2641
3473
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
|
|
2642
3474
|
const fragment = document.adoptNode(template.content);
|
|
2643
|
-
const context = new CompilationContext(fragment,
|
|
3475
|
+
const context = new CompilationContext(fragment, factories, policy);
|
|
2644
3476
|
compileAttributes(context, "", template, /* host */ "h", 0, true);
|
|
2645
3477
|
if (
|
|
2646
3478
|
// If the first node in a fragment is a marker, that means it's an unstable first node,
|
|
2647
3479
|
// because something like a when, repeat, etc. could add nodes before the marker.
|
|
2648
3480
|
// To mitigate this, we insert a stable first node. However, if we insert a node,
|
|
2649
3481
|
// that will alter the result of the TreeWalker. So, we also need to offset the target index.
|
|
2650
|
-
isMarker(fragment.firstChild,
|
|
3482
|
+
isMarker(fragment.firstChild, factories) ||
|
|
2651
3483
|
// Or if there is only one node and a directive, it means the template's content
|
|
2652
3484
|
// is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
|
|
2653
3485
|
// the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
|
|
2654
|
-
(fragment.childNodes.length === 1 && Object.keys(
|
|
3486
|
+
(fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
|
|
2655
3487
|
fragment.insertBefore(document.createComment(""), fragment.firstChild);
|
|
2656
3488
|
}
|
|
2657
3489
|
compileChildren(context, fragment, /* root */ "r");
|
|
@@ -2670,23 +3502,24 @@ const Compiler = {
|
|
|
2670
3502
|
* Aggregates an array of strings and directives into a single directive.
|
|
2671
3503
|
* @param parts - A heterogeneous array of static strings interspersed with
|
|
2672
3504
|
* directives.
|
|
3505
|
+
* @param policy - The security policy to use with the aggregated bindings.
|
|
2673
3506
|
* @returns A single inline directive that aggregates the behavior of all the parts.
|
|
2674
3507
|
*/
|
|
2675
|
-
aggregate(parts) {
|
|
3508
|
+
aggregate(parts, policy = DOM.policy) {
|
|
2676
3509
|
if (parts.length === 1) {
|
|
2677
3510
|
return parts[0];
|
|
2678
3511
|
}
|
|
2679
3512
|
let sourceAspect;
|
|
2680
|
-
let binding;
|
|
2681
3513
|
let isVolatile = false;
|
|
3514
|
+
let bindingPolicy = void 0;
|
|
2682
3515
|
const partCount = parts.length;
|
|
2683
3516
|
const finalParts = parts.map((x) => {
|
|
2684
3517
|
if (isString(x)) {
|
|
2685
3518
|
return () => x;
|
|
2686
3519
|
}
|
|
2687
3520
|
sourceAspect = x.sourceAspect || sourceAspect;
|
|
2688
|
-
binding = x.dataBinding || binding;
|
|
2689
3521
|
isVolatile = isVolatile || x.dataBinding.isVolatile;
|
|
3522
|
+
bindingPolicy = bindingPolicy || x.dataBinding.policy;
|
|
2690
3523
|
return x.dataBinding.evaluate;
|
|
2691
3524
|
});
|
|
2692
3525
|
const expression = (scope, context) => {
|
|
@@ -2696,14 +3529,56 @@ const Compiler = {
|
|
|
2696
3529
|
}
|
|
2697
3530
|
return output;
|
|
2698
3531
|
};
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
const directive = new HTMLBindingDirective(binding);
|
|
2702
|
-
Aspect.assign(directive, sourceAspect);
|
|
3532
|
+
const directive = new HTMLBindingDirective(oneWay(expression, bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy, isVolatile));
|
|
3533
|
+
HTMLDirective.assignAspect(directive, sourceAspect);
|
|
2703
3534
|
return directive;
|
|
2704
3535
|
},
|
|
2705
3536
|
};
|
|
2706
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
|
+
}
|
|
2707
3582
|
/**
|
|
2708
3583
|
* A template capable of creating HTMLView instances or rendering directly to DOM.
|
|
2709
3584
|
* @public
|
|
@@ -2713,21 +3588,53 @@ class ViewTemplate {
|
|
|
2713
3588
|
* Creates an instance of ViewTemplate.
|
|
2714
3589
|
* @param html - The html representing what this template will instantiate, including placeholders for directives.
|
|
2715
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.
|
|
2716
3592
|
*/
|
|
2717
|
-
constructor(html, factories) {
|
|
3593
|
+
constructor(html, factories = {}, policy) {
|
|
3594
|
+
this.policy = policy;
|
|
2718
3595
|
this.result = null;
|
|
2719
3596
|
this.html = html;
|
|
2720
3597
|
this.factories = factories;
|
|
2721
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
|
+
}
|
|
2722
3608
|
/**
|
|
2723
3609
|
* Creates an HTMLView instance based on this template definition.
|
|
2724
3610
|
* @param hostBindingTarget - The element that host behaviors will be bound to.
|
|
2725
3611
|
*/
|
|
2726
3612
|
create(hostBindingTarget) {
|
|
2727
|
-
|
|
2728
|
-
|
|
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 */);
|
|
2729
3632
|
}
|
|
2730
|
-
|
|
3633
|
+
if (this.policy) {
|
|
3634
|
+
throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
|
|
3635
|
+
}
|
|
3636
|
+
this.policy = policy;
|
|
3637
|
+
return this;
|
|
2731
3638
|
}
|
|
2732
3639
|
/**
|
|
2733
3640
|
* Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
|
|
@@ -2742,18 +3649,49 @@ class ViewTemplate {
|
|
|
2742
3649
|
view.appendTo(host);
|
|
2743
3650
|
return view;
|
|
2744
3651
|
}
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
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);
|
|
2754
3692
|
}
|
|
2755
|
-
return value.createHTML(add);
|
|
2756
3693
|
}
|
|
3694
|
+
makeSerializationNoop(ViewTemplate);
|
|
2757
3695
|
/**
|
|
2758
3696
|
* Transforms a template literal string into a ViewTemplate.
|
|
2759
3697
|
* @param strings - The string fragments that are interpolated with the values.
|
|
@@ -2763,51 +3701,15 @@ function createAspectedHTML(value, prevString, add) {
|
|
|
2763
3701
|
* other template instances, and Directive instances.
|
|
2764
3702
|
* @public
|
|
2765
3703
|
*/
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
const add = (factory) => {
|
|
2770
|
-
var _a;
|
|
2771
|
-
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
2772
|
-
factories[id] = factory;
|
|
2773
|
-
return id;
|
|
2774
|
-
};
|
|
2775
|
-
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
2776
|
-
const currentString = strings[i];
|
|
2777
|
-
const currentValue = values[i];
|
|
2778
|
-
let definition;
|
|
2779
|
-
html += currentString;
|
|
2780
|
-
if (isFunction(currentValue)) {
|
|
2781
|
-
html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
|
|
2782
|
-
}
|
|
2783
|
-
else if (isString(currentValue)) {
|
|
2784
|
-
const match = lastAttributeNameRegex.exec(currentString);
|
|
2785
|
-
if (match !== null) {
|
|
2786
|
-
const directive = new HTMLBindingDirective(oneTime(() => currentValue));
|
|
2787
|
-
Aspect.assign(directive, match[2]);
|
|
2788
|
-
html += directive.createHTML(add);
|
|
2789
|
-
}
|
|
2790
|
-
else {
|
|
2791
|
-
html += currentValue;
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
else if (currentValue instanceof Binding) {
|
|
2795
|
-
html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
|
|
2796
|
-
}
|
|
2797
|
-
else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
|
|
2798
|
-
html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
|
|
2799
|
-
}
|
|
2800
|
-
else {
|
|
2801
|
-
if (definition.aspected) {
|
|
2802
|
-
html += createAspectedHTML(currentValue, currentString, add);
|
|
2803
|
-
}
|
|
2804
|
-
else {
|
|
2805
|
-
html += currentValue.createHTML(add);
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
3704
|
+
const html = ((strings, ...values) => {
|
|
3705
|
+
if (Array.isArray(strings) && Array.isArray(strings.raw)) {
|
|
3706
|
+
return ViewTemplate.create(strings, values);
|
|
2808
3707
|
}
|
|
2809
|
-
|
|
2810
|
-
}
|
|
3708
|
+
throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
|
|
3709
|
+
});
|
|
3710
|
+
html.partial = (html) => {
|
|
3711
|
+
return new InlineTemplateDirective(html);
|
|
3712
|
+
};
|
|
2811
3713
|
|
|
2812
3714
|
/**
|
|
2813
3715
|
* The runtime behavior for template references.
|
|
@@ -2819,7 +3721,7 @@ class RefDirective extends StatelessAttachedAttributeDirective {
|
|
|
2819
3721
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2820
3722
|
*/
|
|
2821
3723
|
bind(controller) {
|
|
2822
|
-
controller.source[this.options] = controller.targets[this.
|
|
3724
|
+
controller.source[this.options] = controller.targets[this.targetNodeId];
|
|
2823
3725
|
}
|
|
2824
3726
|
}
|
|
2825
3727
|
HTMLDirective.define(RefDirective);
|
|
@@ -2830,19 +3732,26 @@ HTMLDirective.define(RefDirective);
|
|
|
2830
3732
|
*/
|
|
2831
3733
|
const ref = (propertyName) => new RefDirective(propertyName);
|
|
2832
3734
|
|
|
3735
|
+
const noTemplate = () => null;
|
|
3736
|
+
function normalizeBinding(value) {
|
|
3737
|
+
return value === undefined ? noTemplate : isFunction(value) ? value : () => value;
|
|
3738
|
+
}
|
|
2833
3739
|
/**
|
|
2834
3740
|
* A directive that enables basic conditional rendering in a template.
|
|
2835
3741
|
* @param condition - The condition to test for rendering.
|
|
2836
3742
|
* @param templateOrTemplateBinding - The template or a binding that gets
|
|
2837
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.
|
|
2838
3746
|
* @public
|
|
2839
3747
|
*/
|
|
2840
|
-
function when(condition, templateOrTemplateBinding) {
|
|
3748
|
+
function when(condition, templateOrTemplateBinding, elseTemplateOrTemplateBinding) {
|
|
2841
3749
|
const dataBinding = isFunction(condition) ? condition : () => condition;
|
|
2842
|
-
const templateBinding =
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
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);
|
|
2846
3755
|
}
|
|
2847
3756
|
|
|
2848
3757
|
const defaultRepeatOptions = Object.freeze({
|
|
@@ -2861,6 +3770,19 @@ function bindWithPositioning(view, items, index, controller) {
|
|
|
2861
3770
|
view.context.index = index;
|
|
2862
3771
|
view.bind(items[index]);
|
|
2863
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
|
+
}
|
|
2864
3786
|
/**
|
|
2865
3787
|
* A behavior that renders a template for each item in an array.
|
|
2866
3788
|
* @public
|
|
@@ -2877,12 +3799,13 @@ class RepeatBehavior {
|
|
|
2877
3799
|
*/
|
|
2878
3800
|
constructor(directive) {
|
|
2879
3801
|
this.directive = directive;
|
|
2880
|
-
this.views = [];
|
|
2881
3802
|
this.items = null;
|
|
2882
3803
|
this.itemsObserver = null;
|
|
2883
3804
|
this.bindView = bindWithoutPositioning;
|
|
2884
|
-
|
|
2885
|
-
this.
|
|
3805
|
+
/** @internal */
|
|
3806
|
+
this.views = [];
|
|
3807
|
+
this.itemsBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
3808
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
2886
3809
|
if (directive.options.positioning) {
|
|
2887
3810
|
this.bindView = bindWithPositioning;
|
|
2888
3811
|
}
|
|
@@ -2892,12 +3815,19 @@ class RepeatBehavior {
|
|
|
2892
3815
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2893
3816
|
*/
|
|
2894
3817
|
bind(controller) {
|
|
2895
|
-
this.location = controller.targets[this.directive.
|
|
3818
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
2896
3819
|
this.controller = controller;
|
|
2897
3820
|
this.items = this.itemsBindingObserver.bind(controller);
|
|
2898
3821
|
this.template = this.templateBindingObserver.bind(controller);
|
|
2899
3822
|
this.observeItems(true);
|
|
2900
|
-
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
|
+
}
|
|
2901
3831
|
controller.onUnbind(this);
|
|
2902
3832
|
}
|
|
2903
3833
|
/**
|
|
@@ -2998,10 +3928,10 @@ class RepeatBehavior {
|
|
|
2998
3928
|
leftoverViews[i].dispose();
|
|
2999
3929
|
}
|
|
3000
3930
|
if (this.directive.options.positioning) {
|
|
3001
|
-
for (let i = 0,
|
|
3931
|
+
for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
|
|
3002
3932
|
const context = views[i].context;
|
|
3003
|
-
context.length =
|
|
3004
|
-
context.index =
|
|
3933
|
+
context.length = viewsLength;
|
|
3934
|
+
context.index = i;
|
|
3005
3935
|
}
|
|
3006
3936
|
}
|
|
3007
3937
|
}
|
|
@@ -3035,6 +3965,18 @@ class RepeatBehavior {
|
|
|
3035
3965
|
for (; i < itemsLength; ++i) {
|
|
3036
3966
|
if (i < viewsLength) {
|
|
3037
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
|
+
}
|
|
3038
3980
|
bindView(view, items, i, controller);
|
|
3039
3981
|
}
|
|
3040
3982
|
else {
|
|
@@ -3053,7 +3995,76 @@ class RepeatBehavior {
|
|
|
3053
3995
|
unbindAllViews() {
|
|
3054
3996
|
const views = this.views;
|
|
3055
3997
|
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
3056
|
-
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
|
+
}
|
|
3057
4068
|
}
|
|
3058
4069
|
}
|
|
3059
4070
|
}
|
|
@@ -3072,10 +4083,6 @@ class RepeatDirective {
|
|
|
3072
4083
|
this.dataBinding = dataBinding;
|
|
3073
4084
|
this.templateBinding = templateBinding;
|
|
3074
4085
|
this.options = options;
|
|
3075
|
-
/**
|
|
3076
|
-
* The unique id of the factory.
|
|
3077
|
-
*/
|
|
3078
|
-
this.id = nextId();
|
|
3079
4086
|
ArrayObserver.enable();
|
|
3080
4087
|
}
|
|
3081
4088
|
/**
|
|
@@ -3103,8 +4110,8 @@ HTMLDirective.define(RepeatDirective);
|
|
|
3103
4110
|
* @public
|
|
3104
4111
|
*/
|
|
3105
4112
|
function repeat(items, template, options = defaultRepeatOptions) {
|
|
3106
|
-
const dataBinding = normalizeBinding(items);
|
|
3107
|
-
const templateBinding = normalizeBinding(template);
|
|
4113
|
+
const dataBinding = normalizeBinding$1(items);
|
|
4114
|
+
const templateBinding = normalizeBinding$1(template);
|
|
3108
4115
|
return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
|
|
3109
4116
|
}
|
|
3110
4117
|
|
|
@@ -3124,9 +4131,15 @@ const elements = (selector) => selector
|
|
|
3124
4131
|
* Internally used by the SlottedDirective and the ChildrenDirective.
|
|
3125
4132
|
*/
|
|
3126
4133
|
class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
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`;
|
|
3130
4143
|
}
|
|
3131
4144
|
/**
|
|
3132
4145
|
* Bind this behavior to the source.
|
|
@@ -3135,8 +4148,8 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3135
4148
|
* @param targets - The targets that behaviors in a view can attach to.
|
|
3136
4149
|
*/
|
|
3137
4150
|
bind(controller) {
|
|
3138
|
-
const target = controller.targets[this.
|
|
3139
|
-
target[this.
|
|
4151
|
+
const target = controller.targets[this.targetNodeId];
|
|
4152
|
+
target[this._controllerProperty] = controller;
|
|
3140
4153
|
this.updateTarget(controller.source, this.computeNodes(target));
|
|
3141
4154
|
this.observe(target);
|
|
3142
4155
|
controller.onUnbind(this);
|
|
@@ -3148,10 +4161,10 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3148
4161
|
* @param targets - The targets that behaviors in a view can attach to.
|
|
3149
4162
|
*/
|
|
3150
4163
|
unbind(controller) {
|
|
3151
|
-
const target = controller.targets[this.
|
|
4164
|
+
const target = controller.targets[this.targetNodeId];
|
|
3152
4165
|
this.updateTarget(controller.source, emptyArray);
|
|
3153
4166
|
this.disconnect(target);
|
|
3154
|
-
target[this.
|
|
4167
|
+
target[this._controllerProperty] = null;
|
|
3155
4168
|
}
|
|
3156
4169
|
/**
|
|
3157
4170
|
* Gets the data source for the target.
|
|
@@ -3159,7 +4172,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3159
4172
|
* @returns The source.
|
|
3160
4173
|
*/
|
|
3161
4174
|
getSource(target) {
|
|
3162
|
-
return target[this.
|
|
4175
|
+
return target[this._controllerProperty].source;
|
|
3163
4176
|
}
|
|
3164
4177
|
/**
|
|
3165
4178
|
* Updates the source property with the computed nodes.
|
|
@@ -3243,7 +4256,7 @@ class ChildrenDirective extends NodeObservationDirective {
|
|
|
3243
4256
|
*/
|
|
3244
4257
|
constructor(options) {
|
|
3245
4258
|
super(options);
|
|
3246
|
-
this.observerProperty =
|
|
4259
|
+
this.observerProperty = Symbol();
|
|
3247
4260
|
this.handleEvent = (mutations, observer) => {
|
|
3248
4261
|
const target = observer.target;
|
|
3249
4262
|
this.updateTarget(this.getSource(target), this.computeNodes(target));
|
|
@@ -3255,8 +4268,12 @@ class ChildrenDirective extends NodeObservationDirective {
|
|
|
3255
4268
|
* @param target - The target to observe.
|
|
3256
4269
|
*/
|
|
3257
4270
|
observe(target) {
|
|
3258
|
-
|
|
3259
|
-
|
|
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
|
+
}
|
|
3260
4277
|
observer.target = target;
|
|
3261
4278
|
observer.observe(target, this.options);
|
|
3262
4279
|
}
|
|
@@ -3328,6 +4345,21 @@ const booleanConverter = {
|
|
|
3328
4345
|
: true;
|
|
3329
4346
|
},
|
|
3330
4347
|
};
|
|
4348
|
+
/**
|
|
4349
|
+
* A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`,
|
|
4350
|
+
* and `void` values are converted to `null`.
|
|
4351
|
+
* @public
|
|
4352
|
+
*/
|
|
4353
|
+
const nullableBooleanConverter = {
|
|
4354
|
+
toView(value) {
|
|
4355
|
+
return typeof value === "boolean" ? value.toString() : "";
|
|
4356
|
+
},
|
|
4357
|
+
fromView(value) {
|
|
4358
|
+
return [null, undefined, void 0].includes(value)
|
|
4359
|
+
? null
|
|
4360
|
+
: booleanConverter.fromView(value);
|
|
4361
|
+
},
|
|
4362
|
+
};
|
|
3331
4363
|
function toNumber(value) {
|
|
3332
4364
|
if (value === null || value === undefined) {
|
|
3333
4365
|
return null;
|
|
@@ -3493,7 +4525,8 @@ function attr(configOrTarget, prop) {
|
|
|
3493
4525
|
|
|
3494
4526
|
const defaultShadowOptions = { mode: "open" };
|
|
3495
4527
|
const defaultElementOptions = {};
|
|
3496
|
-
const
|
|
4528
|
+
const fastElementBaseTypes = new Set();
|
|
4529
|
+
const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
|
|
3497
4530
|
/**
|
|
3498
4531
|
* Defines metadata for a FASTElement.
|
|
3499
4532
|
* @public
|
|
@@ -3541,51 +4574,564 @@ class FASTElementDefinition {
|
|
|
3541
4574
|
this.styles = ElementStyles.normalize(nameOrConfig.styles);
|
|
3542
4575
|
fastElementRegistry.register(this);
|
|
3543
4576
|
}
|
|
3544
|
-
/**
|
|
3545
|
-
* Indicates if this element has been defined in at least one registry.
|
|
3546
|
-
*/
|
|
3547
|
-
get isDefined() {
|
|
3548
|
-
return this.platformDefined;
|
|
4577
|
+
/**
|
|
4578
|
+
* Indicates if this element has been defined in at least one registry.
|
|
4579
|
+
*/
|
|
4580
|
+
get isDefined() {
|
|
4581
|
+
return this.platformDefined;
|
|
4582
|
+
}
|
|
4583
|
+
/**
|
|
4584
|
+
* Defines a custom element based on this definition.
|
|
4585
|
+
* @param registry - The element registry to define the element in.
|
|
4586
|
+
* @remarks
|
|
4587
|
+
* This operation is idempotent per registry.
|
|
4588
|
+
*/
|
|
4589
|
+
define(registry = this.registry) {
|
|
4590
|
+
const type = this.type;
|
|
4591
|
+
if (!registry.get(this.name)) {
|
|
4592
|
+
this.platformDefined = true;
|
|
4593
|
+
registry.define(this.name, type, this.elementOptions);
|
|
4594
|
+
}
|
|
4595
|
+
return this;
|
|
4596
|
+
}
|
|
4597
|
+
/**
|
|
4598
|
+
* Creates an instance of FASTElementDefinition.
|
|
4599
|
+
* @param type - The type this definition is being created for.
|
|
4600
|
+
* @param nameOrDef - The name of the element to define or a config object
|
|
4601
|
+
* that describes the element to define.
|
|
4602
|
+
*/
|
|
4603
|
+
static compose(type, nameOrDef) {
|
|
4604
|
+
if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
|
|
4605
|
+
return new FASTElementDefinition(class extends type {
|
|
4606
|
+
}, nameOrDef);
|
|
4607
|
+
}
|
|
4608
|
+
return new FASTElementDefinition(type, nameOrDef);
|
|
4609
|
+
}
|
|
4610
|
+
/**
|
|
4611
|
+
* Registers a FASTElement base type.
|
|
4612
|
+
* @param type - The type to register as a base type.
|
|
4613
|
+
* @internal
|
|
4614
|
+
*/
|
|
4615
|
+
static registerBaseType(type) {
|
|
4616
|
+
fastElementBaseTypes.add(type);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
/**
|
|
4620
|
+
* Gets the element definition associated with the specified type.
|
|
4621
|
+
* @param type - The custom element type to retrieve the definition for.
|
|
4622
|
+
*/
|
|
4623
|
+
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
4624
|
+
/**
|
|
4625
|
+
* Gets the element definition associated with the instance.
|
|
4626
|
+
* @param instance - The custom element instance to retrieve the definition for.
|
|
4627
|
+
*/
|
|
4628
|
+
FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
|
|
4629
|
+
|
|
4630
|
+
/**
|
|
4631
|
+
* A Behavior that enables advanced rendering.
|
|
4632
|
+
* @public
|
|
4633
|
+
*/
|
|
4634
|
+
class RenderBehavior {
|
|
4635
|
+
/**
|
|
4636
|
+
* Creates an instance of RenderBehavior.
|
|
4637
|
+
* @param directive - The render directive that created this behavior.
|
|
4638
|
+
*/
|
|
4639
|
+
constructor(directive) {
|
|
4640
|
+
this.directive = directive;
|
|
4641
|
+
this.location = null;
|
|
4642
|
+
this.controller = null;
|
|
4643
|
+
this.view = null;
|
|
4644
|
+
this.data = null;
|
|
4645
|
+
this.dataBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
4646
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
4647
|
+
}
|
|
4648
|
+
/**
|
|
4649
|
+
* Bind this behavior.
|
|
4650
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
4651
|
+
*/
|
|
4652
|
+
bind(controller) {
|
|
4653
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
4654
|
+
this.controller = controller;
|
|
4655
|
+
this.data = this.dataBindingObserver.bind(controller);
|
|
4656
|
+
this.template = this.templateBindingObserver.bind(controller);
|
|
4657
|
+
controller.onUnbind(this);
|
|
4658
|
+
if (isHydratable(this.template) &&
|
|
4659
|
+
isHydratable(controller) &&
|
|
4660
|
+
controller.hydrationStage !== HydrationStage.hydrated &&
|
|
4661
|
+
!this.view) {
|
|
4662
|
+
const viewNodes = controller.bindingViewBoundaries[this.directive.targetNodeId];
|
|
4663
|
+
if (viewNodes) {
|
|
4664
|
+
this.view = this.template.hydrate(viewNodes.first, viewNodes.last);
|
|
4665
|
+
this.bindView(this.view);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
else {
|
|
4669
|
+
this.refreshView();
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
/**
|
|
4673
|
+
* Unbinds this behavior.
|
|
4674
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
4675
|
+
*/
|
|
4676
|
+
unbind(controller) {
|
|
4677
|
+
const view = this.view;
|
|
4678
|
+
if (view !== null && view.isComposed) {
|
|
4679
|
+
view.unbind();
|
|
4680
|
+
view.needsBindOnly = true;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
/** @internal */
|
|
4684
|
+
handleChange(source, observer) {
|
|
4685
|
+
if (observer === this.dataBindingObserver) {
|
|
4686
|
+
this.data = this.dataBindingObserver.bind(this.controller);
|
|
4687
|
+
}
|
|
4688
|
+
if (this.directive.templateBindingDependsOnData ||
|
|
4689
|
+
observer === this.templateBindingObserver) {
|
|
4690
|
+
this.template = this.templateBindingObserver.bind(this.controller);
|
|
4691
|
+
}
|
|
4692
|
+
this.refreshView();
|
|
4693
|
+
}
|
|
4694
|
+
bindView(view) {
|
|
4695
|
+
// It's possible that the value is the same as the previous template
|
|
4696
|
+
// and that there's actually no need to compose it.
|
|
4697
|
+
if (!view.isComposed) {
|
|
4698
|
+
view.isComposed = true;
|
|
4699
|
+
view.bind(this.data);
|
|
4700
|
+
view.insertBefore(this.location);
|
|
4701
|
+
view.$fastTemplate = this.template;
|
|
4702
|
+
}
|
|
4703
|
+
else if (view.needsBindOnly) {
|
|
4704
|
+
view.needsBindOnly = false;
|
|
4705
|
+
view.bind(this.data);
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
refreshView() {
|
|
4709
|
+
let view = this.view;
|
|
4710
|
+
const template = this.template;
|
|
4711
|
+
if (view === null) {
|
|
4712
|
+
this.view = view = template.create();
|
|
4713
|
+
this.view.context.parent = this.controller.source;
|
|
4714
|
+
this.view.context.parentContext = this.controller.context;
|
|
4715
|
+
}
|
|
4716
|
+
else {
|
|
4717
|
+
// If there is a previous view, but it wasn't created
|
|
4718
|
+
// from the same template as the new value, then we
|
|
4719
|
+
// need to remove the old view if it's still in the DOM
|
|
4720
|
+
// and create a new view from the template.
|
|
4721
|
+
if (view.$fastTemplate !== template) {
|
|
4722
|
+
if (view.isComposed) {
|
|
4723
|
+
view.remove();
|
|
4724
|
+
view.unbind();
|
|
4725
|
+
}
|
|
4726
|
+
this.view = view = template.create();
|
|
4727
|
+
this.view.context.parent = this.controller.source;
|
|
4728
|
+
this.view.context.parentContext = this.controller.context;
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
this.bindView(view);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
/**
|
|
4735
|
+
* A Directive that enables use of the RenderBehavior.
|
|
4736
|
+
* @public
|
|
4737
|
+
*/
|
|
4738
|
+
class RenderDirective {
|
|
4739
|
+
/**
|
|
4740
|
+
* Creates an instance of RenderDirective.
|
|
4741
|
+
* @param dataBinding - A binding expression that returns the data to render.
|
|
4742
|
+
* @param templateBinding - A binding expression that returns the template to use to render the data.
|
|
4743
|
+
*/
|
|
4744
|
+
constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
|
|
4745
|
+
this.dataBinding = dataBinding;
|
|
4746
|
+
this.templateBinding = templateBinding;
|
|
4747
|
+
this.templateBindingDependsOnData = templateBindingDependsOnData;
|
|
4748
|
+
}
|
|
4749
|
+
/**
|
|
4750
|
+
* Creates HTML to be used within a template.
|
|
4751
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
4752
|
+
*/
|
|
4753
|
+
createHTML(add) {
|
|
4754
|
+
return Markup.comment(add(this));
|
|
4755
|
+
}
|
|
4756
|
+
/**
|
|
4757
|
+
* Creates a behavior.
|
|
4758
|
+
* @param targets - The targets available for behaviors to be attached to.
|
|
4759
|
+
*/
|
|
4760
|
+
createBehavior() {
|
|
4761
|
+
return new RenderBehavior(this);
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
HTMLDirective.define(RenderDirective);
|
|
4765
|
+
function isElementRenderOptions(object) {
|
|
4766
|
+
return !!object.element || !!object.tagName;
|
|
4767
|
+
}
|
|
4768
|
+
const typeToInstructionLookup = new Map();
|
|
4769
|
+
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
4770
|
+
const defaultAttributes = { ":model": (x) => x };
|
|
4771
|
+
const brand = Symbol("RenderInstruction");
|
|
4772
|
+
const defaultViewName = "default-view";
|
|
4773
|
+
const nullTemplate = html `
|
|
4774
|
+
|
|
4775
|
+
`;
|
|
4776
|
+
function instructionToTemplate(def) {
|
|
4777
|
+
if (def === void 0) {
|
|
4778
|
+
return nullTemplate;
|
|
4779
|
+
}
|
|
4780
|
+
return def.template;
|
|
4781
|
+
}
|
|
4782
|
+
function createElementTemplate(tagName, options) {
|
|
4783
|
+
const markup = [];
|
|
4784
|
+
const values = [];
|
|
4785
|
+
const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
|
|
4786
|
+
markup.push(`<${tagName}`);
|
|
4787
|
+
if (attributes) {
|
|
4788
|
+
const attrNames = Object.getOwnPropertyNames(attributes);
|
|
4789
|
+
for (let i = 0, ii = attrNames.length; i < ii; ++i) {
|
|
4790
|
+
const name = attrNames[i];
|
|
4791
|
+
if (i === 0) {
|
|
4792
|
+
markup[0] = `${markup[0]} ${name}="`;
|
|
4793
|
+
}
|
|
4794
|
+
else {
|
|
4795
|
+
markup.push(`" ${name}="`);
|
|
4796
|
+
}
|
|
4797
|
+
values.push(attributes[name]);
|
|
4798
|
+
}
|
|
4799
|
+
markup.push(`"`);
|
|
4800
|
+
}
|
|
4801
|
+
if (directives) {
|
|
4802
|
+
markup[markup.length - 1] += " ";
|
|
4803
|
+
for (let i = 0, ii = directives.length; i < ii; ++i) {
|
|
4804
|
+
const directive = directives[i];
|
|
4805
|
+
markup.push(i > 0 ? "" : " ");
|
|
4806
|
+
values.push(directive);
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
markup[markup.length - 1] += ">";
|
|
4810
|
+
if (content && isFunction(content.create)) {
|
|
4811
|
+
values.push(content);
|
|
4812
|
+
markup.push(`</${tagName}>`);
|
|
4813
|
+
}
|
|
4814
|
+
else {
|
|
4815
|
+
const lastIndex = markup.length - 1;
|
|
4816
|
+
markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
|
|
4817
|
+
}
|
|
4818
|
+
return ViewTemplate.create(markup, values, policy);
|
|
4819
|
+
}
|
|
4820
|
+
function create(options) {
|
|
4821
|
+
var _a;
|
|
4822
|
+
const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
|
|
4823
|
+
let template;
|
|
4824
|
+
if (isElementRenderOptions(options)) {
|
|
4825
|
+
let tagName = options.tagName;
|
|
4826
|
+
if (!tagName) {
|
|
4827
|
+
const def = FASTElementDefinition.getByType(options.element);
|
|
4828
|
+
if (def) {
|
|
4829
|
+
tagName = def.name;
|
|
4830
|
+
}
|
|
4831
|
+
else {
|
|
4832
|
+
throw new Error("Invalid element for model rendering.");
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
if (!options.attributes) {
|
|
4836
|
+
options.attributes = defaultAttributes;
|
|
4837
|
+
}
|
|
4838
|
+
template = createElementTemplate(tagName, options);
|
|
4839
|
+
}
|
|
4840
|
+
else {
|
|
4841
|
+
template = options.template;
|
|
4842
|
+
}
|
|
4843
|
+
return {
|
|
4844
|
+
brand,
|
|
4845
|
+
type: options.type,
|
|
4846
|
+
name,
|
|
4847
|
+
template,
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
function instanceOf(object) {
|
|
4851
|
+
return object && object.brand === brand;
|
|
4852
|
+
}
|
|
4853
|
+
function register(optionsOrInstruction) {
|
|
4854
|
+
let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
|
|
4855
|
+
if (lookup === void 0) {
|
|
4856
|
+
typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
|
|
4857
|
+
}
|
|
4858
|
+
const instruction = instanceOf(optionsOrInstruction)
|
|
4859
|
+
? optionsOrInstruction
|
|
4860
|
+
: create(optionsOrInstruction);
|
|
4861
|
+
return (lookup[instruction.name] = instruction);
|
|
4862
|
+
}
|
|
4863
|
+
function getByType(type, name) {
|
|
4864
|
+
const entries = typeToInstructionLookup.get(type);
|
|
4865
|
+
if (entries === void 0) {
|
|
4866
|
+
return void 0;
|
|
4867
|
+
}
|
|
4868
|
+
return entries[name !== null && name !== void 0 ? name : defaultViewName];
|
|
4869
|
+
}
|
|
4870
|
+
function getForInstance(object, name) {
|
|
4871
|
+
if (object) {
|
|
4872
|
+
return getByType(object.constructor, name);
|
|
4873
|
+
}
|
|
4874
|
+
return void 0;
|
|
4875
|
+
}
|
|
4876
|
+
/**
|
|
4877
|
+
* Provides APIs for creating and interacting with render instructions.
|
|
4878
|
+
* @public
|
|
4879
|
+
*/
|
|
4880
|
+
Object.freeze({
|
|
4881
|
+
/**
|
|
4882
|
+
* Checks whether the provided object is a RenderInstruction.
|
|
4883
|
+
* @param object - The object to check.
|
|
4884
|
+
* @returns true if the object is a RenderInstruction; false otherwise
|
|
4885
|
+
*/
|
|
4886
|
+
instanceOf,
|
|
4887
|
+
/**
|
|
4888
|
+
* Creates a RenderInstruction for a set of options.
|
|
4889
|
+
* @param options - The options to use when creating the RenderInstruction.
|
|
4890
|
+
* @remarks
|
|
4891
|
+
* This API should be used with caution. When providing attributes or content,
|
|
4892
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
4893
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
4894
|
+
* content and attribute values particularly if they can come from user input.
|
|
4895
|
+
*/
|
|
4896
|
+
create,
|
|
4897
|
+
/**
|
|
4898
|
+
* Creates a template based on a tag name.
|
|
4899
|
+
* @param tagName - The tag name to use when creating the template.
|
|
4900
|
+
* @param attributes - The attributes to apply to the element.
|
|
4901
|
+
* @param content - The content to insert into the element.
|
|
4902
|
+
* @param policy - The DOMPolicy to create the template with.
|
|
4903
|
+
* @returns A template based on the provided specifications.
|
|
4904
|
+
* @remarks
|
|
4905
|
+
* This API should be used with caution. When providing attributes or content,
|
|
4906
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
4907
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
4908
|
+
* content and attribute values particularly if they can come from user input.
|
|
4909
|
+
*/
|
|
4910
|
+
createElementTemplate,
|
|
4911
|
+
/**
|
|
4912
|
+
* Creates and registers an instruction.
|
|
4913
|
+
* @param options The options to use when creating the RenderInstruction.
|
|
4914
|
+
* @remarks
|
|
4915
|
+
* A previously created RenderInstruction can also be registered.
|
|
4916
|
+
*/
|
|
4917
|
+
register,
|
|
4918
|
+
/**
|
|
4919
|
+
* Finds a previously registered RenderInstruction by type and optionally by name.
|
|
4920
|
+
* @param type - The type to retrieve the RenderInstruction for.
|
|
4921
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
4922
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
4923
|
+
*/
|
|
4924
|
+
getByType,
|
|
4925
|
+
/**
|
|
4926
|
+
* Finds a previously registered RenderInstruction for the instance's type and optionally by name.
|
|
4927
|
+
* @param object - The instance to retrieve the RenderInstruction for.
|
|
4928
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
4929
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
4930
|
+
*/
|
|
4931
|
+
getForInstance,
|
|
4932
|
+
});
|
|
4933
|
+
/**
|
|
4934
|
+
* @internal
|
|
4935
|
+
*/
|
|
4936
|
+
class NodeTemplate {
|
|
4937
|
+
constructor(node) {
|
|
4938
|
+
this.node = node;
|
|
4939
|
+
node.$fastTemplate = this;
|
|
4940
|
+
}
|
|
4941
|
+
get context() {
|
|
4942
|
+
// HACK
|
|
4943
|
+
return this;
|
|
4944
|
+
}
|
|
4945
|
+
bind(source) { }
|
|
4946
|
+
unbind() { }
|
|
4947
|
+
insertBefore(refNode) {
|
|
4948
|
+
refNode.parentNode.insertBefore(this.node, refNode);
|
|
4949
|
+
}
|
|
4950
|
+
remove() {
|
|
4951
|
+
this.node.parentNode.removeChild(this.node);
|
|
4952
|
+
}
|
|
4953
|
+
create() {
|
|
4954
|
+
return this;
|
|
4955
|
+
}
|
|
4956
|
+
hydrate(first, last) {
|
|
4957
|
+
return this;
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
/**
|
|
4961
|
+
* Creates a RenderDirective for use in advanced rendering scenarios.
|
|
4962
|
+
* @param value - The binding expression that returns the data to be rendered. The expression
|
|
4963
|
+
* can also return a Node to render directly.
|
|
4964
|
+
* @param template - A template to render the data with
|
|
4965
|
+
* or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
|
|
4966
|
+
* Expressions can also be provided to dynamically determine either the template or the name.
|
|
4967
|
+
* @returns A RenderDirective suitable for use in a template.
|
|
4968
|
+
* @remarks
|
|
4969
|
+
* If no binding is provided, then a default binding that returns the source is created.
|
|
4970
|
+
* If no template is provided, then a binding is created that will use registered
|
|
4971
|
+
* RenderInstructions to determine the view.
|
|
4972
|
+
* If the template binding returns a string, then it will be used to look up a
|
|
4973
|
+
* RenderInstruction to determine the view.
|
|
4974
|
+
* @public
|
|
4975
|
+
*/
|
|
4976
|
+
function render(value, template) {
|
|
4977
|
+
let dataBinding;
|
|
4978
|
+
if (value === void 0) {
|
|
4979
|
+
dataBinding = oneTime((source) => source);
|
|
4980
|
+
}
|
|
4981
|
+
else {
|
|
4982
|
+
dataBinding = normalizeBinding$1(value);
|
|
4983
|
+
}
|
|
4984
|
+
let templateBinding;
|
|
4985
|
+
let templateBindingDependsOnData = false;
|
|
4986
|
+
if (template === void 0) {
|
|
4987
|
+
templateBindingDependsOnData = true;
|
|
4988
|
+
templateBinding = oneTime((s, c) => {
|
|
4989
|
+
var _a;
|
|
4990
|
+
const data = dataBinding.evaluate(s, c);
|
|
4991
|
+
if (data instanceof Node) {
|
|
4992
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
4993
|
+
}
|
|
4994
|
+
return instructionToTemplate(getForInstance(data));
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4997
|
+
else if (isFunction(template)) {
|
|
4998
|
+
templateBinding = oneWay((s, c) => {
|
|
4999
|
+
var _a;
|
|
5000
|
+
let result = template(s, c);
|
|
5001
|
+
if (isString(result)) {
|
|
5002
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
5003
|
+
}
|
|
5004
|
+
else if (result instanceof Node) {
|
|
5005
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
5006
|
+
}
|
|
5007
|
+
return result;
|
|
5008
|
+
}, void 0, true);
|
|
5009
|
+
}
|
|
5010
|
+
else if (isString(template)) {
|
|
5011
|
+
templateBindingDependsOnData = true;
|
|
5012
|
+
templateBinding = oneTime((s, c) => {
|
|
5013
|
+
var _a;
|
|
5014
|
+
const data = dataBinding.evaluate(s, c);
|
|
5015
|
+
if (data instanceof Node) {
|
|
5016
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
5017
|
+
}
|
|
5018
|
+
return instructionToTemplate(getForInstance(data, template));
|
|
5019
|
+
});
|
|
5020
|
+
}
|
|
5021
|
+
else if (template instanceof Binding) {
|
|
5022
|
+
const evaluateTemplate = template.evaluate;
|
|
5023
|
+
template.evaluate = (s, c) => {
|
|
5024
|
+
var _a;
|
|
5025
|
+
let result = evaluateTemplate(s, c);
|
|
5026
|
+
if (isString(result)) {
|
|
5027
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
5028
|
+
}
|
|
5029
|
+
else if (result instanceof Node) {
|
|
5030
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
5031
|
+
}
|
|
5032
|
+
return result;
|
|
5033
|
+
};
|
|
5034
|
+
templateBinding = template;
|
|
5035
|
+
}
|
|
5036
|
+
else {
|
|
5037
|
+
templateBinding = oneTime((s, c) => template);
|
|
3549
5038
|
}
|
|
5039
|
+
return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
/**
|
|
5043
|
+
* An extension of MutationObserver that supports unobserving nodes.
|
|
5044
|
+
* @internal
|
|
5045
|
+
*/
|
|
5046
|
+
class UnobservableMutationObserver extends MutationObserver {
|
|
3550
5047
|
/**
|
|
3551
|
-
*
|
|
3552
|
-
* @param
|
|
3553
|
-
* @remarks
|
|
3554
|
-
* This operation is idempotent per registry.
|
|
5048
|
+
* Creates an instance of UnobservableMutationObserver.
|
|
5049
|
+
* @param callback - The callback to invoke when observed nodes are changed.
|
|
3555
5050
|
*/
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
this.platformDefined = true;
|
|
3560
|
-
registry.define(this.name, type, this.elementOptions);
|
|
5051
|
+
constructor(callback) {
|
|
5052
|
+
function handler(mutations) {
|
|
5053
|
+
this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
|
|
3561
5054
|
}
|
|
3562
|
-
|
|
5055
|
+
super(handler);
|
|
5056
|
+
this.callback = callback;
|
|
5057
|
+
this.observedNodes = new Set();
|
|
3563
5058
|
}
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
if (found) {
|
|
3573
|
-
return new FASTElementDefinition(class extends type {
|
|
3574
|
-
}, nameOrDef);
|
|
5059
|
+
observe(target, options) {
|
|
5060
|
+
this.observedNodes.add(target);
|
|
5061
|
+
super.observe(target, options);
|
|
5062
|
+
}
|
|
5063
|
+
unobserve(target) {
|
|
5064
|
+
this.observedNodes.delete(target);
|
|
5065
|
+
if (this.observedNodes.size < 1) {
|
|
5066
|
+
this.disconnect();
|
|
3575
5067
|
}
|
|
3576
|
-
return new FASTElementDefinition(type, nameOrDef);
|
|
3577
5068
|
}
|
|
3578
5069
|
}
|
|
3579
5070
|
/**
|
|
3580
|
-
*
|
|
3581
|
-
*
|
|
3582
|
-
|
|
3583
|
-
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
3584
|
-
/**
|
|
3585
|
-
* Gets the element definition associated with the instance.
|
|
3586
|
-
* @param instance - The custom element instance to retrieve the definition for.
|
|
5071
|
+
* Bridges between ViewBehaviors and HostBehaviors, enabling a host to
|
|
5072
|
+
* control ViewBehaviors.
|
|
5073
|
+
* @public
|
|
3587
5074
|
*/
|
|
3588
|
-
|
|
5075
|
+
Object.freeze({
|
|
5076
|
+
/**
|
|
5077
|
+
* Creates a ViewBehaviorOrchestrator.
|
|
5078
|
+
* @param source - The source to to associate behaviors with.
|
|
5079
|
+
* @returns A ViewBehaviorOrchestrator.
|
|
5080
|
+
*/
|
|
5081
|
+
create(source) {
|
|
5082
|
+
const behaviors = [];
|
|
5083
|
+
const targets = {};
|
|
5084
|
+
let unbindables = null;
|
|
5085
|
+
let isConnected = false;
|
|
5086
|
+
return {
|
|
5087
|
+
source,
|
|
5088
|
+
context: ExecutionContext.default,
|
|
5089
|
+
targets,
|
|
5090
|
+
get isBound() {
|
|
5091
|
+
return isConnected;
|
|
5092
|
+
},
|
|
5093
|
+
addBehaviorFactory(factory, target) {
|
|
5094
|
+
var _a, _b, _c, _d;
|
|
5095
|
+
const compiled = factory;
|
|
5096
|
+
compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
|
|
5097
|
+
compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
|
|
5098
|
+
compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
|
|
5099
|
+
compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
|
|
5100
|
+
this.addTarget(compiled.targetNodeId, target);
|
|
5101
|
+
this.addBehavior(compiled.createBehavior());
|
|
5102
|
+
},
|
|
5103
|
+
addTarget(nodeId, target) {
|
|
5104
|
+
targets[nodeId] = target;
|
|
5105
|
+
},
|
|
5106
|
+
addBehavior(behavior) {
|
|
5107
|
+
behaviors.push(behavior);
|
|
5108
|
+
if (isConnected) {
|
|
5109
|
+
behavior.bind(this);
|
|
5110
|
+
}
|
|
5111
|
+
},
|
|
5112
|
+
onUnbind(unbindable) {
|
|
5113
|
+
if (unbindables === null) {
|
|
5114
|
+
unbindables = [];
|
|
5115
|
+
}
|
|
5116
|
+
unbindables.push(unbindable);
|
|
5117
|
+
},
|
|
5118
|
+
connectedCallback(controller) {
|
|
5119
|
+
if (!isConnected) {
|
|
5120
|
+
isConnected = true;
|
|
5121
|
+
behaviors.forEach(x => x.bind(this));
|
|
5122
|
+
}
|
|
5123
|
+
},
|
|
5124
|
+
disconnectedCallback(controller) {
|
|
5125
|
+
if (isConnected) {
|
|
5126
|
+
isConnected = false;
|
|
5127
|
+
if (unbindables !== null) {
|
|
5128
|
+
unbindables.forEach(x => x.unbind(this));
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
},
|
|
5132
|
+
};
|
|
5133
|
+
},
|
|
5134
|
+
});
|
|
3589
5135
|
|
|
3590
5136
|
const defaultEventOptions = {
|
|
3591
5137
|
bubbles: true,
|
|
@@ -3598,6 +5144,7 @@ function getShadowRoot(element) {
|
|
|
3598
5144
|
var _a, _b;
|
|
3599
5145
|
return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
|
|
3600
5146
|
}
|
|
5147
|
+
let elementControllerStrategy;
|
|
3601
5148
|
/**
|
|
3602
5149
|
* Controls the lifecycle and rendering of a `FASTElement`.
|
|
3603
5150
|
* @public
|
|
@@ -3616,8 +5163,19 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3616
5163
|
this.needsInitialization = true;
|
|
3617
5164
|
this.hasExistingShadowRoot = false;
|
|
3618
5165
|
this._template = null;
|
|
3619
|
-
this.
|
|
5166
|
+
this.stage = 3 /* Stages.disconnected */;
|
|
5167
|
+
/**
|
|
5168
|
+
* A guard against connecting behaviors multiple times
|
|
5169
|
+
* during connect in scenarios where a behavior adds
|
|
5170
|
+
* another behavior during it's connectedCallback
|
|
5171
|
+
*/
|
|
5172
|
+
this.guardBehaviorConnection = false;
|
|
3620
5173
|
this.behaviors = null;
|
|
5174
|
+
/**
|
|
5175
|
+
* Tracks whether behaviors are connected so that
|
|
5176
|
+
* behaviors cant be connected multiple times
|
|
5177
|
+
*/
|
|
5178
|
+
this.behaviorsConnected = false;
|
|
3621
5179
|
this._mainStyles = null;
|
|
3622
5180
|
/**
|
|
3623
5181
|
* This allows Observable.getNotifier(...) to return the Controller
|
|
@@ -3672,11 +5230,28 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3672
5230
|
*/
|
|
3673
5231
|
get isConnected() {
|
|
3674
5232
|
Observable.track(this, isConnectedPropertyName);
|
|
3675
|
-
return this.
|
|
5233
|
+
return this.stage === 1 /* Stages.connected */;
|
|
3676
5234
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
5235
|
+
/**
|
|
5236
|
+
* The context the expression is evaluated against.
|
|
5237
|
+
*/
|
|
5238
|
+
get context() {
|
|
5239
|
+
var _a, _b;
|
|
5240
|
+
return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
|
|
5241
|
+
}
|
|
5242
|
+
/**
|
|
5243
|
+
* Indicates whether the controller is bound.
|
|
5244
|
+
*/
|
|
5245
|
+
get isBound() {
|
|
5246
|
+
var _a, _b;
|
|
5247
|
+
return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
|
|
5248
|
+
}
|
|
5249
|
+
/**
|
|
5250
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
5251
|
+
*/
|
|
5252
|
+
get sourceLifetime() {
|
|
5253
|
+
var _a;
|
|
5254
|
+
return (_a = this.view) === null || _a === void 0 ? void 0 : _a.sourceLifetime;
|
|
3680
5255
|
}
|
|
3681
5256
|
/**
|
|
3682
5257
|
* Gets/sets the template used to render the component.
|
|
@@ -3740,6 +5315,14 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3740
5315
|
this.addStyles(value);
|
|
3741
5316
|
}
|
|
3742
5317
|
}
|
|
5318
|
+
/**
|
|
5319
|
+
* Registers an unbind handler with the controller.
|
|
5320
|
+
* @param behavior - An object to call when the controller unbinds.
|
|
5321
|
+
*/
|
|
5322
|
+
onUnbind(behavior) {
|
|
5323
|
+
var _a;
|
|
5324
|
+
(_a = this.view) === null || _a === void 0 ? void 0 : _a.onUnbind(behavior);
|
|
5325
|
+
}
|
|
3743
5326
|
/**
|
|
3744
5327
|
* Adds the behavior to the component.
|
|
3745
5328
|
* @param behavior - The behavior to add.
|
|
@@ -3751,7 +5334,9 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3751
5334
|
if (count === 0) {
|
|
3752
5335
|
targetBehaviors.set(behavior, 1);
|
|
3753
5336
|
behavior.addedCallback && behavior.addedCallback(this);
|
|
3754
|
-
if (behavior.connectedCallback &&
|
|
5337
|
+
if (behavior.connectedCallback &&
|
|
5338
|
+
!this.guardBehaviorConnection &&
|
|
5339
|
+
(this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
|
|
3755
5340
|
behavior.connectedCallback(this);
|
|
3756
5341
|
}
|
|
3757
5342
|
}
|
|
@@ -3775,7 +5360,7 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3775
5360
|
}
|
|
3776
5361
|
if (count === 1 || force) {
|
|
3777
5362
|
targetBehaviors.delete(behavior);
|
|
3778
|
-
if (behavior.disconnectedCallback && this.
|
|
5363
|
+
if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
|
|
3779
5364
|
behavior.disconnectedCallback(this);
|
|
3780
5365
|
}
|
|
3781
5366
|
behavior.removedCallback && behavior.removedCallback(this);
|
|
@@ -3794,13 +5379,13 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3794
5379
|
return;
|
|
3795
5380
|
}
|
|
3796
5381
|
const source = this.source;
|
|
3797
|
-
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3798
5382
|
if (styles instanceof HTMLElement) {
|
|
5383
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
|
|
3799
5384
|
target.append(styles);
|
|
3800
5385
|
}
|
|
3801
|
-
else if (!styles.isAttachedTo(
|
|
5386
|
+
else if (!styles.isAttachedTo(source)) {
|
|
3802
5387
|
const sourceBehaviors = styles.behaviors;
|
|
3803
|
-
styles.addStylesTo(
|
|
5388
|
+
styles.addStylesTo(source);
|
|
3804
5389
|
if (sourceBehaviors !== null) {
|
|
3805
5390
|
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3806
5391
|
this.addBehavior(sourceBehaviors[i]);
|
|
@@ -3818,16 +5403,16 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3818
5403
|
return;
|
|
3819
5404
|
}
|
|
3820
5405
|
const source = this.source;
|
|
3821
|
-
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3822
5406
|
if (styles instanceof HTMLElement) {
|
|
5407
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
|
|
3823
5408
|
target.removeChild(styles);
|
|
3824
5409
|
}
|
|
3825
|
-
else if (styles.isAttachedTo(
|
|
5410
|
+
else if (styles.isAttachedTo(source)) {
|
|
3826
5411
|
const sourceBehaviors = styles.behaviors;
|
|
3827
|
-
styles.removeStylesFrom(
|
|
5412
|
+
styles.removeStylesFrom(source);
|
|
3828
5413
|
if (sourceBehaviors !== null) {
|
|
3829
5414
|
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3830
|
-
this.
|
|
5415
|
+
this.removeBehavior(sourceBehaviors[i]);
|
|
3831
5416
|
}
|
|
3832
5417
|
}
|
|
3833
5418
|
}
|
|
@@ -3836,40 +5421,73 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3836
5421
|
* Runs connected lifecycle behavior on the associated element.
|
|
3837
5422
|
*/
|
|
3838
5423
|
connect() {
|
|
3839
|
-
if (this.
|
|
5424
|
+
if (this.stage !== 3 /* Stages.disconnected */) {
|
|
3840
5425
|
return;
|
|
3841
5426
|
}
|
|
5427
|
+
this.stage = 0 /* Stages.connecting */;
|
|
5428
|
+
this.bindObservables();
|
|
5429
|
+
this.connectBehaviors();
|
|
3842
5430
|
if (this.needsInitialization) {
|
|
3843
|
-
this.
|
|
5431
|
+
this.renderTemplate(this.template);
|
|
5432
|
+
this.addStyles(this.mainStyles);
|
|
5433
|
+
this.needsInitialization = false;
|
|
3844
5434
|
}
|
|
3845
5435
|
else if (this.view !== null) {
|
|
3846
5436
|
this.view.bind(this.source);
|
|
3847
5437
|
}
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
5438
|
+
this.stage = 1 /* Stages.connected */;
|
|
5439
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
5440
|
+
}
|
|
5441
|
+
bindObservables() {
|
|
5442
|
+
if (this.boundObservables !== null) {
|
|
5443
|
+
const element = this.source;
|
|
5444
|
+
const boundObservables = this.boundObservables;
|
|
5445
|
+
const propertyNames = Object.keys(boundObservables);
|
|
5446
|
+
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
5447
|
+
const propertyName = propertyNames[i];
|
|
5448
|
+
element[propertyName] = boundObservables[propertyName];
|
|
5449
|
+
}
|
|
5450
|
+
this.boundObservables = null;
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
connectBehaviors() {
|
|
5454
|
+
if (this.behaviorsConnected === false) {
|
|
5455
|
+
const behaviors = this.behaviors;
|
|
5456
|
+
if (behaviors !== null) {
|
|
5457
|
+
this.guardBehaviorConnection = true;
|
|
5458
|
+
for (const key of behaviors.keys()) {
|
|
5459
|
+
key.connectedCallback && key.connectedCallback(this);
|
|
5460
|
+
}
|
|
5461
|
+
this.guardBehaviorConnection = false;
|
|
5462
|
+
}
|
|
5463
|
+
this.behaviorsConnected = true;
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
disconnectBehaviors() {
|
|
5467
|
+
if (this.behaviorsConnected === true) {
|
|
5468
|
+
const behaviors = this.behaviors;
|
|
5469
|
+
if (behaviors !== null) {
|
|
5470
|
+
for (const key of behaviors.keys()) {
|
|
5471
|
+
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
5472
|
+
}
|
|
3852
5473
|
}
|
|
5474
|
+
this.behaviorsConnected = false;
|
|
3853
5475
|
}
|
|
3854
|
-
this.setIsConnected(true);
|
|
3855
5476
|
}
|
|
3856
5477
|
/**
|
|
3857
5478
|
* Runs disconnected lifecycle behavior on the associated element.
|
|
3858
5479
|
*/
|
|
3859
5480
|
disconnect() {
|
|
3860
|
-
if (
|
|
5481
|
+
if (this.stage !== 1 /* Stages.connected */) {
|
|
3861
5482
|
return;
|
|
3862
5483
|
}
|
|
3863
|
-
this.
|
|
5484
|
+
this.stage = 2 /* Stages.disconnecting */;
|
|
5485
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
3864
5486
|
if (this.view !== null) {
|
|
3865
5487
|
this.view.unbind();
|
|
3866
5488
|
}
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
for (const key of behaviors.keys()) {
|
|
3870
|
-
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
3871
|
-
}
|
|
3872
|
-
}
|
|
5489
|
+
this.disconnectBehaviors();
|
|
5490
|
+
this.stage = 3 /* Stages.disconnected */;
|
|
3873
5491
|
}
|
|
3874
5492
|
/**
|
|
3875
5493
|
* Runs the attribute changed callback for the associated element.
|
|
@@ -3892,27 +5510,11 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3892
5510
|
* Only emits events if connected.
|
|
3893
5511
|
*/
|
|
3894
5512
|
emit(type, detail, options) {
|
|
3895
|
-
if (this.
|
|
5513
|
+
if (this.stage === 1 /* Stages.connected */) {
|
|
3896
5514
|
return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
|
|
3897
5515
|
}
|
|
3898
5516
|
return false;
|
|
3899
5517
|
}
|
|
3900
|
-
finishInitialization() {
|
|
3901
|
-
const element = this.source;
|
|
3902
|
-
const boundObservables = this.boundObservables;
|
|
3903
|
-
// If we have any observables that were bound, re-apply their values.
|
|
3904
|
-
if (boundObservables !== null) {
|
|
3905
|
-
const propertyNames = Object.keys(boundObservables);
|
|
3906
|
-
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
3907
|
-
const propertyName = propertyNames[i];
|
|
3908
|
-
element[propertyName] = boundObservables[propertyName];
|
|
3909
|
-
}
|
|
3910
|
-
this.boundObservables = null;
|
|
3911
|
-
}
|
|
3912
|
-
this.renderTemplate(this.template);
|
|
3913
|
-
this.addStyles(this.mainStyles);
|
|
3914
|
-
this.needsInitialization = false;
|
|
3915
|
-
}
|
|
3916
5518
|
renderTemplate(template) {
|
|
3917
5519
|
var _a;
|
|
3918
5520
|
// When getting the host to render to, we start by looking
|
|
@@ -3956,13 +5558,224 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3956
5558
|
if (definition === void 0) {
|
|
3957
5559
|
throw FAST.error(1401 /* Message.missingElementDefinition */);
|
|
3958
5560
|
}
|
|
3959
|
-
return (element.$fastController = new
|
|
5561
|
+
return (element.$fastController = new elementControllerStrategy(element, definition));
|
|
5562
|
+
}
|
|
5563
|
+
/**
|
|
5564
|
+
* Sets the strategy that ElementController.forCustomElement uses to construct
|
|
5565
|
+
* ElementController instances for an element.
|
|
5566
|
+
* @param strategy - The strategy to use.
|
|
5567
|
+
*/
|
|
5568
|
+
static setStrategy(strategy) {
|
|
5569
|
+
elementControllerStrategy = strategy;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
makeSerializationNoop(ElementController);
|
|
5573
|
+
// Set default strategy for ElementController
|
|
5574
|
+
ElementController.setStrategy(ElementController);
|
|
5575
|
+
/**
|
|
5576
|
+
* Converts a styleTarget into the operative target. When the provided target is an Element
|
|
5577
|
+
* that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
|
|
5578
|
+
* it will return the root node for the element.
|
|
5579
|
+
* @param target
|
|
5580
|
+
* @returns
|
|
5581
|
+
*/
|
|
5582
|
+
function normalizeStyleTarget(target) {
|
|
5583
|
+
var _a;
|
|
5584
|
+
if ("adoptedStyleSheets" in target) {
|
|
5585
|
+
return target;
|
|
5586
|
+
}
|
|
5587
|
+
else {
|
|
5588
|
+
return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
// Default StyleStrategy implementations are defined in this module because they
|
|
5592
|
+
// require access to element shadowRoots, and we don't want to leak shadowRoot
|
|
5593
|
+
// objects out of this module.
|
|
5594
|
+
/**
|
|
5595
|
+
* https://wicg.github.io/construct-stylesheets/
|
|
5596
|
+
* https://developers.google.com/web/updates/2019/02/constructable-stylesheets
|
|
5597
|
+
*
|
|
5598
|
+
* @internal
|
|
5599
|
+
*/
|
|
5600
|
+
class AdoptedStyleSheetsStrategy {
|
|
5601
|
+
constructor(styles) {
|
|
5602
|
+
const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
|
|
5603
|
+
this.sheets = styles.map((x) => {
|
|
5604
|
+
if (x instanceof CSSStyleSheet) {
|
|
5605
|
+
return x;
|
|
5606
|
+
}
|
|
5607
|
+
let sheet = styleSheetCache.get(x);
|
|
5608
|
+
if (sheet === void 0) {
|
|
5609
|
+
sheet = new CSSStyleSheet();
|
|
5610
|
+
sheet.replaceSync(x);
|
|
5611
|
+
styleSheetCache.set(x, sheet);
|
|
5612
|
+
}
|
|
5613
|
+
return sheet;
|
|
5614
|
+
});
|
|
5615
|
+
}
|
|
5616
|
+
addStylesTo(target) {
|
|
5617
|
+
addAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
|
|
5618
|
+
}
|
|
5619
|
+
removeStylesFrom(target) {
|
|
5620
|
+
removeAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
|
|
5624
|
+
let id = 0;
|
|
5625
|
+
const nextStyleId = () => `fast-${++id}`;
|
|
5626
|
+
function usableStyleTarget(target) {
|
|
5627
|
+
return target === document ? document.body : target;
|
|
5628
|
+
}
|
|
5629
|
+
/**
|
|
5630
|
+
* @internal
|
|
5631
|
+
*/
|
|
5632
|
+
class StyleElementStrategy {
|
|
5633
|
+
constructor(styles) {
|
|
5634
|
+
this.styles = styles;
|
|
5635
|
+
this.styleClass = nextStyleId();
|
|
5636
|
+
}
|
|
5637
|
+
addStylesTo(target) {
|
|
5638
|
+
target = usableStyleTarget(normalizeStyleTarget(target));
|
|
5639
|
+
const styles = this.styles;
|
|
5640
|
+
const styleClass = this.styleClass;
|
|
5641
|
+
for (let i = 0; i < styles.length; i++) {
|
|
5642
|
+
const element = document.createElement("style");
|
|
5643
|
+
element.innerHTML = styles[i];
|
|
5644
|
+
element.className = styleClass;
|
|
5645
|
+
target.append(element);
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
removeStylesFrom(target) {
|
|
5649
|
+
target = usableStyleTarget(normalizeStyleTarget(target));
|
|
5650
|
+
const styles = target.querySelectorAll(`.${this.styleClass}`);
|
|
5651
|
+
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
|
5652
|
+
target.removeChild(styles[i]);
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
let addAdoptedStyleSheets = (target, sheets) => {
|
|
5657
|
+
target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...sheets];
|
|
5658
|
+
};
|
|
5659
|
+
let removeAdoptedStyleSheets = (target, sheets) => {
|
|
5660
|
+
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
|
|
5661
|
+
};
|
|
5662
|
+
if (ElementStyles.supportsAdoptedStyleSheets) {
|
|
5663
|
+
try {
|
|
5664
|
+
// Test if browser implementation uses FrozenArray.
|
|
5665
|
+
// If not, use push / splice to alter the stylesheets
|
|
5666
|
+
// in place. This circumvents a bug in Safari 16.4 where
|
|
5667
|
+
// periodically, assigning the array would previously
|
|
5668
|
+
// cause sheets to be removed.
|
|
5669
|
+
document.adoptedStyleSheets.push();
|
|
5670
|
+
document.adoptedStyleSheets.splice();
|
|
5671
|
+
addAdoptedStyleSheets = (target, sheets) => {
|
|
5672
|
+
target.adoptedStyleSheets.push(...sheets);
|
|
5673
|
+
};
|
|
5674
|
+
removeAdoptedStyleSheets = (target, sheets) => {
|
|
5675
|
+
for (const sheet of sheets) {
|
|
5676
|
+
const index = target.adoptedStyleSheets.indexOf(sheet);
|
|
5677
|
+
if (index !== -1) {
|
|
5678
|
+
target.adoptedStyleSheets.splice(index, 1);
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
};
|
|
5682
|
+
}
|
|
5683
|
+
catch (e) {
|
|
5684
|
+
// Do nothing if an error is thrown, the default
|
|
5685
|
+
// case handles FrozenArray.
|
|
5686
|
+
}
|
|
5687
|
+
ElementStyles.setDefaultStrategy(AdoptedStyleSheetsStrategy);
|
|
5688
|
+
}
|
|
5689
|
+
else {
|
|
5690
|
+
ElementStyles.setDefaultStrategy(StyleElementStrategy);
|
|
5691
|
+
}
|
|
5692
|
+
const deferHydrationAttribute = "defer-hydration";
|
|
5693
|
+
const needsHydrationAttribute = "needs-hydration";
|
|
5694
|
+
/**
|
|
5695
|
+
* An ElementController capable of hydrating FAST elements from
|
|
5696
|
+
* Declarative Shadow DOM.
|
|
5697
|
+
*
|
|
5698
|
+
* @beta
|
|
5699
|
+
*/
|
|
5700
|
+
class HydratableElementController extends ElementController {
|
|
5701
|
+
static hydrationObserverHandler(records) {
|
|
5702
|
+
for (const record of records) {
|
|
5703
|
+
HydratableElementController.hydrationObserver.unobserve(record.target);
|
|
5704
|
+
record.target.$fastController.connect();
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
connect() {
|
|
5708
|
+
var _a, _b;
|
|
5709
|
+
// Initialize needsHydration on first connect
|
|
5710
|
+
if (this.needsHydration === undefined) {
|
|
5711
|
+
this.needsHydration =
|
|
5712
|
+
this.source.getAttribute(needsHydrationAttribute) !== null;
|
|
5713
|
+
}
|
|
5714
|
+
// If the `defer-hydration` attribute exists on the source,
|
|
5715
|
+
// wait for it to be removed before continuing connection behavior.
|
|
5716
|
+
if (this.source.hasAttribute(deferHydrationAttribute)) {
|
|
5717
|
+
HydratableElementController.hydrationObserver.observe(this.source, {
|
|
5718
|
+
attributeFilter: [deferHydrationAttribute],
|
|
5719
|
+
});
|
|
5720
|
+
return;
|
|
5721
|
+
}
|
|
5722
|
+
// If the controller does not need to be hydrated, defer connection behavior
|
|
5723
|
+
// to the base-class. This case handles element re-connection and initial connection
|
|
5724
|
+
// of elements that did not get declarative shadow-dom emitted, as well as if an extending
|
|
5725
|
+
// class
|
|
5726
|
+
if (!this.needsHydration) {
|
|
5727
|
+
super.connect();
|
|
5728
|
+
return;
|
|
5729
|
+
}
|
|
5730
|
+
if (this.stage !== 3 /* Stages.disconnected */) {
|
|
5731
|
+
return;
|
|
5732
|
+
}
|
|
5733
|
+
this.stage = 0 /* Stages.connecting */;
|
|
5734
|
+
this.bindObservables();
|
|
5735
|
+
this.connectBehaviors();
|
|
5736
|
+
const element = this.source;
|
|
5737
|
+
const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
|
|
5738
|
+
if (this.template) {
|
|
5739
|
+
if (isHydratable(this.template)) {
|
|
5740
|
+
let firstChild = host.firstChild;
|
|
5741
|
+
let lastChild = host.lastChild;
|
|
5742
|
+
if (element.shadowRoot === null) {
|
|
5743
|
+
// handle element boundary markers when shadowRoot is not present
|
|
5744
|
+
if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
|
|
5745
|
+
firstChild.data = "";
|
|
5746
|
+
firstChild = firstChild.nextSibling;
|
|
5747
|
+
}
|
|
5748
|
+
if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
|
|
5749
|
+
lastChild.data = "";
|
|
5750
|
+
lastChild = lastChild.previousSibling;
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5753
|
+
this.view = this.template.hydrate(firstChild, lastChild, element);
|
|
5754
|
+
(_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
|
|
5755
|
+
}
|
|
5756
|
+
else {
|
|
5757
|
+
this.renderTemplate(this.template);
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
this.addStyles(this.mainStyles);
|
|
5761
|
+
this.stage = 1 /* Stages.connected */;
|
|
5762
|
+
this.source.removeAttribute(needsHydrationAttribute);
|
|
5763
|
+
this.needsInitialization = this.needsHydration = false;
|
|
5764
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
5765
|
+
}
|
|
5766
|
+
disconnect() {
|
|
5767
|
+
super.disconnect();
|
|
5768
|
+
HydratableElementController.hydrationObserver.unobserve(this.source);
|
|
5769
|
+
}
|
|
5770
|
+
static install() {
|
|
5771
|
+
ElementController.setStrategy(HydratableElementController);
|
|
3960
5772
|
}
|
|
3961
5773
|
}
|
|
5774
|
+
HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
|
|
3962
5775
|
|
|
3963
5776
|
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
3964
5777
|
function createFASTElement(BaseType) {
|
|
3965
|
-
|
|
5778
|
+
const type = class extends BaseType {
|
|
3966
5779
|
constructor() {
|
|
3967
5780
|
/* eslint-disable-next-line */
|
|
3968
5781
|
super();
|
|
@@ -3981,6 +5794,8 @@ function createFASTElement(BaseType) {
|
|
|
3981
5794
|
this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
|
|
3982
5795
|
}
|
|
3983
5796
|
};
|
|
5797
|
+
FASTElementDefinition.registerBaseType(type);
|
|
5798
|
+
return type;
|
|
3984
5799
|
}
|
|
3985
5800
|
function compose(type, nameOrDef) {
|
|
3986
5801
|
if (isFunction(type)) {
|
|
@@ -4035,4 +5850,6 @@ function customElement(nameOrDef) {
|
|
|
4035
5850
|
};
|
|
4036
5851
|
}
|
|
4037
5852
|
|
|
4038
|
-
|
|
5853
|
+
DOM.setPolicy(DOMPolicy.create());
|
|
5854
|
+
|
|
5855
|
+
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 };
|