@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
|
@@ -1,89 +1,3 @@
|
|
|
1
|
-
(function ensureGlobalThis() {
|
|
2
|
-
if (typeof globalThis !== "undefined") {
|
|
3
|
-
// We're running in a modern environment.
|
|
4
|
-
return;
|
|
5
|
-
}
|
|
6
|
-
if (typeof global !== "undefined") {
|
|
7
|
-
// We're running in NodeJS
|
|
8
|
-
global.globalThis = global;
|
|
9
|
-
}
|
|
10
|
-
else if (typeof self !== "undefined") {
|
|
11
|
-
self.globalThis = self;
|
|
12
|
-
}
|
|
13
|
-
else if (typeof window !== "undefined") {
|
|
14
|
-
// We're running in the browser's main thread.
|
|
15
|
-
window.globalThis = window;
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
// Hopefully we never get here...
|
|
19
|
-
// Not all environments allow eval and Function. Use only as a last resort:
|
|
20
|
-
// eslint-disable-next-line no-new-func
|
|
21
|
-
const result = new Function("return this")();
|
|
22
|
-
result.globalThis = result;
|
|
23
|
-
}
|
|
24
|
-
})();
|
|
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$2 = globalThis.FAST;
|
|
41
|
-
if (FAST$2.getById === void 0) {
|
|
42
|
-
const storage = Object.create(null);
|
|
43
|
-
Reflect.defineProperty(FAST$2, "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$2.getById(/* KernelServiceId.styleSheetStrategy */ 5, () => StyleElementStrategy);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
1
|
if (globalThis.FAST === void 0) {
|
|
88
2
|
Reflect.defineProperty(globalThis, "FAST", {
|
|
89
3
|
value: Object.create(null),
|
|
@@ -95,18 +9,24 @@ if (globalThis.FAST === void 0) {
|
|
|
95
9
|
const FAST$1 = globalThis.FAST;
|
|
96
10
|
const debugMessages = {
|
|
97
11
|
[1101 /* needsArrayObservation */]: "Must call enableArrayObservation before observing arrays.",
|
|
98
|
-
[1201 /*
|
|
12
|
+
[1201 /* onlySetDOMPolicyOnce */]: "The DOM Policy can only be set once.",
|
|
99
13
|
[1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
|
|
100
14
|
[1203 /* twoWayBindingRequiresObservables */]: "View=>Model update skipped. To use twoWay binding, the target property must be observable.",
|
|
101
15
|
[1204 /* hostBindingWithoutHost */]: "No host element is present. Cannot bind host with ${name}.",
|
|
102
16
|
[1205 /* unsupportedBindingBehavior */]: "The requested binding behavior is not supported by the binding engine.",
|
|
17
|
+
[1206 /* directCallToHTMLTagNotAllowed */]: "Calling html`` as a normal function invalidates the security guarantees provided by FAST.",
|
|
18
|
+
[1207 /* onlySetTemplatePolicyOnce */]: "The DOM Policy for an HTML template can only be set once.",
|
|
19
|
+
[1208 /* cannotSetTemplatePolicyAfterCompilation */]: "The DOM Policy cannot be set after a template is compiled.",
|
|
20
|
+
[1209 /* blockedByDOMPolicy */]: "'${aspectName}' on '${tagName}' is blocked by the current DOMPolicy.",
|
|
103
21
|
[1401 /* missingElementDefinition */]: "Missing FASTElement definition.",
|
|
104
22
|
[1501 /* noRegistrationForContext */]: "No registration for Context/Interface '${name}'.",
|
|
105
23
|
[1502 /* noFactoryForResolver */]: "Dependency injection resolver for '${key}' returned a null factory.",
|
|
106
24
|
[1503 /* invalidResolverStrategy */]: "Invalid dependency injection resolver strategy specified '${strategy}'.",
|
|
107
25
|
[1504 /* cannotAutoregisterDependency */]: "Unable to autoregister dependency.",
|
|
108
26
|
[1505 /* cannotResolveKey */]: "Unable to resolve dependency injection key '${key}'.",
|
|
109
|
-
[1506 /* cannotConstructNativeFunction */]:
|
|
27
|
+
[1506 /* cannotConstructNativeFunction */]:
|
|
28
|
+
/* eslint-disable-next-line max-len */
|
|
29
|
+
"'${name}' is a native function and therefore cannot be safely constructed by DI. If this is intentional, please use a callback or cachedCallback resolver.",
|
|
110
30
|
[1507 /* cannotJITRegisterNonConstructor */]: "Attempted to jitRegister something that is not a constructor '${value}'. Did you forget to register this dependency?",
|
|
111
31
|
[1508 /* cannotJITRegisterIntrinsic */]: "Attempted to jitRegister an intrinsic type '${value}'. Did you forget to add @inject(Key)?",
|
|
112
32
|
[1509 /* cannotJITRegisterInterface */]: "Attempted to jitRegister an interface '${value}'.",
|
|
@@ -145,7 +65,95 @@ Object.assign(FAST$1, {
|
|
|
145
65
|
},
|
|
146
66
|
});
|
|
147
67
|
|
|
148
|
-
|
|
68
|
+
let kernelMode;
|
|
69
|
+
const kernelAttr = "fast-kernel";
|
|
70
|
+
try {
|
|
71
|
+
if (document.currentScript) {
|
|
72
|
+
kernelMode = document.currentScript.getAttribute(kernelAttr);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const scripts = document.getElementsByTagName("script");
|
|
76
|
+
const currentScript = scripts[scripts.length - 1];
|
|
77
|
+
kernelMode = currentScript.getAttribute(kernelAttr);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
kernelMode = "isolate";
|
|
82
|
+
}
|
|
83
|
+
let KernelServiceId;
|
|
84
|
+
switch (kernelMode) {
|
|
85
|
+
case "share": // share the kernel across major versions
|
|
86
|
+
KernelServiceId = Object.freeze({
|
|
87
|
+
updateQueue: 1,
|
|
88
|
+
observable: 2,
|
|
89
|
+
contextEvent: 3,
|
|
90
|
+
elementRegistry: 4,
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
case "share-v2": // only share the kernel with other v2 instances
|
|
94
|
+
KernelServiceId = Object.freeze({
|
|
95
|
+
updateQueue: 1.2,
|
|
96
|
+
observable: 2.2,
|
|
97
|
+
contextEvent: 3.2,
|
|
98
|
+
elementRegistry: 4.2,
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
// fully isolate the kernel from all other FAST instances
|
|
103
|
+
const postfix = `-${Math.random().toString(36).substring(2, 8)}`;
|
|
104
|
+
KernelServiceId = Object.freeze({
|
|
105
|
+
updateQueue: `1.2${postfix}`,
|
|
106
|
+
observable: `2.2${postfix}`,
|
|
107
|
+
contextEvent: `3.2${postfix}`,
|
|
108
|
+
elementRegistry: `4.2${postfix}`,
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Determines whether or not an object is a function.
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
const isFunction = (object) => typeof object === "function";
|
|
117
|
+
/**
|
|
118
|
+
* Determines whether or not an object is a string.
|
|
119
|
+
* @public
|
|
120
|
+
*/
|
|
121
|
+
const isString = (object) => typeof object === "string";
|
|
122
|
+
/**
|
|
123
|
+
* A function which does nothing.
|
|
124
|
+
* @public
|
|
125
|
+
*/
|
|
126
|
+
const noop = () => void 0;
|
|
127
|
+
|
|
128
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
129
|
+
(function ensureGlobalThis() {
|
|
130
|
+
if (typeof globalThis !== "undefined") {
|
|
131
|
+
// We're running in a modern environment.
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
if (typeof global !== "undefined") {
|
|
136
|
+
// We're running in NodeJS
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
global.globalThis = global;
|
|
139
|
+
}
|
|
140
|
+
else if (typeof self !== "undefined") {
|
|
141
|
+
self.globalThis = self;
|
|
142
|
+
}
|
|
143
|
+
else if (typeof window !== "undefined") {
|
|
144
|
+
// We're running in the browser's main thread.
|
|
145
|
+
window.globalThis = window;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Hopefully we never get here...
|
|
149
|
+
// Not all environments allow eval and Function. Use only as a last resort:
|
|
150
|
+
// eslint-disable-next-line no-new-func
|
|
151
|
+
const result = new Function("return this")();
|
|
152
|
+
result.globalThis = result;
|
|
153
|
+
}
|
|
154
|
+
})();
|
|
155
|
+
|
|
156
|
+
// ensure FAST global - duplicated debug.ts
|
|
149
157
|
const propConfig = {
|
|
150
158
|
configurable: false,
|
|
151
159
|
enumerable: false,
|
|
@@ -156,7 +164,7 @@ if (globalThis.FAST === void 0) {
|
|
|
156
164
|
}
|
|
157
165
|
/**
|
|
158
166
|
* The FAST global.
|
|
159
|
-
* @
|
|
167
|
+
* @public
|
|
160
168
|
*/
|
|
161
169
|
const FAST = globalThis.FAST;
|
|
162
170
|
if (FAST.getById === void 0) {
|
|
@@ -232,122 +240,563 @@ function createMetadataLocator() {
|
|
|
232
240
|
return metadata;
|
|
233
241
|
};
|
|
234
242
|
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* @internal
|
|
238
|
-
*/
|
|
239
|
-
const isFunction = (object) => typeof object === "function";
|
|
240
243
|
/**
|
|
244
|
+
* Makes a type noop for JSON serialization.
|
|
245
|
+
* @param type - The type to make noop for JSON serialization.
|
|
241
246
|
* @internal
|
|
242
247
|
*/
|
|
243
|
-
|
|
248
|
+
function makeSerializationNoop(type) {
|
|
249
|
+
type.prototype.toJSON = noop;
|
|
250
|
+
}
|
|
244
251
|
|
|
245
252
|
/**
|
|
246
|
-
* The
|
|
253
|
+
* The type of HTML aspect to target.
|
|
247
254
|
* @public
|
|
248
255
|
*/
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
tryRunTask(tasks[index]);
|
|
279
|
-
index++;
|
|
280
|
-
// Prevent leaking memory for long chains of recursive calls to `enqueue`.
|
|
281
|
-
// If we call `enqueue` within a task scheduled by `enqueue`, the queue will
|
|
282
|
-
// grow, but to avoid an O(n) walk for every task we execute, we don't
|
|
283
|
-
// shift tasks off the queue after they have been executed.
|
|
284
|
-
// Instead, we periodically shift 1024 tasks off the queue.
|
|
285
|
-
if (index > capacity) {
|
|
286
|
-
// Manually shift all values starting at the index back to the
|
|
287
|
-
// beginning of the queue.
|
|
288
|
-
for (let scan = 0, newLength = tasks.length - index; scan < newLength; scan++) {
|
|
289
|
-
tasks[scan] = tasks[scan + index];
|
|
290
|
-
}
|
|
291
|
-
tasks.length -= index;
|
|
292
|
-
index = 0;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
tasks.length = 0;
|
|
296
|
-
}
|
|
297
|
-
function enqueue(callable) {
|
|
298
|
-
tasks.push(callable);
|
|
299
|
-
if (tasks.length < 2) {
|
|
300
|
-
updateAsync ? rAF(process) : process();
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return Object.freeze({
|
|
304
|
-
enqueue,
|
|
305
|
-
next: () => new Promise(enqueue),
|
|
306
|
-
process,
|
|
307
|
-
setMode: (isAsync) => (updateAsync = isAsync),
|
|
308
|
-
});
|
|
256
|
+
const DOMAspect = Object.freeze({
|
|
257
|
+
/**
|
|
258
|
+
* Not aspected.
|
|
259
|
+
*/
|
|
260
|
+
none: 0,
|
|
261
|
+
/**
|
|
262
|
+
* An attribute.
|
|
263
|
+
*/
|
|
264
|
+
attribute: 1,
|
|
265
|
+
/**
|
|
266
|
+
* A boolean attribute.
|
|
267
|
+
*/
|
|
268
|
+
booleanAttribute: 2,
|
|
269
|
+
/**
|
|
270
|
+
* A property.
|
|
271
|
+
*/
|
|
272
|
+
property: 3,
|
|
273
|
+
/**
|
|
274
|
+
* Content
|
|
275
|
+
*/
|
|
276
|
+
content: 4,
|
|
277
|
+
/**
|
|
278
|
+
* A token list.
|
|
279
|
+
*/
|
|
280
|
+
tokenList: 5,
|
|
281
|
+
/**
|
|
282
|
+
* An event.
|
|
283
|
+
*/
|
|
284
|
+
event: 6,
|
|
309
285
|
});
|
|
310
|
-
|
|
286
|
+
const createHTML$1 = html => html;
|
|
287
|
+
const fastTrustedType = globalThis.trustedTypes
|
|
288
|
+
? globalThis.trustedTypes.createPolicy("fast-html", { createHTML: createHTML$1 })
|
|
289
|
+
: { createHTML: createHTML$1 };
|
|
290
|
+
let defaultPolicy = Object.freeze({
|
|
291
|
+
createHTML(value) {
|
|
292
|
+
return fastTrustedType.createHTML(value);
|
|
293
|
+
},
|
|
294
|
+
protect(tagName, aspect, aspectName, sink) {
|
|
295
|
+
return sink;
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
const fastPolicy = defaultPolicy;
|
|
311
299
|
/**
|
|
312
|
-
*
|
|
313
|
-
* subscribers interested in a specific change notification on an
|
|
314
|
-
* observable subject.
|
|
315
|
-
*
|
|
316
|
-
* @remarks
|
|
317
|
-
* This set is optimized for the most common scenario of 1 or 2 subscribers.
|
|
318
|
-
* With this in mind, it can store a subscriber in an internal field, allowing it to avoid Array#push operations.
|
|
319
|
-
* If the set ever exceeds two subscribers, it upgrades to an array automatically.
|
|
300
|
+
* Common DOM APIs.
|
|
320
301
|
* @public
|
|
321
302
|
*/
|
|
322
|
-
|
|
303
|
+
const DOM = Object.freeze({
|
|
323
304
|
/**
|
|
324
|
-
*
|
|
325
|
-
* @param subject - The subject that subscribers will receive notifications from.
|
|
326
|
-
* @param initialSubscriber - An initial subscriber to changes.
|
|
305
|
+
* Gets the dom policy used by the templating system.
|
|
327
306
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this.spillover = void 0;
|
|
332
|
-
this.subject = subject;
|
|
333
|
-
this.sub1 = initialSubscriber;
|
|
334
|
-
}
|
|
307
|
+
get policy() {
|
|
308
|
+
return defaultPolicy;
|
|
309
|
+
},
|
|
335
310
|
/**
|
|
336
|
-
*
|
|
337
|
-
* @param
|
|
311
|
+
* Sets the dom policy used by the templating system.
|
|
312
|
+
* @param policy - The policy to set.
|
|
313
|
+
* @remarks
|
|
314
|
+
* This API can only be called once, for security reasons. It should be
|
|
315
|
+
* called by the application developer at the start of their program.
|
|
338
316
|
*/
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
317
|
+
setPolicy(value) {
|
|
318
|
+
if (defaultPolicy !== fastPolicy) {
|
|
319
|
+
throw FAST.error(1201 /* Message.onlySetDOMPolicyOnce */);
|
|
320
|
+
}
|
|
321
|
+
defaultPolicy = value;
|
|
322
|
+
},
|
|
344
323
|
/**
|
|
345
|
-
*
|
|
346
|
-
* @param
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
324
|
+
* Sets an attribute value on an element.
|
|
325
|
+
* @param element - The element to set the attribute value on.
|
|
326
|
+
* @param attributeName - The attribute name to set.
|
|
327
|
+
* @param value - The value of the attribute to set.
|
|
328
|
+
* @remarks
|
|
329
|
+
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
330
|
+
* it is set to the provided value using the standard `setAttribute` API.
|
|
331
|
+
*/
|
|
332
|
+
setAttribute(element, attributeName, value) {
|
|
333
|
+
value === null || value === undefined
|
|
334
|
+
? element.removeAttribute(attributeName)
|
|
335
|
+
: element.setAttribute(attributeName, value);
|
|
336
|
+
},
|
|
337
|
+
/**
|
|
338
|
+
* Sets a boolean attribute value.
|
|
339
|
+
* @param element - The element to set the boolean attribute value on.
|
|
340
|
+
* @param attributeName - The attribute name to set.
|
|
341
|
+
* @param value - The value of the attribute to set.
|
|
342
|
+
* @remarks
|
|
343
|
+
* If the value is true, the attribute is added; otherwise it is removed.
|
|
344
|
+
*/
|
|
345
|
+
setBooleanAttribute(element, attributeName, value) {
|
|
346
|
+
value
|
|
347
|
+
? element.setAttribute(attributeName, "")
|
|
348
|
+
: element.removeAttribute(attributeName);
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
function safeURL(tagName, aspect, aspectName, sink) {
|
|
353
|
+
return (target, name, value, ...rest) => {
|
|
354
|
+
if (isString(value)) {
|
|
355
|
+
value = value.replace(/(javascript:|vbscript:|data:)/, "");
|
|
356
|
+
}
|
|
357
|
+
sink(target, name, value, ...rest);
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function block(tagName, aspect, aspectName, sink) {
|
|
361
|
+
throw FAST.error(1209 /* Message.blockedByDOMPolicy */, {
|
|
362
|
+
aspectName,
|
|
363
|
+
tagName: tagName !== null && tagName !== void 0 ? tagName : "text",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const defaultDOMElementGuards = {
|
|
367
|
+
a: {
|
|
368
|
+
[DOMAspect.attribute]: {
|
|
369
|
+
href: safeURL,
|
|
370
|
+
},
|
|
371
|
+
[DOMAspect.property]: {
|
|
372
|
+
href: safeURL,
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
area: {
|
|
376
|
+
[DOMAspect.attribute]: {
|
|
377
|
+
href: safeURL,
|
|
378
|
+
},
|
|
379
|
+
[DOMAspect.property]: {
|
|
380
|
+
href: safeURL,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
button: {
|
|
384
|
+
[DOMAspect.attribute]: {
|
|
385
|
+
formaction: safeURL,
|
|
386
|
+
},
|
|
387
|
+
[DOMAspect.property]: {
|
|
388
|
+
formAction: safeURL,
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
embed: {
|
|
392
|
+
[DOMAspect.attribute]: {
|
|
393
|
+
src: block,
|
|
394
|
+
},
|
|
395
|
+
[DOMAspect.property]: {
|
|
396
|
+
src: block,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
form: {
|
|
400
|
+
[DOMAspect.attribute]: {
|
|
401
|
+
action: safeURL,
|
|
402
|
+
},
|
|
403
|
+
[DOMAspect.property]: {
|
|
404
|
+
action: safeURL,
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
frame: {
|
|
408
|
+
[DOMAspect.attribute]: {
|
|
409
|
+
src: safeURL,
|
|
410
|
+
},
|
|
411
|
+
[DOMAspect.property]: {
|
|
412
|
+
src: safeURL,
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
iframe: {
|
|
416
|
+
[DOMAspect.attribute]: {
|
|
417
|
+
src: safeURL,
|
|
418
|
+
},
|
|
419
|
+
[DOMAspect.property]: {
|
|
420
|
+
src: safeURL,
|
|
421
|
+
srcdoc: block,
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
input: {
|
|
425
|
+
[DOMAspect.attribute]: {
|
|
426
|
+
formaction: safeURL,
|
|
427
|
+
},
|
|
428
|
+
[DOMAspect.property]: {
|
|
429
|
+
formAction: safeURL,
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
link: {
|
|
433
|
+
[DOMAspect.attribute]: {
|
|
434
|
+
href: block,
|
|
435
|
+
},
|
|
436
|
+
[DOMAspect.property]: {
|
|
437
|
+
href: block,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
object: {
|
|
441
|
+
[DOMAspect.attribute]: {
|
|
442
|
+
codebase: block,
|
|
443
|
+
data: block,
|
|
444
|
+
},
|
|
445
|
+
[DOMAspect.property]: {
|
|
446
|
+
codeBase: block,
|
|
447
|
+
data: block,
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
script: {
|
|
451
|
+
[DOMAspect.attribute]: {
|
|
452
|
+
src: block,
|
|
453
|
+
text: block,
|
|
454
|
+
},
|
|
455
|
+
[DOMAspect.property]: {
|
|
456
|
+
src: block,
|
|
457
|
+
text: block,
|
|
458
|
+
innerText: block,
|
|
459
|
+
textContent: block,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
style: {
|
|
463
|
+
[DOMAspect.property]: {
|
|
464
|
+
innerText: block,
|
|
465
|
+
textContent: block,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
const blockedEvents = {
|
|
470
|
+
onabort: block,
|
|
471
|
+
onauxclick: block,
|
|
472
|
+
onbeforeinput: block,
|
|
473
|
+
onbeforematch: block,
|
|
474
|
+
onblur: block,
|
|
475
|
+
oncancel: block,
|
|
476
|
+
oncanplay: block,
|
|
477
|
+
oncanplaythrough: block,
|
|
478
|
+
onchange: block,
|
|
479
|
+
onclick: block,
|
|
480
|
+
onclose: block,
|
|
481
|
+
oncontextlost: block,
|
|
482
|
+
oncontextmenu: block,
|
|
483
|
+
oncontextrestored: block,
|
|
484
|
+
oncopy: block,
|
|
485
|
+
oncuechange: block,
|
|
486
|
+
oncut: block,
|
|
487
|
+
ondblclick: block,
|
|
488
|
+
ondrag: block,
|
|
489
|
+
ondragend: block,
|
|
490
|
+
ondragenter: block,
|
|
491
|
+
ondragleave: block,
|
|
492
|
+
ondragover: block,
|
|
493
|
+
ondragstart: block,
|
|
494
|
+
ondrop: block,
|
|
495
|
+
ondurationchange: block,
|
|
496
|
+
onemptied: block,
|
|
497
|
+
onended: block,
|
|
498
|
+
onerror: block,
|
|
499
|
+
onfocus: block,
|
|
500
|
+
onformdata: block,
|
|
501
|
+
oninput: block,
|
|
502
|
+
oninvalid: block,
|
|
503
|
+
onkeydown: block,
|
|
504
|
+
onkeypress: block,
|
|
505
|
+
onkeyup: block,
|
|
506
|
+
onload: block,
|
|
507
|
+
onloadeddata: block,
|
|
508
|
+
onloadedmetadata: block,
|
|
509
|
+
onloadstart: block,
|
|
510
|
+
onmousedown: block,
|
|
511
|
+
onmouseenter: block,
|
|
512
|
+
onmouseleave: block,
|
|
513
|
+
onmousemove: block,
|
|
514
|
+
onmouseout: block,
|
|
515
|
+
onmouseover: block,
|
|
516
|
+
onmouseup: block,
|
|
517
|
+
onpaste: block,
|
|
518
|
+
onpause: block,
|
|
519
|
+
onplay: block,
|
|
520
|
+
onplaying: block,
|
|
521
|
+
onprogress: block,
|
|
522
|
+
onratechange: block,
|
|
523
|
+
onreset: block,
|
|
524
|
+
onresize: block,
|
|
525
|
+
onscroll: block,
|
|
526
|
+
onsecuritypolicyviolation: block,
|
|
527
|
+
onseeked: block,
|
|
528
|
+
onseeking: block,
|
|
529
|
+
onselect: block,
|
|
530
|
+
onslotchange: block,
|
|
531
|
+
onstalled: block,
|
|
532
|
+
onsubmit: block,
|
|
533
|
+
onsuspend: block,
|
|
534
|
+
ontimeupdate: block,
|
|
535
|
+
ontoggle: block,
|
|
536
|
+
onvolumechange: block,
|
|
537
|
+
onwaiting: block,
|
|
538
|
+
onwebkitanimationend: block,
|
|
539
|
+
onwebkitanimationiteration: block,
|
|
540
|
+
onwebkitanimationstart: block,
|
|
541
|
+
onwebkittransitionend: block,
|
|
542
|
+
onwheel: block,
|
|
543
|
+
};
|
|
544
|
+
const defaultDOMGuards = {
|
|
545
|
+
elements: defaultDOMElementGuards,
|
|
546
|
+
aspects: {
|
|
547
|
+
[DOMAspect.attribute]: Object.assign({}, blockedEvents),
|
|
548
|
+
[DOMAspect.property]: Object.assign({ innerHTML: block }, blockedEvents),
|
|
549
|
+
[DOMAspect.event]: Object.assign({}, blockedEvents),
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
function createDomSinkGuards(config, defaults) {
|
|
553
|
+
const result = {};
|
|
554
|
+
for (const name in defaults) {
|
|
555
|
+
const overrideValue = config[name];
|
|
556
|
+
const defaultValue = defaults[name];
|
|
557
|
+
switch (overrideValue) {
|
|
558
|
+
case null:
|
|
559
|
+
// remove the default
|
|
560
|
+
break;
|
|
561
|
+
case undefined:
|
|
562
|
+
// keep the default
|
|
563
|
+
result[name] = defaultValue;
|
|
564
|
+
break;
|
|
565
|
+
default:
|
|
566
|
+
// override the default
|
|
567
|
+
result[name] = overrideValue;
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// add any new sinks that were not overrides
|
|
572
|
+
for (const name in config) {
|
|
573
|
+
if (!(name in result)) {
|
|
574
|
+
result[name] = config[name];
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return Object.freeze(result);
|
|
578
|
+
}
|
|
579
|
+
function createDOMAspectGuards(config, defaults) {
|
|
580
|
+
const result = {};
|
|
581
|
+
for (const aspect in defaults) {
|
|
582
|
+
const overrideValue = config[aspect];
|
|
583
|
+
const defaultValue = defaults[aspect];
|
|
584
|
+
switch (overrideValue) {
|
|
585
|
+
case null:
|
|
586
|
+
// remove the default
|
|
587
|
+
break;
|
|
588
|
+
case undefined:
|
|
589
|
+
// keep the default
|
|
590
|
+
result[aspect] = createDomSinkGuards(defaultValue, {});
|
|
591
|
+
break;
|
|
592
|
+
default:
|
|
593
|
+
// override the default
|
|
594
|
+
result[aspect] = createDomSinkGuards(overrideValue, defaultValue);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// add any new aspect guards that were not overrides
|
|
599
|
+
for (const aspect in config) {
|
|
600
|
+
if (!(aspect in result)) {
|
|
601
|
+
result[aspect] = createDomSinkGuards(config[aspect], {});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return Object.freeze(result);
|
|
605
|
+
}
|
|
606
|
+
function createElementGuards(config, defaults) {
|
|
607
|
+
const result = {};
|
|
608
|
+
for (const tag in defaults) {
|
|
609
|
+
const overrideValue = config[tag];
|
|
610
|
+
const defaultValue = defaults[tag];
|
|
611
|
+
switch (overrideValue) {
|
|
612
|
+
case null:
|
|
613
|
+
// remove the default
|
|
614
|
+
break;
|
|
615
|
+
case undefined:
|
|
616
|
+
// keep the default
|
|
617
|
+
result[tag] = createDOMAspectGuards(overrideValue, {});
|
|
618
|
+
break;
|
|
619
|
+
default:
|
|
620
|
+
// override the default aspects
|
|
621
|
+
result[tag] = createDOMAspectGuards(overrideValue, defaultValue);
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
// Add any new element guards that were not overrides
|
|
626
|
+
for (const tag in config) {
|
|
627
|
+
if (!(tag in result)) {
|
|
628
|
+
result[tag] = createDOMAspectGuards(config[tag], {});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return Object.freeze(result);
|
|
632
|
+
}
|
|
633
|
+
function createDOMGuards(config, defaults) {
|
|
634
|
+
return Object.freeze({
|
|
635
|
+
elements: config.elements
|
|
636
|
+
? createElementGuards(config.elements, defaults.elements)
|
|
637
|
+
: defaults.elements,
|
|
638
|
+
aspects: config.aspects
|
|
639
|
+
? createDOMAspectGuards(config.aspects, defaults.aspects)
|
|
640
|
+
: defaults.aspects,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
function createTrustedType() {
|
|
644
|
+
const createHTML = html => html;
|
|
645
|
+
return globalThis.trustedTypes
|
|
646
|
+
? globalThis.trustedTypes.createPolicy("fast-html", { createHTML })
|
|
647
|
+
: { createHTML };
|
|
648
|
+
}
|
|
649
|
+
function tryGuard(aspectGuards, tagName, aspect, aspectName, sink) {
|
|
650
|
+
const sinkGuards = aspectGuards[aspect];
|
|
651
|
+
if (sinkGuards) {
|
|
652
|
+
const guard = sinkGuards[aspectName];
|
|
653
|
+
if (guard) {
|
|
654
|
+
return guard(tagName, aspect, aspectName, sink);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* A helper for creating DOM policies.
|
|
660
|
+
* @public
|
|
661
|
+
*/
|
|
662
|
+
const DOMPolicy = Object.freeze({
|
|
663
|
+
/**
|
|
664
|
+
* Creates a new DOM Policy object.
|
|
665
|
+
* @param options The options to use in creating the policy.
|
|
666
|
+
* @returns The newly created DOMPolicy.
|
|
667
|
+
*/
|
|
668
|
+
create(options = {}) {
|
|
669
|
+
var _a, _b;
|
|
670
|
+
const trustedType = (_a = options.trustedType) !== null && _a !== void 0 ? _a : createTrustedType();
|
|
671
|
+
const guards = createDOMGuards((_b = options.guards) !== null && _b !== void 0 ? _b : {}, defaultDOMGuards);
|
|
672
|
+
return Object.freeze({
|
|
673
|
+
createHTML(value) {
|
|
674
|
+
return trustedType.createHTML(value);
|
|
675
|
+
},
|
|
676
|
+
protect(tagName, aspect, aspectName, sink) {
|
|
677
|
+
var _a;
|
|
678
|
+
// Check for element-specific guards.
|
|
679
|
+
const key = (tagName !== null && tagName !== void 0 ? tagName : "").toLowerCase();
|
|
680
|
+
const elementGuards = guards.elements[key];
|
|
681
|
+
if (elementGuards) {
|
|
682
|
+
const guard = tryGuard(elementGuards, tagName, aspect, aspectName, sink);
|
|
683
|
+
if (guard) {
|
|
684
|
+
return guard;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Check for guards applicable to all nodes.
|
|
688
|
+
return ((_a = tryGuard(guards.aspects, tagName, aspect, aspectName, sink)) !== null && _a !== void 0 ? _a : sink);
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* The default UpdateQueue.
|
|
696
|
+
* @public
|
|
697
|
+
*/
|
|
698
|
+
const Updates = FAST.getById(KernelServiceId.updateQueue, () => {
|
|
699
|
+
const tasks = [];
|
|
700
|
+
const pendingErrors = [];
|
|
701
|
+
const rAF = globalThis.requestAnimationFrame;
|
|
702
|
+
let updateAsync = true;
|
|
703
|
+
function throwFirstError() {
|
|
704
|
+
if (pendingErrors.length) {
|
|
705
|
+
throw pendingErrors.shift();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function tryRunTask(task) {
|
|
709
|
+
try {
|
|
710
|
+
task.call();
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
if (updateAsync) {
|
|
714
|
+
pendingErrors.push(error);
|
|
715
|
+
setTimeout(throwFirstError, 0);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
tasks.length = 0;
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function process() {
|
|
724
|
+
const capacity = 1024;
|
|
725
|
+
let index = 0;
|
|
726
|
+
while (index < tasks.length) {
|
|
727
|
+
tryRunTask(tasks[index]);
|
|
728
|
+
index++;
|
|
729
|
+
// Prevent leaking memory for long chains of recursive calls to `enqueue`.
|
|
730
|
+
// If we call `enqueue` within a task scheduled by `enqueue`, the queue will
|
|
731
|
+
// grow, but to avoid an O(n) walk for every task we execute, we don't
|
|
732
|
+
// shift tasks off the queue after they have been executed.
|
|
733
|
+
// Instead, we periodically shift 1024 tasks off the queue.
|
|
734
|
+
if (index > capacity) {
|
|
735
|
+
// Manually shift all values starting at the index back to the
|
|
736
|
+
// beginning of the queue.
|
|
737
|
+
for (let scan = 0, newLength = tasks.length - index; scan < newLength; scan++) {
|
|
738
|
+
tasks[scan] = tasks[scan + index];
|
|
739
|
+
}
|
|
740
|
+
tasks.length -= index;
|
|
741
|
+
index = 0;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
tasks.length = 0;
|
|
745
|
+
}
|
|
746
|
+
function enqueue(callable) {
|
|
747
|
+
tasks.push(callable);
|
|
748
|
+
if (tasks.length < 2) {
|
|
749
|
+
updateAsync ? rAF(process) : process();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return Object.freeze({
|
|
753
|
+
enqueue,
|
|
754
|
+
next: () => new Promise(enqueue),
|
|
755
|
+
process,
|
|
756
|
+
setMode: (isAsync) => (updateAsync = isAsync),
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* An implementation of {@link Notifier} that efficiently keeps track of
|
|
762
|
+
* subscribers interested in a specific change notification on an
|
|
763
|
+
* observable subject.
|
|
764
|
+
*
|
|
765
|
+
* @remarks
|
|
766
|
+
* This set is optimized for the most common scenario of 1 or 2 subscribers.
|
|
767
|
+
* With this in mind, it can store a subscriber in an internal field, allowing it to avoid Array#push operations.
|
|
768
|
+
* If the set ever exceeds two subscribers, it upgrades to an array automatically.
|
|
769
|
+
* @public
|
|
770
|
+
*/
|
|
771
|
+
class SubscriberSet {
|
|
772
|
+
/**
|
|
773
|
+
* Creates an instance of SubscriberSet for the specified subject.
|
|
774
|
+
* @param subject - The subject that subscribers will receive notifications from.
|
|
775
|
+
* @param initialSubscriber - An initial subscriber to changes.
|
|
776
|
+
*/
|
|
777
|
+
constructor(subject, initialSubscriber) {
|
|
778
|
+
this.sub1 = void 0;
|
|
779
|
+
this.sub2 = void 0;
|
|
780
|
+
this.spillover = void 0;
|
|
781
|
+
this.subject = subject;
|
|
782
|
+
this.sub1 = initialSubscriber;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Checks whether the provided subscriber has been added to this set.
|
|
786
|
+
* @param subscriber - The subscriber to test for inclusion in this set.
|
|
787
|
+
*/
|
|
788
|
+
has(subscriber) {
|
|
789
|
+
return this.spillover === void 0
|
|
790
|
+
? this.sub1 === subscriber || this.sub2 === subscriber
|
|
791
|
+
: this.spillover.indexOf(subscriber) !== -1;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Subscribes to notification of changes in an object's state.
|
|
795
|
+
* @param subscriber - The object that is subscribing for change notification.
|
|
796
|
+
*/
|
|
797
|
+
subscribe(subscriber) {
|
|
798
|
+
const spillover = this.spillover;
|
|
799
|
+
if (spillover === void 0) {
|
|
351
800
|
if (this.has(subscriber)) {
|
|
352
801
|
return;
|
|
353
802
|
}
|
|
@@ -492,9 +941,9 @@ const SourceLifetime = Object.freeze({
|
|
|
492
941
|
* Common Observable APIs.
|
|
493
942
|
* @public
|
|
494
943
|
*/
|
|
495
|
-
const Observable = FAST.getById(
|
|
944
|
+
const Observable = FAST.getById(KernelServiceId.observable, () => {
|
|
496
945
|
const queueUpdate = Updates.enqueue;
|
|
497
|
-
const volatileRegex = /(:|&&|\|\||if)/;
|
|
946
|
+
const volatileRegex = /(:|&&|\|\||if|\?\.)/;
|
|
498
947
|
const notifierLookup = new WeakMap();
|
|
499
948
|
let watcher = void 0;
|
|
500
949
|
let createArrayObserver = (array) => {
|
|
@@ -537,9 +986,9 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
537
986
|
}
|
|
538
987
|
}
|
|
539
988
|
class ExpressionNotifierImplementation extends SubscriberSet {
|
|
540
|
-
constructor(
|
|
541
|
-
super(
|
|
542
|
-
this.
|
|
989
|
+
constructor(expression, initialSubscriber, isVolatileBinding = false) {
|
|
990
|
+
super(expression, initialSubscriber);
|
|
991
|
+
this.expression = expression;
|
|
543
992
|
this.isVolatileBinding = isVolatileBinding;
|
|
544
993
|
this.needsRefresh = true;
|
|
545
994
|
this.needsQueue = true;
|
|
@@ -579,13 +1028,17 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
579
1028
|
this.needsRefresh = this.isVolatileBinding;
|
|
580
1029
|
let result;
|
|
581
1030
|
try {
|
|
582
|
-
result = this.
|
|
1031
|
+
result = this.expression(source, context);
|
|
583
1032
|
}
|
|
584
1033
|
finally {
|
|
585
1034
|
watcher = previousWatcher;
|
|
586
1035
|
}
|
|
587
1036
|
return result;
|
|
588
1037
|
}
|
|
1038
|
+
// backwards compat with v1 kernel
|
|
1039
|
+
disconnect() {
|
|
1040
|
+
this.dispose();
|
|
1041
|
+
}
|
|
589
1042
|
dispose() {
|
|
590
1043
|
if (this.last !== null) {
|
|
591
1044
|
let current = this.first;
|
|
@@ -647,6 +1100,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
647
1100
|
}
|
|
648
1101
|
}
|
|
649
1102
|
}
|
|
1103
|
+
makeSerializationNoop(ExpressionNotifierImplementation);
|
|
650
1104
|
return Object.freeze({
|
|
651
1105
|
/**
|
|
652
1106
|
* @internal
|
|
@@ -714,20 +1168,20 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
|
714
1168
|
/**
|
|
715
1169
|
* Creates a {@link ExpressionNotifier} that can watch the
|
|
716
1170
|
* provided {@link Expression} for changes.
|
|
717
|
-
* @param
|
|
1171
|
+
* @param expression - The binding to observe.
|
|
718
1172
|
* @param initialSubscriber - An initial subscriber to changes in the binding value.
|
|
719
1173
|
* @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
|
|
720
1174
|
*/
|
|
721
|
-
binding(
|
|
722
|
-
return new ExpressionNotifierImplementation(
|
|
1175
|
+
binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
|
|
1176
|
+
return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
|
|
723
1177
|
},
|
|
724
1178
|
/**
|
|
725
1179
|
* Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
|
|
726
1180
|
* on every evaluation of the value.
|
|
727
|
-
* @param
|
|
1181
|
+
* @param expression - The binding to inspect.
|
|
728
1182
|
*/
|
|
729
|
-
isVolatileBinding(
|
|
730
|
-
return volatileRegex.test(
|
|
1183
|
+
isVolatileBinding(expression) {
|
|
1184
|
+
return volatileRegex.test(expression.toString());
|
|
731
1185
|
},
|
|
732
1186
|
});
|
|
733
1187
|
});
|
|
@@ -755,7 +1209,7 @@ function volatile(target, name, descriptor) {
|
|
|
755
1209
|
},
|
|
756
1210
|
});
|
|
757
1211
|
}
|
|
758
|
-
const contextEvent = FAST.getById(
|
|
1212
|
+
const contextEvent = FAST.getById(KernelServiceId.contextEvent, () => {
|
|
759
1213
|
let current = null;
|
|
760
1214
|
return {
|
|
761
1215
|
get() {
|
|
@@ -1170,7 +1624,7 @@ let defaultSpliceStrategy = Object.freeze({
|
|
|
1170
1624
|
if (changes === void 0) {
|
|
1171
1625
|
return emptyArray;
|
|
1172
1626
|
}
|
|
1173
|
-
return
|
|
1627
|
+
return project(current, changes);
|
|
1174
1628
|
}
|
|
1175
1629
|
return resetSplices;
|
|
1176
1630
|
},
|
|
@@ -1370,34 +1824,115 @@ function lengthOf(array) {
|
|
|
1370
1824
|
return array.length;
|
|
1371
1825
|
}
|
|
1372
1826
|
|
|
1373
|
-
const styleSheetCache = new Map();
|
|
1374
|
-
let DefaultStyleStrategy;
|
|
1375
|
-
function reduceStyles(styles) {
|
|
1376
|
-
return styles
|
|
1377
|
-
.map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
|
|
1378
|
-
.reduce((prev, curr) => prev.concat(curr), []);
|
|
1379
|
-
}
|
|
1380
1827
|
/**
|
|
1381
|
-
*
|
|
1828
|
+
* Captures a binding expression along with related information and capabilities.
|
|
1829
|
+
*
|
|
1382
1830
|
* @public
|
|
1383
1831
|
*/
|
|
1384
|
-
class
|
|
1832
|
+
class Binding {
|
|
1385
1833
|
/**
|
|
1386
|
-
* Creates
|
|
1387
|
-
* @param
|
|
1834
|
+
* Creates a binding.
|
|
1835
|
+
* @param evaluate - Evaluates the binding.
|
|
1836
|
+
* @param policy - The security policy to associate with this binding.
|
|
1837
|
+
* @param isVolatile - Indicates whether the binding is volatile.
|
|
1388
1838
|
*/
|
|
1389
|
-
constructor(
|
|
1390
|
-
this.
|
|
1391
|
-
this.
|
|
1392
|
-
this.
|
|
1393
|
-
this.behaviors = styles
|
|
1394
|
-
.map((x) => x instanceof ElementStyles ? x.behaviors : null)
|
|
1395
|
-
.reduce((prev, curr) => (curr === null ? prev : prev === null ? curr : prev.concat(curr)), null);
|
|
1839
|
+
constructor(evaluate, policy, isVolatile = false) {
|
|
1840
|
+
this.evaluate = evaluate;
|
|
1841
|
+
this.policy = policy;
|
|
1842
|
+
this.isVolatile = isVolatile;
|
|
1396
1843
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
class OneWayBinding extends Binding {
|
|
1847
|
+
createObserver(subscriber) {
|
|
1848
|
+
return Observable.binding(this.evaluate, subscriber, this.isVolatile);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Creates an standard binding.
|
|
1853
|
+
* @param expression - The binding to refresh when changed.
|
|
1854
|
+
* @param policy - The security policy to associate with th binding.
|
|
1855
|
+
* @param isVolatile - Indicates whether the binding is volatile or not.
|
|
1856
|
+
* @returns A binding configuration.
|
|
1857
|
+
* @public
|
|
1858
|
+
*/
|
|
1859
|
+
function oneWay(expression, policy, isVolatile = Observable.isVolatileBinding(expression)) {
|
|
1860
|
+
return new OneWayBinding(expression, policy, isVolatile);
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Creates an event listener binding.
|
|
1864
|
+
* @param expression - The binding to invoke when the event is raised.
|
|
1865
|
+
* @param options - Event listener options.
|
|
1866
|
+
* @returns A binding configuration.
|
|
1867
|
+
* @public
|
|
1868
|
+
*/
|
|
1869
|
+
function listener(expression, options) {
|
|
1870
|
+
const config = new OneWayBinding(expression);
|
|
1871
|
+
config.options = options;
|
|
1872
|
+
return config;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
class OneTimeBinding extends Binding {
|
|
1876
|
+
createObserver() {
|
|
1877
|
+
return this;
|
|
1878
|
+
}
|
|
1879
|
+
bind(controller) {
|
|
1880
|
+
return this.evaluate(controller.source, controller.context);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
makeSerializationNoop(OneTimeBinding);
|
|
1884
|
+
/**
|
|
1885
|
+
* Creates a one time binding
|
|
1886
|
+
* @param expression - The binding to refresh when signaled.
|
|
1887
|
+
* @param policy - The security policy to associate with th binding.
|
|
1888
|
+
* @returns A binding configuration.
|
|
1889
|
+
* @public
|
|
1890
|
+
*/
|
|
1891
|
+
function oneTime(expression, policy) {
|
|
1892
|
+
return new OneTimeBinding(expression, policy);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
/**
|
|
1896
|
+
* Normalizes the input value into a binding.
|
|
1897
|
+
* @param value - The value to create the default binding for.
|
|
1898
|
+
* @returns A binding configuration for the provided value.
|
|
1899
|
+
* @public
|
|
1900
|
+
*/
|
|
1901
|
+
function normalizeBinding$1(value) {
|
|
1902
|
+
return isFunction(value)
|
|
1903
|
+
? oneWay(value)
|
|
1904
|
+
: value instanceof Binding
|
|
1905
|
+
? value
|
|
1906
|
+
: oneTime(() => value);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
let DefaultStyleStrategy;
|
|
1910
|
+
function reduceStyles(styles) {
|
|
1911
|
+
return styles
|
|
1912
|
+
.map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
|
|
1913
|
+
.reduce((prev, curr) => prev.concat(curr), []);
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Represents styles that can be applied to a custom element.
|
|
1917
|
+
* @public
|
|
1918
|
+
*/
|
|
1919
|
+
class ElementStyles {
|
|
1920
|
+
/**
|
|
1921
|
+
* Creates an instance of ElementStyles.
|
|
1922
|
+
* @param styles - The styles that will be associated with elements.
|
|
1923
|
+
*/
|
|
1924
|
+
constructor(styles) {
|
|
1925
|
+
this.styles = styles;
|
|
1926
|
+
this.targets = new WeakSet();
|
|
1927
|
+
this._strategy = null;
|
|
1928
|
+
this.behaviors = styles
|
|
1929
|
+
.map((x) => x instanceof ElementStyles ? x.behaviors : null)
|
|
1930
|
+
.reduce((prev, curr) => (curr === null ? prev : prev === null ? curr : prev.concat(curr)), null);
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Gets the StyleStrategy associated with these element styles.
|
|
1934
|
+
*/
|
|
1935
|
+
get strategy() {
|
|
1401
1936
|
if (this._strategy === null) {
|
|
1402
1937
|
this.withStrategy(DefaultStyleStrategy);
|
|
1403
1938
|
}
|
|
@@ -1461,36 +1996,6 @@ class ElementStyles {
|
|
|
1461
1996
|
*/
|
|
1462
1997
|
ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
|
|
1463
1998
|
"replace" in CSSStyleSheet.prototype;
|
|
1464
|
-
/**
|
|
1465
|
-
* https://wicg.github.io/construct-stylesheets/
|
|
1466
|
-
* https://developers.google.com/web/updates/2019/02/constructable-stylesheets
|
|
1467
|
-
*
|
|
1468
|
-
* @internal
|
|
1469
|
-
*/
|
|
1470
|
-
class AdoptedStyleSheetsStrategy {
|
|
1471
|
-
constructor(styles) {
|
|
1472
|
-
this.sheets = styles.map((x) => {
|
|
1473
|
-
if (x instanceof CSSStyleSheet) {
|
|
1474
|
-
return x;
|
|
1475
|
-
}
|
|
1476
|
-
let sheet = styleSheetCache.get(x);
|
|
1477
|
-
if (sheet === void 0) {
|
|
1478
|
-
sheet = new CSSStyleSheet();
|
|
1479
|
-
sheet.replaceSync(x);
|
|
1480
|
-
styleSheetCache.set(x, sheet);
|
|
1481
|
-
}
|
|
1482
|
-
return sheet;
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
addStylesTo(target) {
|
|
1486
|
-
target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
|
|
1487
|
-
}
|
|
1488
|
-
removeStylesFrom(target) {
|
|
1489
|
-
const sheets = this.sheets;
|
|
1490
|
-
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
|
|
1494
1999
|
|
|
1495
2000
|
const registry$1 = createTypeRegistry();
|
|
1496
2001
|
/**
|
|
@@ -1529,6 +2034,85 @@ function cssDirective() {
|
|
|
1529
2034
|
};
|
|
1530
2035
|
}
|
|
1531
2036
|
|
|
2037
|
+
function handleChange(directive, controller, observer) {
|
|
2038
|
+
controller.source.style.setProperty(directive.targetAspect, observer.bind(controller));
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Enables bindings in CSS.
|
|
2042
|
+
*
|
|
2043
|
+
* @public
|
|
2044
|
+
*/
|
|
2045
|
+
class CSSBindingDirective {
|
|
2046
|
+
/**
|
|
2047
|
+
* Creates an instance of CSSBindingDirective.
|
|
2048
|
+
* @param dataBinding - The binding to use in CSS.
|
|
2049
|
+
* @param targetAspect - The CSS property to target.
|
|
2050
|
+
*/
|
|
2051
|
+
constructor(dataBinding, targetAspect) {
|
|
2052
|
+
this.dataBinding = dataBinding;
|
|
2053
|
+
this.targetAspect = targetAspect;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Creates a CSS fragment to interpolate into the CSS document.
|
|
2057
|
+
* @returns - the string to interpolate into CSS
|
|
2058
|
+
*/
|
|
2059
|
+
createCSS(add) {
|
|
2060
|
+
add(this);
|
|
2061
|
+
return `var(${this.targetAspect})`;
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Executed when this behavior is attached to a controller.
|
|
2065
|
+
* @param controller - Controls the behavior lifecycle.
|
|
2066
|
+
*/
|
|
2067
|
+
addedCallback(controller) {
|
|
2068
|
+
var _a;
|
|
2069
|
+
const element = controller.source;
|
|
2070
|
+
if (!element.$cssBindings) {
|
|
2071
|
+
element.$cssBindings = new Map();
|
|
2072
|
+
const setAttribute = element.setAttribute;
|
|
2073
|
+
element.setAttribute = (attr, value) => {
|
|
2074
|
+
setAttribute.call(element, attr, value);
|
|
2075
|
+
if (attr === "style") {
|
|
2076
|
+
element.$cssBindings.forEach((v, k) => handleChange(k, v.controller, v.observer));
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
const observer = (_a = controller[this.targetAspect]) !== null && _a !== void 0 ? _a : (controller[this.targetAspect] = this.dataBinding.createObserver(this, this));
|
|
2081
|
+
observer.controller = controller;
|
|
2082
|
+
controller.source.$cssBindings.set(this, { controller, observer });
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Executed when this behavior's host is connected.
|
|
2086
|
+
* @param controller - Controls the behavior lifecycle.
|
|
2087
|
+
*/
|
|
2088
|
+
connectedCallback(controller) {
|
|
2089
|
+
handleChange(this, controller, controller[this.targetAspect]);
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Executed when this behavior is detached from a controller.
|
|
2093
|
+
* @param controller - Controls the behavior lifecycle.
|
|
2094
|
+
*/
|
|
2095
|
+
removedCallback(controller) {
|
|
2096
|
+
if (controller.source.$cssBindings) {
|
|
2097
|
+
controller.source.$cssBindings.delete(this);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Called when a subject this instance has subscribed to changes.
|
|
2102
|
+
* @param subject - The subject of the change.
|
|
2103
|
+
* @param args - The event args detailing the change that occurred.
|
|
2104
|
+
*
|
|
2105
|
+
* @internal
|
|
2106
|
+
*/
|
|
2107
|
+
handleChange(_, observer) {
|
|
2108
|
+
handleChange(this, observer.controller, observer);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
CSSDirective.define(CSSBindingDirective);
|
|
2112
|
+
|
|
2113
|
+
const marker$1 = `${Math.random().toString(36).substring(2, 8)}`;
|
|
2114
|
+
let varId = 0;
|
|
2115
|
+
const nextCSSVariable = () => `--v${marker$1}${++varId}`;
|
|
1532
2116
|
function collectStyles(strings, values) {
|
|
1533
2117
|
const styles = [];
|
|
1534
2118
|
let cssString = "";
|
|
@@ -1539,7 +2123,13 @@ function collectStyles(strings, values) {
|
|
|
1539
2123
|
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
1540
2124
|
cssString += strings[i];
|
|
1541
2125
|
let value = values[i];
|
|
1542
|
-
if (
|
|
2126
|
+
if (isFunction(value)) {
|
|
2127
|
+
value = new CSSBindingDirective(oneWay(value), nextCSSVariable()).createCSS(add);
|
|
2128
|
+
}
|
|
2129
|
+
else if (value instanceof Binding) {
|
|
2130
|
+
value = new CSSBindingDirective(value, nextCSSVariable()).createCSS(add);
|
|
2131
|
+
}
|
|
2132
|
+
else if (CSSDirective.getForInstance(value) !== void 0) {
|
|
1543
2133
|
value = value.createCSS(add);
|
|
1544
2134
|
}
|
|
1545
2135
|
if (value instanceof ElementStyles || value instanceof CSSStyleSheet) {
|
|
@@ -1611,68 +2201,119 @@ css.partial = (strings, ...values) => {
|
|
|
1611
2201
|
const { styles, behaviors } = collectStyles(strings, values);
|
|
1612
2202
|
return new CSSPartial(styles, behaviors);
|
|
1613
2203
|
};
|
|
1614
|
-
/**
|
|
1615
|
-
* @deprecated Use css.partial instead.
|
|
1616
|
-
* @public
|
|
1617
|
-
*/
|
|
1618
|
-
const cssPartial = css.partial;
|
|
1619
2204
|
|
|
2205
|
+
const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
|
|
2206
|
+
const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
|
|
2207
|
+
const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
|
|
2208
|
+
const repeatViewEndMarker = /fe-repeat\$\$end\$\$(\d+)\$\$fe-repeat/;
|
|
2209
|
+
const elementBoundaryStartMarker = /^(?:.{0,1000})fe-eb\$\$start\$\$(.+?)\$\$fe-eb/;
|
|
2210
|
+
const elementBoundaryEndMarker = /fe-eb\$\$end\$\$(.{0,1000})\$\$fe-eb(?:.{0,1000})$/;
|
|
2211
|
+
function isComment$1(node) {
|
|
2212
|
+
return node && node.nodeType === Node.COMMENT_NODE;
|
|
2213
|
+
}
|
|
1620
2214
|
/**
|
|
1621
|
-
*
|
|
1622
|
-
* @
|
|
2215
|
+
* Markup utilities to aid in template hydration.
|
|
2216
|
+
* @internal
|
|
1623
2217
|
*/
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
2218
|
+
const HydrationMarkup = Object.freeze({
|
|
2219
|
+
attributeMarkerName: "data-fe-b",
|
|
2220
|
+
attributeBindingSeparator: " ",
|
|
2221
|
+
contentBindingStartMarker(index, uniqueId) {
|
|
2222
|
+
return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
|
|
2223
|
+
},
|
|
2224
|
+
contentBindingEndMarker(index, uniqueId) {
|
|
2225
|
+
return `fe-b$$end$$${index}$$${uniqueId}$$fe-b`;
|
|
2226
|
+
},
|
|
2227
|
+
repeatStartMarker(index) {
|
|
2228
|
+
return `fe-repeat$$start$$${index}$$fe-repeat`;
|
|
2229
|
+
},
|
|
2230
|
+
repeatEndMarker(index) {
|
|
2231
|
+
return `fe-repeat$$end$$${index}$$fe-repeat`;
|
|
2232
|
+
},
|
|
2233
|
+
isContentBindingStartMarker(content) {
|
|
2234
|
+
return bindingStartMarker.test(content);
|
|
2235
|
+
},
|
|
2236
|
+
isContentBindingEndMarker(content) {
|
|
2237
|
+
return bindingEndMarker.test(content);
|
|
2238
|
+
},
|
|
2239
|
+
isRepeatViewStartMarker(content) {
|
|
2240
|
+
return repeatViewStartMarker.test(content);
|
|
2241
|
+
},
|
|
2242
|
+
isRepeatViewEndMarker(content) {
|
|
2243
|
+
return repeatViewEndMarker.test(content);
|
|
2244
|
+
},
|
|
2245
|
+
isElementBoundaryStartMarker(node) {
|
|
2246
|
+
return isComment$1(node) && elementBoundaryStartMarker.test(node.data.trim());
|
|
2247
|
+
},
|
|
2248
|
+
isElementBoundaryEndMarker(node) {
|
|
2249
|
+
return isComment$1(node) && elementBoundaryEndMarker.test(node.data);
|
|
2250
|
+
},
|
|
1630
2251
|
/**
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
2252
|
+
* Returns the indexes of the ViewBehaviorFactories affecting
|
|
2253
|
+
* attributes for the element, or null if no factories were found.
|
|
1633
2254
|
*/
|
|
1634
|
-
|
|
2255
|
+
parseAttributeBinding(node) {
|
|
2256
|
+
const attr = node.getAttribute(this.attributeMarkerName);
|
|
2257
|
+
return attr === null
|
|
2258
|
+
? attr
|
|
2259
|
+
: attr.split(this.attributeBindingSeparator).map(i => parseInt(i));
|
|
2260
|
+
},
|
|
1635
2261
|
/**
|
|
1636
|
-
*
|
|
1637
|
-
*
|
|
2262
|
+
* Parses the ViewBehaviorFactory index from string data. Returns
|
|
2263
|
+
* the binding index or null if the index cannot be retrieved.
|
|
1638
2264
|
*/
|
|
1639
|
-
|
|
2265
|
+
parseContentBindingStartMarker(content) {
|
|
2266
|
+
return parseIndexAndIdMarker(bindingStartMarker, content);
|
|
2267
|
+
},
|
|
2268
|
+
parseContentBindingEndMarker(content) {
|
|
2269
|
+
return parseIndexAndIdMarker(bindingEndMarker, content);
|
|
2270
|
+
},
|
|
1640
2271
|
/**
|
|
1641
|
-
*
|
|
1642
|
-
* @param element - The element to set the attribute value on.
|
|
1643
|
-
* @param attributeName - The attribute name to set.
|
|
1644
|
-
* @param value - The value of the attribute to set.
|
|
1645
|
-
* @remarks
|
|
1646
|
-
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
1647
|
-
* it is set to the provided value using the standard `setAttribute` API.
|
|
2272
|
+
* Parses the index of a repeat directive from a content string.
|
|
1648
2273
|
*/
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
2274
|
+
parseRepeatStartMarker(content) {
|
|
2275
|
+
return parseIntMarker(repeatViewStartMarker, content);
|
|
2276
|
+
},
|
|
2277
|
+
parseRepeatEndMarker(content) {
|
|
2278
|
+
return parseIntMarker(repeatViewEndMarker, content);
|
|
1653
2279
|
},
|
|
1654
2280
|
/**
|
|
1655
|
-
*
|
|
1656
|
-
* @param element - The element to set the boolean attribute value on.
|
|
1657
|
-
* @param attributeName - The attribute name to set.
|
|
1658
|
-
* @param value - The value of the attribute to set.
|
|
1659
|
-
* @remarks
|
|
1660
|
-
* If the value is true, the attribute is added; otherwise it is removed.
|
|
2281
|
+
* Parses element Id from element boundary markers
|
|
1661
2282
|
*/
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
2283
|
+
parseElementBoundaryStartMarker(content) {
|
|
2284
|
+
return parseStringMarker(elementBoundaryStartMarker, content.trim());
|
|
2285
|
+
},
|
|
2286
|
+
parseElementBoundaryEndMarker(content) {
|
|
2287
|
+
return parseStringMarker(elementBoundaryEndMarker, content);
|
|
1666
2288
|
},
|
|
1667
2289
|
});
|
|
2290
|
+
function parseIntMarker(regex, content) {
|
|
2291
|
+
const match = regex.exec(content);
|
|
2292
|
+
return match === null ? match : parseInt(match[1]);
|
|
2293
|
+
}
|
|
2294
|
+
function parseStringMarker(regex, content) {
|
|
2295
|
+
const match = regex.exec(content);
|
|
2296
|
+
return match === null ? match : match[1];
|
|
2297
|
+
}
|
|
2298
|
+
function parseIndexAndIdMarker(regex, content) {
|
|
2299
|
+
const match = regex.exec(content);
|
|
2300
|
+
return match === null ? match : [parseInt(match[1]), match[2]];
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* @internal
|
|
2304
|
+
*/
|
|
2305
|
+
const Hydratable = Symbol.for("fe-hydration");
|
|
2306
|
+
function isHydratable(value) {
|
|
2307
|
+
return value[Hydratable] === Hydratable;
|
|
2308
|
+
}
|
|
1668
2309
|
|
|
1669
2310
|
const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
|
|
1670
2311
|
const interpolationStart = `${marker}{`;
|
|
1671
2312
|
const interpolationEnd = `}${marker}`;
|
|
1672
2313
|
const interpolationEndLength = interpolationEnd.length;
|
|
1673
|
-
let id = 0;
|
|
2314
|
+
let id$1 = 0;
|
|
1674
2315
|
/** @internal */
|
|
1675
|
-
const nextId = () => `${marker}-${++id}`;
|
|
2316
|
+
const nextId = () => `${marker}-${++id$1}`;
|
|
1676
2317
|
/**
|
|
1677
2318
|
* Common APIs related to markup generation.
|
|
1678
2319
|
* @public
|
|
@@ -1742,67 +2383,6 @@ const Parser = Object.freeze({
|
|
|
1742
2383
|
},
|
|
1743
2384
|
});
|
|
1744
2385
|
|
|
1745
|
-
/**
|
|
1746
|
-
* Bridges between ViewBehaviors and HostBehaviors, enabling a host to
|
|
1747
|
-
* control ViewBehaviors.
|
|
1748
|
-
* @public
|
|
1749
|
-
*/
|
|
1750
|
-
const ViewBehaviorOrchestrator = Object.freeze({
|
|
1751
|
-
/**
|
|
1752
|
-
* Creates a ViewBehaviorOrchestrator.
|
|
1753
|
-
* @param source - The source to to associate behaviors with.
|
|
1754
|
-
* @returns A ViewBehaviorOrchestrator.
|
|
1755
|
-
*/
|
|
1756
|
-
create(source) {
|
|
1757
|
-
const behaviors = [];
|
|
1758
|
-
const targets = {};
|
|
1759
|
-
let unbindables = null;
|
|
1760
|
-
let isConnected = false;
|
|
1761
|
-
return {
|
|
1762
|
-
source,
|
|
1763
|
-
context: ExecutionContext.default,
|
|
1764
|
-
targets,
|
|
1765
|
-
get isBound() {
|
|
1766
|
-
return isConnected;
|
|
1767
|
-
},
|
|
1768
|
-
addBehaviorFactory(factory, target) {
|
|
1769
|
-
const nodeId = factory.nodeId || (factory.nodeId = nextId());
|
|
1770
|
-
factory.id || (factory.id = nextId());
|
|
1771
|
-
this.addTarget(nodeId, target);
|
|
1772
|
-
this.addBehavior(factory.createBehavior());
|
|
1773
|
-
},
|
|
1774
|
-
addTarget(nodeId, target) {
|
|
1775
|
-
targets[nodeId] = target;
|
|
1776
|
-
},
|
|
1777
|
-
addBehavior(behavior) {
|
|
1778
|
-
behaviors.push(behavior);
|
|
1779
|
-
if (isConnected) {
|
|
1780
|
-
behavior.bind(this);
|
|
1781
|
-
}
|
|
1782
|
-
},
|
|
1783
|
-
onUnbind(unbindable) {
|
|
1784
|
-
if (unbindables === null) {
|
|
1785
|
-
unbindables = [];
|
|
1786
|
-
}
|
|
1787
|
-
unbindables.push(unbindable);
|
|
1788
|
-
},
|
|
1789
|
-
connectedCallback(controller) {
|
|
1790
|
-
if (!isConnected) {
|
|
1791
|
-
isConnected = true;
|
|
1792
|
-
behaviors.forEach(x => x.bind(this));
|
|
1793
|
-
}
|
|
1794
|
-
},
|
|
1795
|
-
disconnectedCallback(controller) {
|
|
1796
|
-
if (isConnected) {
|
|
1797
|
-
isConnected = false;
|
|
1798
|
-
if (unbindables !== null) {
|
|
1799
|
-
unbindables.forEach(x => x.unbind(this));
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
},
|
|
1803
|
-
};
|
|
1804
|
-
},
|
|
1805
|
-
});
|
|
1806
2386
|
const registry = createTypeRegistry();
|
|
1807
2387
|
/**
|
|
1808
2388
|
* Instructs the template engine to apply behavior to a node.
|
|
@@ -1830,67 +2410,6 @@ const HTMLDirective = Object.freeze({
|
|
|
1830
2410
|
registry.register(options);
|
|
1831
2411
|
return type;
|
|
1832
2412
|
},
|
|
1833
|
-
});
|
|
1834
|
-
/**
|
|
1835
|
-
* Decorator: Defines an HTMLDirective.
|
|
1836
|
-
* @param options - Provides options that specify the directive's application.
|
|
1837
|
-
* @public
|
|
1838
|
-
*/
|
|
1839
|
-
function htmlDirective(options) {
|
|
1840
|
-
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
1841
|
-
return function (type) {
|
|
1842
|
-
HTMLDirective.define(type, options);
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
/**
|
|
1846
|
-
* Captures a binding expression along with related information and capabilities.
|
|
1847
|
-
*
|
|
1848
|
-
* @public
|
|
1849
|
-
*/
|
|
1850
|
-
class Binding {
|
|
1851
|
-
/**
|
|
1852
|
-
* Creates a binding.
|
|
1853
|
-
* @param evaluate - Evaluates the binding.
|
|
1854
|
-
* @param isVolatile - Indicates whether the binding is volatile.
|
|
1855
|
-
*/
|
|
1856
|
-
constructor(evaluate, isVolatile = false) {
|
|
1857
|
-
this.evaluate = evaluate;
|
|
1858
|
-
this.isVolatile = isVolatile;
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
/**
|
|
1862
|
-
* The type of HTML aspect to target.
|
|
1863
|
-
* @public
|
|
1864
|
-
*/
|
|
1865
|
-
const Aspect = Object.freeze({
|
|
1866
|
-
/**
|
|
1867
|
-
* Not aspected.
|
|
1868
|
-
*/
|
|
1869
|
-
none: 0,
|
|
1870
|
-
/**
|
|
1871
|
-
* An attribute.
|
|
1872
|
-
*/
|
|
1873
|
-
attribute: 1,
|
|
1874
|
-
/**
|
|
1875
|
-
* A boolean attribute.
|
|
1876
|
-
*/
|
|
1877
|
-
booleanAttribute: 2,
|
|
1878
|
-
/**
|
|
1879
|
-
* A property.
|
|
1880
|
-
*/
|
|
1881
|
-
property: 3,
|
|
1882
|
-
/**
|
|
1883
|
-
* Content
|
|
1884
|
-
*/
|
|
1885
|
-
content: 4,
|
|
1886
|
-
/**
|
|
1887
|
-
* A token list.
|
|
1888
|
-
*/
|
|
1889
|
-
tokenList: 5,
|
|
1890
|
-
/**
|
|
1891
|
-
* An event.
|
|
1892
|
-
*/
|
|
1893
|
-
event: 6,
|
|
1894
2413
|
/**
|
|
1895
2414
|
*
|
|
1896
2415
|
* @param directive - The directive to assign the aspect to.
|
|
@@ -1898,48 +2417,46 @@ const Aspect = Object.freeze({
|
|
|
1898
2417
|
* @remarks
|
|
1899
2418
|
* If a falsy value is provided, then the content aspect will be assigned.
|
|
1900
2419
|
*/
|
|
1901
|
-
|
|
2420
|
+
assignAspect(directive, value) {
|
|
1902
2421
|
if (!value) {
|
|
1903
|
-
directive.aspectType =
|
|
2422
|
+
directive.aspectType = DOMAspect.content;
|
|
1904
2423
|
return;
|
|
1905
2424
|
}
|
|
1906
2425
|
directive.sourceAspect = value;
|
|
1907
2426
|
switch (value[0]) {
|
|
1908
2427
|
case ":":
|
|
1909
2428
|
directive.targetAspect = value.substring(1);
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
case "classList":
|
|
1915
|
-
directive.aspectType = Aspect.tokenList;
|
|
1916
|
-
break;
|
|
1917
|
-
default:
|
|
1918
|
-
directive.aspectType = Aspect.property;
|
|
1919
|
-
break;
|
|
1920
|
-
}
|
|
2429
|
+
directive.aspectType =
|
|
2430
|
+
directive.targetAspect === "classList"
|
|
2431
|
+
? DOMAspect.tokenList
|
|
2432
|
+
: DOMAspect.property;
|
|
1921
2433
|
break;
|
|
1922
2434
|
case "?":
|
|
1923
2435
|
directive.targetAspect = value.substring(1);
|
|
1924
|
-
directive.aspectType =
|
|
2436
|
+
directive.aspectType = DOMAspect.booleanAttribute;
|
|
1925
2437
|
break;
|
|
1926
2438
|
case "@":
|
|
1927
2439
|
directive.targetAspect = value.substring(1);
|
|
1928
|
-
directive.aspectType =
|
|
2440
|
+
directive.aspectType = DOMAspect.event;
|
|
1929
2441
|
break;
|
|
1930
2442
|
default:
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
directive.aspectType = Aspect.property;
|
|
1934
|
-
}
|
|
1935
|
-
else {
|
|
1936
|
-
directive.targetAspect = value;
|
|
1937
|
-
directive.aspectType = Aspect.attribute;
|
|
1938
|
-
}
|
|
2443
|
+
directive.targetAspect = value;
|
|
2444
|
+
directive.aspectType = DOMAspect.attribute;
|
|
1939
2445
|
break;
|
|
1940
2446
|
}
|
|
1941
2447
|
},
|
|
1942
2448
|
});
|
|
2449
|
+
/**
|
|
2450
|
+
* Decorator: Defines an HTMLDirective.
|
|
2451
|
+
* @param options - Provides options that specify the directive's application.
|
|
2452
|
+
* @public
|
|
2453
|
+
*/
|
|
2454
|
+
function htmlDirective(options) {
|
|
2455
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
2456
|
+
return function (type) {
|
|
2457
|
+
HTMLDirective.define(type, options);
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
1943
2460
|
/**
|
|
1944
2461
|
* A base class used for attribute directives that don't need internal state.
|
|
1945
2462
|
* @public
|
|
@@ -1951,10 +2468,6 @@ class StatelessAttachedAttributeDirective {
|
|
|
1951
2468
|
*/
|
|
1952
2469
|
constructor(options) {
|
|
1953
2470
|
this.options = options;
|
|
1954
|
-
/**
|
|
1955
|
-
* The unique id of the factory.
|
|
1956
|
-
*/
|
|
1957
|
-
this.id = nextId();
|
|
1958
2471
|
}
|
|
1959
2472
|
/**
|
|
1960
2473
|
* Creates a placeholder string based on the directive's index within the template.
|
|
@@ -1972,318 +2485,201 @@ class StatelessAttachedAttributeDirective {
|
|
|
1972
2485
|
createBehavior() {
|
|
1973
2486
|
return this;
|
|
1974
2487
|
}
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
bind(controller) {
|
|
1996
|
-
return this.evaluate(controller.source, controller.context);
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
function updateContent(target, aspect, value, controller) {
|
|
2000
|
-
// If there's no actual value, then this equates to the
|
|
2001
|
-
// empty string for the purposes of content bindings.
|
|
2002
|
-
if (value === null || value === undefined) {
|
|
2003
|
-
value = "";
|
|
2004
|
-
}
|
|
2005
|
-
// If the value has a "create" method, then it's a ContentTemplate.
|
|
2006
|
-
if (value.create) {
|
|
2007
|
-
target.textContent = "";
|
|
2008
|
-
let view = target.$fastView;
|
|
2009
|
-
// If there's no previous view that we might be able to
|
|
2010
|
-
// reuse then create a new view from the template.
|
|
2011
|
-
if (view === void 0) {
|
|
2012
|
-
view = value.create();
|
|
2013
|
-
}
|
|
2014
|
-
else {
|
|
2015
|
-
// If there is a previous view, but it wasn't created
|
|
2016
|
-
// from the same template as the new value, then we
|
|
2017
|
-
// need to remove the old view if it's still in the DOM
|
|
2018
|
-
// and create a new view from the template.
|
|
2019
|
-
if (target.$fastTemplate !== value) {
|
|
2020
|
-
if (view.isComposed) {
|
|
2021
|
-
view.remove();
|
|
2022
|
-
view.unbind();
|
|
2023
|
-
}
|
|
2024
|
-
view = value.create();
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
// It's possible that the value is the same as the previous template
|
|
2028
|
-
// and that there's actually no need to compose it.
|
|
2029
|
-
if (!view.isComposed) {
|
|
2030
|
-
view.isComposed = true;
|
|
2031
|
-
view.bind(controller.source);
|
|
2032
|
-
view.insertBefore(target);
|
|
2033
|
-
target.$fastView = view;
|
|
2034
|
-
target.$fastTemplate = value;
|
|
2035
|
-
}
|
|
2036
|
-
else if (view.needsBindOnly) {
|
|
2037
|
-
view.needsBindOnly = false;
|
|
2038
|
-
view.bind(controller.source);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
else {
|
|
2042
|
-
const view = target.$fastView;
|
|
2043
|
-
// If there is a view and it's currently composed into
|
|
2044
|
-
// the DOM, then we need to remove it.
|
|
2045
|
-
if (view !== void 0 && view.isComposed) {
|
|
2046
|
-
view.isComposed = false;
|
|
2047
|
-
view.remove();
|
|
2048
|
-
if (view.needsBindOnly) {
|
|
2049
|
-
view.needsBindOnly = false;
|
|
2050
|
-
}
|
|
2051
|
-
else {
|
|
2052
|
-
view.unbind();
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
target.textContent = value;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
function updateTokenList(target, aspect, value) {
|
|
2059
|
-
var _a;
|
|
2060
|
-
const lookup = `${this.id}-t`;
|
|
2061
|
-
const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
|
|
2062
|
-
const versions = state.v;
|
|
2063
|
-
let currentVersion = state.c;
|
|
2064
|
-
const tokenList = target[aspect];
|
|
2065
|
-
// Add the classes, tracking the version at which they were added.
|
|
2066
|
-
if (value !== null && value !== undefined && value.length) {
|
|
2067
|
-
const names = value.split(/\s+/);
|
|
2068
|
-
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
2069
|
-
const currentName = names[i];
|
|
2070
|
-
if (currentName === "") {
|
|
2071
|
-
continue;
|
|
2072
|
-
}
|
|
2073
|
-
versions[currentName] = currentVersion;
|
|
2074
|
-
tokenList.add(currentName);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
state.v = currentVersion + 1;
|
|
2078
|
-
// If this is the first call to add classes, there's no need to remove old ones.
|
|
2079
|
-
if (currentVersion === 0) {
|
|
2080
|
-
return;
|
|
2081
|
-
}
|
|
2082
|
-
// Remove classes from the previous version.
|
|
2083
|
-
currentVersion -= 1;
|
|
2084
|
-
for (const name in versions) {
|
|
2085
|
-
if (versions[name] === currentVersion) {
|
|
2086
|
-
tokenList.remove(name);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
const setProperty = (t, a, v) => (t[a] = v);
|
|
2091
|
-
const eventTarget = () => void 0;
|
|
2092
|
-
/**
|
|
2093
|
-
* A directive that applies bindings.
|
|
2094
|
-
* @public
|
|
2095
|
-
*/
|
|
2096
|
-
class HTMLBindingDirective {
|
|
2097
|
-
/**
|
|
2098
|
-
* Creates an instance of HTMLBindingDirective.
|
|
2099
|
-
* @param dataBinding - The binding configuration to apply.
|
|
2100
|
-
*/
|
|
2101
|
-
constructor(dataBinding) {
|
|
2102
|
-
this.dataBinding = dataBinding;
|
|
2103
|
-
this.updateTarget = null;
|
|
2104
|
-
/**
|
|
2105
|
-
* The unique id of the factory.
|
|
2106
|
-
*/
|
|
2107
|
-
this.id = nextId();
|
|
2108
|
-
/**
|
|
2109
|
-
* The type of aspect to target.
|
|
2110
|
-
*/
|
|
2111
|
-
this.aspectType = Aspect.content;
|
|
2112
|
-
/** @internal */
|
|
2113
|
-
this.bind = this.bindDefault;
|
|
2114
|
-
this.data = `${this.id}-d`;
|
|
2115
|
-
}
|
|
2116
|
-
/**
|
|
2117
|
-
* Creates HTML to be used within a template.
|
|
2118
|
-
* @param add - Can be used to add behavior factories to a template.
|
|
2119
|
-
*/
|
|
2120
|
-
createHTML(add) {
|
|
2121
|
-
return Markup.interpolation(add(this));
|
|
2122
|
-
}
|
|
2123
|
-
/**
|
|
2124
|
-
* Creates a behavior.
|
|
2125
|
-
*/
|
|
2126
|
-
createBehavior() {
|
|
2127
|
-
if (this.updateTarget === null) {
|
|
2128
|
-
if (this.targetAspect === "innerHTML") {
|
|
2129
|
-
this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
|
|
2130
|
-
}
|
|
2131
|
-
switch (this.aspectType) {
|
|
2132
|
-
case 1:
|
|
2133
|
-
this.updateTarget = DOM.setAttribute;
|
|
2134
|
-
break;
|
|
2135
|
-
case 2:
|
|
2136
|
-
this.updateTarget = DOM.setBooleanAttribute;
|
|
2137
|
-
break;
|
|
2138
|
-
case 3:
|
|
2139
|
-
this.updateTarget = setProperty;
|
|
2140
|
-
break;
|
|
2141
|
-
case 4:
|
|
2142
|
-
this.bind = this.bindContent;
|
|
2143
|
-
this.updateTarget = updateContent;
|
|
2144
|
-
break;
|
|
2145
|
-
case 5:
|
|
2146
|
-
this.updateTarget = updateTokenList;
|
|
2147
|
-
break;
|
|
2148
|
-
case 6:
|
|
2149
|
-
this.bind = this.bindEvent;
|
|
2150
|
-
this.updateTarget = eventTarget;
|
|
2151
|
-
break;
|
|
2152
|
-
default:
|
|
2153
|
-
throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
return this;
|
|
2157
|
-
}
|
|
2158
|
-
/** @internal */
|
|
2159
|
-
bindDefault(controller) {
|
|
2160
|
-
var _a;
|
|
2161
|
-
const target = controller.targets[this.nodeId];
|
|
2162
|
-
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
2163
|
-
observer.target = target;
|
|
2164
|
-
observer.controller = controller;
|
|
2165
|
-
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
2166
|
-
if (this.updateTarget === updateContent) {
|
|
2167
|
-
controller.onUnbind(this);
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
/** @internal */
|
|
2171
|
-
bindContent(controller) {
|
|
2172
|
-
this.bindDefault(controller);
|
|
2173
|
-
controller.onUnbind(this);
|
|
2174
|
-
}
|
|
2175
|
-
/** @internal */
|
|
2176
|
-
bindEvent(controller) {
|
|
2177
|
-
const target = controller.targets[this.nodeId];
|
|
2178
|
-
target[this.data] = controller;
|
|
2179
|
-
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
2180
|
-
}
|
|
2181
|
-
/** @internal */
|
|
2182
|
-
unbind(controller) {
|
|
2183
|
-
const target = controller.targets[this.nodeId];
|
|
2184
|
-
const view = target.$fastView;
|
|
2185
|
-
if (view !== void 0 && view.isComposed) {
|
|
2186
|
-
view.unbind();
|
|
2187
|
-
view.needsBindOnly = true;
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
/** @internal */
|
|
2191
|
-
handleEvent(event) {
|
|
2192
|
-
const target = event.currentTarget;
|
|
2193
|
-
ExecutionContext.setEvent(event);
|
|
2194
|
-
const controller = target[this.data];
|
|
2195
|
-
const result = this.dataBinding.evaluate(controller.source, controller.context);
|
|
2196
|
-
ExecutionContext.setEvent(null);
|
|
2197
|
-
if (result !== true) {
|
|
2198
|
-
event.preventDefault();
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
/** @internal */
|
|
2202
|
-
handleChange(binding, observer) {
|
|
2203
|
-
const target = observer.target;
|
|
2204
|
-
const controller = observer.controller;
|
|
2205
|
-
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
2488
|
+
}
|
|
2489
|
+
makeSerializationNoop(StatelessAttachedAttributeDirective);
|
|
2490
|
+
|
|
2491
|
+
class HydrationTargetElementError extends Error {
|
|
2492
|
+
constructor(
|
|
2493
|
+
/**
|
|
2494
|
+
* The error message
|
|
2495
|
+
*/
|
|
2496
|
+
message,
|
|
2497
|
+
/**
|
|
2498
|
+
* The Compiled View Behavior Factories that belong to the view.
|
|
2499
|
+
*/
|
|
2500
|
+
factories,
|
|
2501
|
+
/**
|
|
2502
|
+
* The node to target factory.
|
|
2503
|
+
*/
|
|
2504
|
+
node) {
|
|
2505
|
+
super(message);
|
|
2506
|
+
this.factories = factories;
|
|
2507
|
+
this.node = node;
|
|
2206
2508
|
}
|
|
2207
2509
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
* @returns A binding configuration.
|
|
2214
|
-
* @public
|
|
2215
|
-
*/
|
|
2216
|
-
function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
|
|
2217
|
-
return new OnChangeBinding(binding, isVolatile);
|
|
2510
|
+
function isComment(node) {
|
|
2511
|
+
return node.nodeType === Node.COMMENT_NODE;
|
|
2512
|
+
}
|
|
2513
|
+
function isText(node) {
|
|
2514
|
+
return node.nodeType === Node.TEXT_NODE;
|
|
2218
2515
|
}
|
|
2219
2516
|
/**
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2222
|
-
* @
|
|
2223
|
-
* @
|
|
2517
|
+
* Returns a range object inclusive of all nodes including and between the
|
|
2518
|
+
* provided first and last node.
|
|
2519
|
+
* @param first - The first node
|
|
2520
|
+
* @param last - This last node
|
|
2521
|
+
* @returns
|
|
2224
2522
|
*/
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2523
|
+
function createRangeForNodes(first, last) {
|
|
2524
|
+
const range = document.createRange();
|
|
2525
|
+
range.setStart(first, 0);
|
|
2526
|
+
// The lastIndex should be inclusive of the end of the lastChild. Obtain offset based
|
|
2527
|
+
// on usageNotes: https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd#usage_notes
|
|
2528
|
+
range.setEnd(last, isComment(last) || isText(last) ? last.data.length : last.childNodes.length);
|
|
2529
|
+
return range;
|
|
2530
|
+
}
|
|
2531
|
+
function isShadowRoot(node) {
|
|
2532
|
+
return node instanceof DocumentFragment && "mode" in node;
|
|
2227
2533
|
}
|
|
2228
2534
|
/**
|
|
2229
|
-
*
|
|
2230
|
-
* @param
|
|
2231
|
-
* @param
|
|
2232
|
-
* @
|
|
2233
|
-
* @
|
|
2535
|
+
* Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
|
|
2536
|
+
* @param firstNode - The first node of the view.
|
|
2537
|
+
* @param lastNode - The last node of the view.
|
|
2538
|
+
* @param factories - The Compiled View Behavior Factories that belong to the view.
|
|
2539
|
+
* @returns - A {@link ViewBehaviorTargets } object for the factories in the view.
|
|
2234
2540
|
*/
|
|
2235
|
-
function
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
|
|
2541
|
+
function buildViewBindingTargets(firstNode, lastNode, factories) {
|
|
2542
|
+
const range = createRangeForNodes(firstNode, lastNode);
|
|
2543
|
+
const treeRoot = range.commonAncestorContainer;
|
|
2544
|
+
const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
|
|
2545
|
+
acceptNode(node) {
|
|
2546
|
+
return range.comparePoint(node, 0) === 0
|
|
2547
|
+
? NodeFilter.FILTER_ACCEPT
|
|
2548
|
+
: NodeFilter.FILTER_REJECT;
|
|
2549
|
+
},
|
|
2550
|
+
});
|
|
2551
|
+
const targets = {};
|
|
2552
|
+
const boundaries = {};
|
|
2553
|
+
let node = (walker.currentNode = firstNode);
|
|
2554
|
+
while (node !== null) {
|
|
2555
|
+
switch (node.nodeType) {
|
|
2556
|
+
case Node.ELEMENT_NODE: {
|
|
2557
|
+
targetElement(node, factories, targets);
|
|
2558
|
+
break;
|
|
2559
|
+
}
|
|
2560
|
+
case Node.COMMENT_NODE: {
|
|
2561
|
+
targetComment(node, walker, factories, targets, boundaries);
|
|
2562
|
+
break;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
node = walker.nextNode();
|
|
2566
|
+
}
|
|
2567
|
+
range.detach();
|
|
2568
|
+
return { targets, boundaries };
|
|
2569
|
+
}
|
|
2570
|
+
function targetElement(node, factories, targets) {
|
|
2571
|
+
// Check for attributes and map any factories.
|
|
2572
|
+
const attrFactoryIds = HydrationMarkup.parseAttributeBinding(node);
|
|
2573
|
+
if (attrFactoryIds !== null) {
|
|
2574
|
+
for (const id of attrFactoryIds) {
|
|
2575
|
+
if (!factories[id]) {
|
|
2576
|
+
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);
|
|
2577
|
+
}
|
|
2578
|
+
targetFactory(factories[id], node, targets);
|
|
2579
|
+
}
|
|
2580
|
+
node.removeAttribute(HydrationMarkup.attributeMarkerName);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
function targetComment(node, walker, factories, targets, boundaries) {
|
|
2584
|
+
if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
|
|
2585
|
+
skipToElementBoundaryEndMarker(node, walker);
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
if (HydrationMarkup.isContentBindingStartMarker(node.data)) {
|
|
2589
|
+
const parsed = HydrationMarkup.parseContentBindingStartMarker(node.data);
|
|
2590
|
+
if (parsed === null) {
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
const [index, id] = parsed;
|
|
2594
|
+
const factory = factories[index];
|
|
2595
|
+
const nodes = [];
|
|
2596
|
+
let current = walker.nextSibling();
|
|
2597
|
+
node.data = "";
|
|
2598
|
+
const first = current;
|
|
2599
|
+
// Search for the binding end marker that closes the binding.
|
|
2600
|
+
while (current !== null) {
|
|
2601
|
+
if (isComment(current)) {
|
|
2602
|
+
const parsed = HydrationMarkup.parseContentBindingEndMarker(current.data);
|
|
2603
|
+
if (parsed && parsed[1] === id) {
|
|
2604
|
+
break;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
nodes.push(current);
|
|
2608
|
+
current = walker.nextSibling();
|
|
2609
|
+
}
|
|
2610
|
+
if (current === null) {
|
|
2611
|
+
const root = node.getRootNode();
|
|
2612
|
+
throw new Error(`Error hydrating Comment node inside "${isShadowRoot(root) ? root.host.nodeName : root.nodeName}".`);
|
|
2613
|
+
}
|
|
2614
|
+
current.data = "";
|
|
2615
|
+
if (nodes.length === 1 && isText(nodes[0])) {
|
|
2616
|
+
targetFactory(factory, nodes[0], targets);
|
|
2617
|
+
}
|
|
2618
|
+
else {
|
|
2619
|
+
// If current === first, it means there is no content in
|
|
2620
|
+
// the view. This happens when a `when` directive evaluates false,
|
|
2621
|
+
// or whenever a content binding returns null or undefined.
|
|
2622
|
+
// In that case, there will never be any content
|
|
2623
|
+
// to hydrate and Binding can simply create a HTMLView
|
|
2624
|
+
// whenever it needs to.
|
|
2625
|
+
if (current !== first && current.previousSibling !== null) {
|
|
2626
|
+
boundaries[factory.targetNodeId] = {
|
|
2627
|
+
first,
|
|
2628
|
+
last: current.previousSibling,
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
// Binding evaluates to null / undefined or a template.
|
|
2632
|
+
// If binding revaluates to string, it will replace content in target
|
|
2633
|
+
// So we always insert a text node to ensure that
|
|
2634
|
+
// text content binding will be written to this text node instead of comment
|
|
2635
|
+
const dummyTextNode = current.parentNode.insertBefore(document.createTextNode(""), current);
|
|
2636
|
+
targetFactory(factory, dummyTextNode, targets);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2239
2639
|
}
|
|
2240
2640
|
/**
|
|
2241
|
-
*
|
|
2242
|
-
* @param
|
|
2243
|
-
* @
|
|
2244
|
-
* @public
|
|
2641
|
+
* Moves TreeWalker to element boundary end marker
|
|
2642
|
+
* @param node - element boundary start marker node
|
|
2643
|
+
* @param walker - tree walker
|
|
2245
2644
|
*/
|
|
2246
|
-
function
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2645
|
+
function skipToElementBoundaryEndMarker(node, walker) {
|
|
2646
|
+
const id = HydrationMarkup.parseElementBoundaryStartMarker(node.data);
|
|
2647
|
+
let current = walker.nextSibling();
|
|
2648
|
+
while (current !== null) {
|
|
2649
|
+
if (isComment(current)) {
|
|
2650
|
+
const parsed = HydrationMarkup.parseElementBoundaryEndMarker(current.data);
|
|
2651
|
+
if (parsed && parsed === id) {
|
|
2652
|
+
break;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
current = walker.nextSibling();
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
function targetFactory(factory, node, targets) {
|
|
2659
|
+
if (factory.targetNodeId === undefined) {
|
|
2660
|
+
// Dev error, this shouldn't ever be thrown
|
|
2661
|
+
throw new Error("Factory could not be target to the node");
|
|
2662
|
+
}
|
|
2663
|
+
targets[factory.targetNodeId] = node;
|
|
2252
2664
|
}
|
|
2253
2665
|
|
|
2666
|
+
var _a;
|
|
2254
2667
|
function removeNodeSequence(firstNode, lastNode) {
|
|
2255
2668
|
const parent = firstNode.parentNode;
|
|
2256
2669
|
let current = firstNode;
|
|
2257
2670
|
let next;
|
|
2258
2671
|
while (current !== lastNode) {
|
|
2259
2672
|
next = current.nextSibling;
|
|
2673
|
+
if (!next) {
|
|
2674
|
+
throw new Error(`Unmatched first/last child inside "${lastNode.getRootNode().host.nodeName}".`);
|
|
2675
|
+
}
|
|
2260
2676
|
parent.removeChild(current);
|
|
2261
2677
|
current = next;
|
|
2262
2678
|
}
|
|
2263
2679
|
parent.removeChild(lastNode);
|
|
2264
2680
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
* @public
|
|
2268
|
-
*/
|
|
2269
|
-
class HTMLView {
|
|
2270
|
-
/**
|
|
2271
|
-
* Constructs an instance of HTMLView.
|
|
2272
|
-
* @param fragment - The html fragment that contains the nodes for this view.
|
|
2273
|
-
* @param behaviors - The behaviors to be applied to this view.
|
|
2274
|
-
*/
|
|
2275
|
-
constructor(fragment, factories, targets) {
|
|
2276
|
-
this.fragment = fragment;
|
|
2277
|
-
this.factories = factories;
|
|
2278
|
-
this.targets = targets;
|
|
2279
|
-
this.behaviors = null;
|
|
2280
|
-
this.unbindables = [];
|
|
2281
|
-
/**
|
|
2282
|
-
* The data that the view is bound to.
|
|
2283
|
-
*/
|
|
2284
|
-
this.source = null;
|
|
2285
|
-
this.isBound = false;
|
|
2286
|
-
this.selfContained = false;
|
|
2681
|
+
class DefaultExecutionContext {
|
|
2682
|
+
constructor() {
|
|
2287
2683
|
/**
|
|
2288
2684
|
* The index of the current item within a repeat context.
|
|
2289
2685
|
*/
|
|
@@ -2292,14 +2688,6 @@ class HTMLView {
|
|
|
2292
2688
|
* The length of the current collection within a repeat context.
|
|
2293
2689
|
*/
|
|
2294
2690
|
this.length = 0;
|
|
2295
|
-
this.firstChild = fragment.firstChild;
|
|
2296
|
-
this.lastChild = fragment.lastChild;
|
|
2297
|
-
}
|
|
2298
|
-
/**
|
|
2299
|
-
* The execution context the view is running within.
|
|
2300
|
-
*/
|
|
2301
|
-
get context() {
|
|
2302
|
-
return this;
|
|
2303
2691
|
}
|
|
2304
2692
|
/**
|
|
2305
2693
|
* The current event within an event handler.
|
|
@@ -2354,6 +2742,43 @@ class HTMLView {
|
|
|
2354
2742
|
eventTarget() {
|
|
2355
2743
|
return this.event.target;
|
|
2356
2744
|
}
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* The standard View implementation, which also implements ElementView and SyntheticView.
|
|
2748
|
+
* @public
|
|
2749
|
+
*/
|
|
2750
|
+
class HTMLView extends DefaultExecutionContext {
|
|
2751
|
+
/**
|
|
2752
|
+
* Constructs an instance of HTMLView.
|
|
2753
|
+
* @param fragment - The html fragment that contains the nodes for this view.
|
|
2754
|
+
* @param behaviors - The behaviors to be applied to this view.
|
|
2755
|
+
*/
|
|
2756
|
+
constructor(fragment, factories, targets) {
|
|
2757
|
+
super();
|
|
2758
|
+
this.fragment = fragment;
|
|
2759
|
+
this.factories = factories;
|
|
2760
|
+
this.targets = targets;
|
|
2761
|
+
this.behaviors = null;
|
|
2762
|
+
this.unbindables = [];
|
|
2763
|
+
/**
|
|
2764
|
+
* The data that the view is bound to.
|
|
2765
|
+
*/
|
|
2766
|
+
this.source = null;
|
|
2767
|
+
/**
|
|
2768
|
+
* Indicates whether the controller is bound.
|
|
2769
|
+
*/
|
|
2770
|
+
this.isBound = false;
|
|
2771
|
+
/**
|
|
2772
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
2773
|
+
*/
|
|
2774
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
2775
|
+
/**
|
|
2776
|
+
* The execution context the view is running within.
|
|
2777
|
+
*/
|
|
2778
|
+
this.context = this;
|
|
2779
|
+
this.firstChild = fragment.firstChild;
|
|
2780
|
+
this.lastChild = fragment.lastChild;
|
|
2781
|
+
}
|
|
2357
2782
|
/**
|
|
2358
2783
|
* Appends the view's DOM nodes to the referenced node.
|
|
2359
2784
|
* @param node - The parent node to append the view's DOM nodes to.
|
|
@@ -2416,13 +2841,14 @@ class HTMLView {
|
|
|
2416
2841
|
* @param source - The binding source for the view's binding behaviors.
|
|
2417
2842
|
* @param context - The execution context to run the behaviors within.
|
|
2418
2843
|
*/
|
|
2419
|
-
bind(source) {
|
|
2844
|
+
bind(source, context = this) {
|
|
2420
2845
|
if (this.source === source) {
|
|
2421
2846
|
return;
|
|
2422
2847
|
}
|
|
2423
2848
|
let behaviors = this.behaviors;
|
|
2424
2849
|
if (behaviors === null) {
|
|
2425
2850
|
this.source = source;
|
|
2851
|
+
this.context = context;
|
|
2426
2852
|
this.behaviors = behaviors = new Array(this.factories.length);
|
|
2427
2853
|
const factories = this.factories;
|
|
2428
2854
|
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
@@ -2437,23 +2863,246 @@ class HTMLView {
|
|
|
2437
2863
|
}
|
|
2438
2864
|
this.isBound = false;
|
|
2439
2865
|
this.source = source;
|
|
2866
|
+
this.context = context;
|
|
2867
|
+
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
2868
|
+
behaviors[i].bind(this);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
this.isBound = true;
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Unbinds a view's behaviors from its binding source.
|
|
2875
|
+
*/
|
|
2876
|
+
unbind() {
|
|
2877
|
+
if (!this.isBound || this.source === null) {
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
this.evaluateUnbindables();
|
|
2881
|
+
this.source = null;
|
|
2882
|
+
this.context = this;
|
|
2883
|
+
this.isBound = false;
|
|
2884
|
+
}
|
|
2885
|
+
evaluateUnbindables() {
|
|
2886
|
+
const unbindables = this.unbindables;
|
|
2887
|
+
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
2888
|
+
unbindables[i].unbind(this);
|
|
2889
|
+
}
|
|
2890
|
+
unbindables.length = 0;
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Efficiently disposes of a contiguous range of synthetic view instances.
|
|
2894
|
+
* @param views - A contiguous range of views to be disposed.
|
|
2895
|
+
*/
|
|
2896
|
+
static disposeContiguousBatch(views) {
|
|
2897
|
+
if (views.length === 0) {
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
|
|
2901
|
+
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
2902
|
+
views[i].unbind();
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
makeSerializationNoop(HTMLView);
|
|
2907
|
+
Observable.defineProperty(HTMLView.prototype, "index");
|
|
2908
|
+
Observable.defineProperty(HTMLView.prototype, "length");
|
|
2909
|
+
const HydrationStage = {
|
|
2910
|
+
unhydrated: "unhydrated",
|
|
2911
|
+
hydrating: "hydrating",
|
|
2912
|
+
hydrated: "hydrated",
|
|
2913
|
+
};
|
|
2914
|
+
/** @public */
|
|
2915
|
+
class HydrationBindingError extends Error {
|
|
2916
|
+
constructor(
|
|
2917
|
+
/**
|
|
2918
|
+
* The error message
|
|
2919
|
+
*/
|
|
2920
|
+
message,
|
|
2921
|
+
/**
|
|
2922
|
+
* The factory that was unable to be bound
|
|
2923
|
+
*/
|
|
2924
|
+
factory,
|
|
2925
|
+
/**
|
|
2926
|
+
* A DocumentFragment containing a clone of the
|
|
2927
|
+
* view's Nodes.
|
|
2928
|
+
*/
|
|
2929
|
+
fragment,
|
|
2930
|
+
/**
|
|
2931
|
+
* String representation of the HTML in the template that
|
|
2932
|
+
* threw the binding error.
|
|
2933
|
+
*/
|
|
2934
|
+
templateString) {
|
|
2935
|
+
super(message);
|
|
2936
|
+
this.factory = factory;
|
|
2937
|
+
this.fragment = fragment;
|
|
2938
|
+
this.templateString = templateString;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
class HydrationView extends DefaultExecutionContext {
|
|
2942
|
+
constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
|
|
2943
|
+
super();
|
|
2944
|
+
this.firstChild = firstChild;
|
|
2945
|
+
this.lastChild = lastChild;
|
|
2946
|
+
this.sourceTemplate = sourceTemplate;
|
|
2947
|
+
this.hostBindingTarget = hostBindingTarget;
|
|
2948
|
+
this[_a] = Hydratable;
|
|
2949
|
+
this.context = this;
|
|
2950
|
+
this.source = null;
|
|
2951
|
+
this.isBound = false;
|
|
2952
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
2953
|
+
this.unbindables = [];
|
|
2954
|
+
this.fragment = null;
|
|
2955
|
+
this.behaviors = null;
|
|
2956
|
+
this._hydrationStage = HydrationStage.unhydrated;
|
|
2957
|
+
this._bindingViewBoundaries = {};
|
|
2958
|
+
this._targets = {};
|
|
2959
|
+
this.factories = sourceTemplate.compile().factories;
|
|
2960
|
+
}
|
|
2961
|
+
get hydrationStage() {
|
|
2962
|
+
return this._hydrationStage;
|
|
2963
|
+
}
|
|
2964
|
+
get targets() {
|
|
2965
|
+
return this._targets;
|
|
2966
|
+
}
|
|
2967
|
+
get bindingViewBoundaries() {
|
|
2968
|
+
return this._bindingViewBoundaries;
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* no-op. Hydrated views are don't need to be moved from a documentFragment
|
|
2972
|
+
* to the target node.
|
|
2973
|
+
*/
|
|
2974
|
+
insertBefore(node) {
|
|
2975
|
+
// No-op in cases where this is called before the view is removed,
|
|
2976
|
+
// because the nodes will already be in the document and just need hydrating.
|
|
2977
|
+
if (this.fragment === null) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
if (this.fragment.hasChildNodes()) {
|
|
2981
|
+
node.parentNode.insertBefore(this.fragment, node);
|
|
2982
|
+
}
|
|
2983
|
+
else {
|
|
2984
|
+
const end = this.lastChild;
|
|
2985
|
+
if (node.previousSibling === end)
|
|
2986
|
+
return;
|
|
2987
|
+
const parentNode = node.parentNode;
|
|
2988
|
+
let current = this.firstChild;
|
|
2989
|
+
let next;
|
|
2990
|
+
while (current !== end) {
|
|
2991
|
+
next = current.nextSibling;
|
|
2992
|
+
parentNode.insertBefore(current, node);
|
|
2993
|
+
current = next;
|
|
2994
|
+
}
|
|
2995
|
+
parentNode.insertBefore(end, node);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Appends the view to a node. In cases where this is called before the
|
|
3000
|
+
* view has been removed, the method will no-op.
|
|
3001
|
+
* @param node - the node to append the view to.
|
|
3002
|
+
*/
|
|
3003
|
+
appendTo(node) {
|
|
3004
|
+
if (this.fragment !== null) {
|
|
3005
|
+
node.appendChild(this.fragment);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
remove() {
|
|
3009
|
+
const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
|
|
3010
|
+
const end = this.lastChild;
|
|
3011
|
+
let current = this.firstChild;
|
|
3012
|
+
let next;
|
|
3013
|
+
while (current !== end) {
|
|
3014
|
+
next = current.nextSibling;
|
|
3015
|
+
if (!next) {
|
|
3016
|
+
throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
|
|
3017
|
+
}
|
|
3018
|
+
fragment.appendChild(current);
|
|
3019
|
+
current = next;
|
|
3020
|
+
}
|
|
3021
|
+
fragment.appendChild(end);
|
|
3022
|
+
}
|
|
3023
|
+
bind(source, context = this) {
|
|
3024
|
+
var _b, _c;
|
|
3025
|
+
if (this.hydrationStage !== HydrationStage.hydrated) {
|
|
3026
|
+
this._hydrationStage = HydrationStage.hydrating;
|
|
3027
|
+
}
|
|
3028
|
+
if (this.source === source) {
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
let behaviors = this.behaviors;
|
|
3032
|
+
if (behaviors === null) {
|
|
3033
|
+
this.source = source;
|
|
3034
|
+
this.context = context;
|
|
3035
|
+
try {
|
|
3036
|
+
const { targets, boundaries } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
|
|
3037
|
+
this._targets = targets;
|
|
3038
|
+
this._bindingViewBoundaries = boundaries;
|
|
3039
|
+
}
|
|
3040
|
+
catch (error) {
|
|
3041
|
+
if (error instanceof HydrationTargetElementError) {
|
|
3042
|
+
let templateString = this.sourceTemplate.html;
|
|
3043
|
+
if (typeof templateString !== "string") {
|
|
3044
|
+
templateString = templateString.innerHTML;
|
|
3045
|
+
}
|
|
3046
|
+
error.templateString = templateString;
|
|
3047
|
+
}
|
|
3048
|
+
throw error;
|
|
3049
|
+
}
|
|
3050
|
+
this.behaviors = behaviors = new Array(this.factories.length);
|
|
3051
|
+
const factories = this.factories;
|
|
3052
|
+
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
3053
|
+
const factory = factories[i];
|
|
3054
|
+
if (factory.targetNodeId === "h" && this.hostBindingTarget) {
|
|
3055
|
+
targetFactory(factory, this.hostBindingTarget, this._targets);
|
|
3056
|
+
}
|
|
3057
|
+
// If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
|
|
3058
|
+
if (factory.targetNodeId in this.targets) {
|
|
3059
|
+
const behavior = factory.createBehavior();
|
|
3060
|
+
behavior.bind(this);
|
|
3061
|
+
behaviors[i] = behavior;
|
|
3062
|
+
}
|
|
3063
|
+
else {
|
|
3064
|
+
let templateString = this.sourceTemplate.html;
|
|
3065
|
+
if (typeof templateString !== "string") {
|
|
3066
|
+
templateString = templateString.innerHTML;
|
|
3067
|
+
}
|
|
3068
|
+
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);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
else {
|
|
3073
|
+
if (this.source !== null) {
|
|
3074
|
+
this.evaluateUnbindables();
|
|
3075
|
+
}
|
|
3076
|
+
this.isBound = false;
|
|
3077
|
+
this.source = source;
|
|
3078
|
+
this.context = context;
|
|
2440
3079
|
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
2441
3080
|
behaviors[i].bind(this);
|
|
2442
3081
|
}
|
|
2443
3082
|
}
|
|
2444
3083
|
this.isBound = true;
|
|
3084
|
+
this._hydrationStage = HydrationStage.hydrated;
|
|
2445
3085
|
}
|
|
2446
|
-
/**
|
|
2447
|
-
* Unbinds a view's behaviors from its binding source.
|
|
2448
|
-
*/
|
|
2449
3086
|
unbind() {
|
|
2450
3087
|
if (!this.isBound || this.source === null) {
|
|
2451
3088
|
return;
|
|
2452
3089
|
}
|
|
2453
3090
|
this.evaluateUnbindables();
|
|
2454
3091
|
this.source = null;
|
|
3092
|
+
this.context = this;
|
|
2455
3093
|
this.isBound = false;
|
|
2456
3094
|
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
|
|
3097
|
+
* Once a view has been disposed, it cannot be inserted or bound again.
|
|
3098
|
+
*/
|
|
3099
|
+
dispose() {
|
|
3100
|
+
removeNodeSequence(this.firstChild, this.lastChild);
|
|
3101
|
+
this.unbind();
|
|
3102
|
+
}
|
|
3103
|
+
onUnbind(behavior) {
|
|
3104
|
+
this.unbindables.push(behavior);
|
|
3105
|
+
}
|
|
2457
3106
|
evaluateUnbindables() {
|
|
2458
3107
|
const unbindables = this.unbindables;
|
|
2459
3108
|
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
@@ -2461,22 +3110,220 @@ class HTMLView {
|
|
|
2461
3110
|
}
|
|
2462
3111
|
unbindables.length = 0;
|
|
2463
3112
|
}
|
|
3113
|
+
}
|
|
3114
|
+
_a = Hydratable;
|
|
3115
|
+
makeSerializationNoop(HydrationView);
|
|
3116
|
+
|
|
3117
|
+
function isContentTemplate(value) {
|
|
3118
|
+
return value.create !== undefined;
|
|
3119
|
+
}
|
|
3120
|
+
function updateContent(target, aspect, value, controller) {
|
|
3121
|
+
// If there's no actual value, then this equates to the
|
|
3122
|
+
// empty string for the purposes of content bindings.
|
|
3123
|
+
if (value === null || value === undefined) {
|
|
3124
|
+
value = "";
|
|
3125
|
+
}
|
|
3126
|
+
// If the value has a "create" method, then it's a ContentTemplate.
|
|
3127
|
+
if (isContentTemplate(value)) {
|
|
3128
|
+
target.textContent = "";
|
|
3129
|
+
let view = target.$fastView;
|
|
3130
|
+
// If there's no previous view that we might be able to
|
|
3131
|
+
// reuse then create a new view from the template.
|
|
3132
|
+
if (view === void 0) {
|
|
3133
|
+
if (isHydratable(controller) &&
|
|
3134
|
+
isHydratable(value) &&
|
|
3135
|
+
controller.bindingViewBoundaries[this.targetNodeId] !== undefined &&
|
|
3136
|
+
controller.hydrationStage !== HydrationStage.hydrated) {
|
|
3137
|
+
const viewNodes = controller.bindingViewBoundaries[this.targetNodeId];
|
|
3138
|
+
view = value.hydrate(viewNodes.first, viewNodes.last);
|
|
3139
|
+
}
|
|
3140
|
+
else {
|
|
3141
|
+
view = value.create();
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
else {
|
|
3145
|
+
// If there is a previous view, but it wasn't created
|
|
3146
|
+
// from the same template as the new value, then we
|
|
3147
|
+
// need to remove the old view if it's still in the DOM
|
|
3148
|
+
// and create a new view from the template.
|
|
3149
|
+
if (target.$fastTemplate !== value) {
|
|
3150
|
+
if (view.isComposed) {
|
|
3151
|
+
view.remove();
|
|
3152
|
+
view.unbind();
|
|
3153
|
+
}
|
|
3154
|
+
view = value.create();
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
// It's possible that the value is the same as the previous template
|
|
3158
|
+
// and that there's actually no need to compose it.
|
|
3159
|
+
if (!view.isComposed) {
|
|
3160
|
+
view.isComposed = true;
|
|
3161
|
+
view.bind(controller.source, controller.context);
|
|
3162
|
+
view.insertBefore(target);
|
|
3163
|
+
target.$fastView = view;
|
|
3164
|
+
target.$fastTemplate = value;
|
|
3165
|
+
}
|
|
3166
|
+
else if (view.needsBindOnly) {
|
|
3167
|
+
view.needsBindOnly = false;
|
|
3168
|
+
view.bind(controller.source, controller.context);
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
else {
|
|
3172
|
+
const view = target.$fastView;
|
|
3173
|
+
// If there is a view and it's currently composed into
|
|
3174
|
+
// the DOM, then we need to remove it.
|
|
3175
|
+
if (view !== void 0 && view.isComposed) {
|
|
3176
|
+
view.isComposed = false;
|
|
3177
|
+
view.remove();
|
|
3178
|
+
if (view.needsBindOnly) {
|
|
3179
|
+
view.needsBindOnly = false;
|
|
3180
|
+
}
|
|
3181
|
+
else {
|
|
3182
|
+
view.unbind();
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
target.textContent = value;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
function updateTokenList(target, aspect, value) {
|
|
3189
|
+
var _a;
|
|
3190
|
+
const lookup = `${this.id}-t`;
|
|
3191
|
+
const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
|
|
3192
|
+
const classVersions = state.cv;
|
|
3193
|
+
let version = state.v;
|
|
3194
|
+
const tokenList = target[aspect];
|
|
3195
|
+
// Add the classes, tracking the version at which they were added.
|
|
3196
|
+
if (value !== null && value !== undefined && value.length) {
|
|
3197
|
+
const names = value.split(/\s+/);
|
|
3198
|
+
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
3199
|
+
const currentName = names[i];
|
|
3200
|
+
if (currentName === "") {
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
classVersions[currentName] = version;
|
|
3204
|
+
tokenList.add(currentName);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
state.v = version + 1;
|
|
3208
|
+
// If this is the first call to add classes, there's no need to remove old ones.
|
|
3209
|
+
if (version === 0) {
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
// Remove classes from the previous version.
|
|
3213
|
+
version -= 1;
|
|
3214
|
+
for (const name in classVersions) {
|
|
3215
|
+
if (classVersions[name] === version) {
|
|
3216
|
+
tokenList.remove(name);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
const sinkLookup = {
|
|
3221
|
+
[DOMAspect.attribute]: DOM.setAttribute,
|
|
3222
|
+
[DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
|
|
3223
|
+
[DOMAspect.property]: (t, a, v) => (t[a] = v),
|
|
3224
|
+
[DOMAspect.content]: updateContent,
|
|
3225
|
+
[DOMAspect.tokenList]: updateTokenList,
|
|
3226
|
+
[DOMAspect.event]: () => void 0,
|
|
3227
|
+
};
|
|
3228
|
+
/**
|
|
3229
|
+
* A directive that applies bindings.
|
|
3230
|
+
* @public
|
|
3231
|
+
*/
|
|
3232
|
+
class HTMLBindingDirective {
|
|
2464
3233
|
/**
|
|
2465
|
-
*
|
|
2466
|
-
* @param
|
|
3234
|
+
* Creates an instance of HTMLBindingDirective.
|
|
3235
|
+
* @param dataBinding - The binding configuration to apply.
|
|
2467
3236
|
*/
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
3237
|
+
constructor(dataBinding) {
|
|
3238
|
+
this.dataBinding = dataBinding;
|
|
3239
|
+
this.updateTarget = null;
|
|
3240
|
+
/**
|
|
3241
|
+
* The type of aspect to target.
|
|
3242
|
+
*/
|
|
3243
|
+
this.aspectType = DOMAspect.content;
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Creates HTML to be used within a template.
|
|
3247
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
3248
|
+
*/
|
|
3249
|
+
createHTML(add) {
|
|
3250
|
+
return Markup.interpolation(add(this));
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Creates a behavior.
|
|
3254
|
+
*/
|
|
3255
|
+
createBehavior() {
|
|
3256
|
+
var _a;
|
|
3257
|
+
if (this.updateTarget === null) {
|
|
3258
|
+
const sink = sinkLookup[this.aspectType];
|
|
3259
|
+
const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
|
|
3260
|
+
if (!sink) {
|
|
3261
|
+
throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
|
|
3262
|
+
}
|
|
3263
|
+
this.data = `${this.id}-d`;
|
|
3264
|
+
this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
|
|
2471
3265
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
3266
|
+
return this;
|
|
3267
|
+
}
|
|
3268
|
+
/** @internal */
|
|
3269
|
+
bind(controller) {
|
|
3270
|
+
var _a;
|
|
3271
|
+
const target = controller.targets[this.targetNodeId];
|
|
3272
|
+
const isHydrating = isHydratable(controller) &&
|
|
3273
|
+
controller.hydrationStage &&
|
|
3274
|
+
controller.hydrationStage !== HydrationStage.hydrated;
|
|
3275
|
+
switch (this.aspectType) {
|
|
3276
|
+
case DOMAspect.event:
|
|
3277
|
+
target[this.data] = controller;
|
|
3278
|
+
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
3279
|
+
break;
|
|
3280
|
+
case DOMAspect.content:
|
|
3281
|
+
controller.onUnbind(this);
|
|
3282
|
+
// intentional fall through
|
|
3283
|
+
default:
|
|
3284
|
+
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
3285
|
+
observer.target = target;
|
|
3286
|
+
observer.controller = controller;
|
|
3287
|
+
if (isHydrating &&
|
|
3288
|
+
(this.aspectType === DOMAspect.attribute ||
|
|
3289
|
+
this.aspectType === DOMAspect.booleanAttribute)) {
|
|
3290
|
+
observer.bind(controller);
|
|
3291
|
+
// Skip updating target during bind for attributes
|
|
3292
|
+
break;
|
|
3293
|
+
}
|
|
3294
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
3295
|
+
break;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
/** @internal */
|
|
3299
|
+
unbind(controller) {
|
|
3300
|
+
const target = controller.targets[this.targetNodeId];
|
|
3301
|
+
const view = target.$fastView;
|
|
3302
|
+
if (view !== void 0 && view.isComposed) {
|
|
3303
|
+
view.unbind();
|
|
3304
|
+
view.needsBindOnly = true;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
/** @internal */
|
|
3308
|
+
handleEvent(event) {
|
|
3309
|
+
const controller = event.currentTarget[this.data];
|
|
3310
|
+
if (controller.isBound) {
|
|
3311
|
+
ExecutionContext.setEvent(event);
|
|
3312
|
+
const result = this.dataBinding.evaluate(controller.source, controller.context);
|
|
3313
|
+
ExecutionContext.setEvent(null);
|
|
3314
|
+
if (result !== true) {
|
|
3315
|
+
event.preventDefault();
|
|
3316
|
+
}
|
|
2475
3317
|
}
|
|
2476
3318
|
}
|
|
3319
|
+
/** @internal */
|
|
3320
|
+
handleChange(binding, observer) {
|
|
3321
|
+
const target = observer.target;
|
|
3322
|
+
const controller = observer.controller;
|
|
3323
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
3324
|
+
}
|
|
2477
3325
|
}
|
|
2478
|
-
|
|
2479
|
-
Observable.defineProperty(HTMLView.prototype, "length");
|
|
3326
|
+
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
2480
3327
|
|
|
2481
3328
|
const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
|
|
2482
3329
|
const descriptorCache = {};
|
|
@@ -2502,20 +3349,25 @@ const warningHost = new Proxy(document.createElement("div"), {
|
|
|
2502
3349
|
},
|
|
2503
3350
|
});
|
|
2504
3351
|
class CompilationContext {
|
|
2505
|
-
constructor(fragment, directives) {
|
|
3352
|
+
constructor(fragment, directives, policy) {
|
|
2506
3353
|
this.fragment = fragment;
|
|
2507
3354
|
this.directives = directives;
|
|
3355
|
+
this.policy = policy;
|
|
2508
3356
|
this.proto = null;
|
|
2509
3357
|
this.nodeIds = new Set();
|
|
2510
3358
|
this.descriptors = {};
|
|
2511
3359
|
this.factories = [];
|
|
2512
3360
|
}
|
|
2513
|
-
addFactory(factory, parentId, nodeId, targetIndex) {
|
|
3361
|
+
addFactory(factory, parentId, nodeId, targetIndex, tagName) {
|
|
3362
|
+
var _a, _b;
|
|
2514
3363
|
if (!this.nodeIds.has(nodeId)) {
|
|
2515
3364
|
this.nodeIds.add(nodeId);
|
|
2516
3365
|
this.addTargetDescriptor(parentId, nodeId, targetIndex);
|
|
2517
3366
|
}
|
|
2518
|
-
factory.
|
|
3367
|
+
factory.id = (_a = factory.id) !== null && _a !== void 0 ? _a : nextId();
|
|
3368
|
+
factory.targetNodeId = nodeId;
|
|
3369
|
+
factory.targetTagName = tagName;
|
|
3370
|
+
factory.policy = (_b = factory.policy) !== null && _b !== void 0 ? _b : this.policy;
|
|
2519
3371
|
this.factories.push(factory);
|
|
2520
3372
|
}
|
|
2521
3373
|
freeze() {
|
|
@@ -2568,19 +3420,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
|
|
|
2568
3420
|
let result = null;
|
|
2569
3421
|
if (parseResult === null) {
|
|
2570
3422
|
if (includeBasicValues) {
|
|
2571
|
-
result = new HTMLBindingDirective(oneTime(() => attrValue));
|
|
2572
|
-
|
|
3423
|
+
result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
|
|
3424
|
+
HTMLDirective.assignAspect(result, attr.name);
|
|
2573
3425
|
}
|
|
2574
3426
|
}
|
|
2575
3427
|
else {
|
|
2576
3428
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2577
|
-
result = Compiler.aggregate(parseResult);
|
|
3429
|
+
result = Compiler.aggregate(parseResult, context.policy);
|
|
2578
3430
|
}
|
|
2579
3431
|
if (result !== null) {
|
|
2580
3432
|
node.removeAttributeNode(attr);
|
|
2581
3433
|
i--;
|
|
2582
3434
|
ii--;
|
|
2583
|
-
context.addFactory(result, parentId, nodeId, nodeIndex);
|
|
3435
|
+
context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
|
|
2584
3436
|
}
|
|
2585
3437
|
}
|
|
2586
3438
|
}
|
|
@@ -2605,8 +3457,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
|
|
|
2605
3457
|
}
|
|
2606
3458
|
else {
|
|
2607
3459
|
currentNode.textContent = " ";
|
|
2608
|
-
|
|
2609
|
-
context.addFactory(currentPart, parentId, nodeId, nodeIndex);
|
|
3460
|
+
HTMLDirective.assignAspect(currentPart);
|
|
3461
|
+
context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
|
|
2610
3462
|
}
|
|
2611
3463
|
lastNode = currentNode;
|
|
2612
3464
|
}
|
|
@@ -2638,7 +3490,7 @@ function compileNode(context, parentId, node, nodeIndex) {
|
|
|
2638
3490
|
if (parts !== null) {
|
|
2639
3491
|
context.addFactory(
|
|
2640
3492
|
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2641
|
-
Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
|
|
3493
|
+
Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
|
|
2642
3494
|
}
|
|
2643
3495
|
break;
|
|
2644
3496
|
}
|
|
@@ -2652,45 +3504,28 @@ function isMarker(node, directives) {
|
|
|
2652
3504
|
Parser.parse(node.data, directives) !== null);
|
|
2653
3505
|
}
|
|
2654
3506
|
const templateTag = "TEMPLATE";
|
|
2655
|
-
const policyOptions = { createHTML: html => html };
|
|
2656
|
-
let htmlPolicy = globalThis.trustedTypes
|
|
2657
|
-
? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
|
|
2658
|
-
: policyOptions;
|
|
2659
|
-
const fastHTMLPolicy = htmlPolicy;
|
|
2660
3507
|
/**
|
|
2661
3508
|
* Common APIs related to compilation.
|
|
2662
3509
|
* @public
|
|
2663
3510
|
*/
|
|
2664
3511
|
const Compiler = {
|
|
2665
|
-
/**
|
|
2666
|
-
* Sets the HTML trusted types policy used by the compiler.
|
|
2667
|
-
* @param policy - The policy to set for HTML.
|
|
2668
|
-
* @remarks
|
|
2669
|
-
* This API can only be called once, for security reasons. It should be
|
|
2670
|
-
* called by the application developer at the start of their program.
|
|
2671
|
-
*/
|
|
2672
|
-
setHTMLPolicy(policy) {
|
|
2673
|
-
if (htmlPolicy !== fastHTMLPolicy) {
|
|
2674
|
-
throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
|
|
2675
|
-
}
|
|
2676
|
-
htmlPolicy = policy;
|
|
2677
|
-
},
|
|
2678
3512
|
/**
|
|
2679
3513
|
* Compiles a template and associated directives into a compilation
|
|
2680
3514
|
* result which can be used to create views.
|
|
2681
3515
|
* @param html - The html string or template element to compile.
|
|
2682
|
-
* @param
|
|
3516
|
+
* @param factories - The behavior factories referenced by the template.
|
|
3517
|
+
* @param policy - The security policy to compile the html with.
|
|
2683
3518
|
* @remarks
|
|
2684
3519
|
* The template that is provided for compilation is altered in-place
|
|
2685
3520
|
* and cannot be compiled again. If the original template must be preserved,
|
|
2686
3521
|
* it is recommended that you clone the original and pass the clone to this API.
|
|
2687
3522
|
* @public
|
|
2688
3523
|
*/
|
|
2689
|
-
compile(html,
|
|
3524
|
+
compile(html, factories, policy = DOM.policy) {
|
|
2690
3525
|
let template;
|
|
2691
3526
|
if (isString(html)) {
|
|
2692
3527
|
template = document.createElement(templateTag);
|
|
2693
|
-
template.innerHTML =
|
|
3528
|
+
template.innerHTML = policy.createHTML(html);
|
|
2694
3529
|
const fec = template.content.firstElementChild;
|
|
2695
3530
|
if (fec !== null && fec.tagName === templateTag) {
|
|
2696
3531
|
template = fec;
|
|
@@ -2699,20 +3534,23 @@ const Compiler = {
|
|
|
2699
3534
|
else {
|
|
2700
3535
|
template = html;
|
|
2701
3536
|
}
|
|
3537
|
+
if (!template.content.firstChild && !template.content.lastChild) {
|
|
3538
|
+
template.content.appendChild(document.createComment(""));
|
|
3539
|
+
}
|
|
2702
3540
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
|
|
2703
3541
|
const fragment = document.adoptNode(template.content);
|
|
2704
|
-
const context = new CompilationContext(fragment,
|
|
3542
|
+
const context = new CompilationContext(fragment, factories, policy);
|
|
2705
3543
|
compileAttributes(context, "", template, /* host */ "h", 0, true);
|
|
2706
3544
|
if (
|
|
2707
3545
|
// If the first node in a fragment is a marker, that means it's an unstable first node,
|
|
2708
3546
|
// because something like a when, repeat, etc. could add nodes before the marker.
|
|
2709
3547
|
// To mitigate this, we insert a stable first node. However, if we insert a node,
|
|
2710
3548
|
// that will alter the result of the TreeWalker. So, we also need to offset the target index.
|
|
2711
|
-
isMarker(fragment.firstChild,
|
|
3549
|
+
isMarker(fragment.firstChild, factories) ||
|
|
2712
3550
|
// Or if there is only one node and a directive, it means the template's content
|
|
2713
3551
|
// is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
|
|
2714
3552
|
// the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
|
|
2715
|
-
(fragment.childNodes.length === 1 && Object.keys(
|
|
3553
|
+
(fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
|
|
2716
3554
|
fragment.insertBefore(document.createComment(""), fragment.firstChild);
|
|
2717
3555
|
}
|
|
2718
3556
|
compileChildren(context, fragment, /* root */ "r");
|
|
@@ -2731,23 +3569,24 @@ const Compiler = {
|
|
|
2731
3569
|
* Aggregates an array of strings and directives into a single directive.
|
|
2732
3570
|
* @param parts - A heterogeneous array of static strings interspersed with
|
|
2733
3571
|
* directives.
|
|
3572
|
+
* @param policy - The security policy to use with the aggregated bindings.
|
|
2734
3573
|
* @returns A single inline directive that aggregates the behavior of all the parts.
|
|
2735
3574
|
*/
|
|
2736
|
-
aggregate(parts) {
|
|
3575
|
+
aggregate(parts, policy = DOM.policy) {
|
|
2737
3576
|
if (parts.length === 1) {
|
|
2738
3577
|
return parts[0];
|
|
2739
3578
|
}
|
|
2740
3579
|
let sourceAspect;
|
|
2741
|
-
let binding;
|
|
2742
3580
|
let isVolatile = false;
|
|
3581
|
+
let bindingPolicy = void 0;
|
|
2743
3582
|
const partCount = parts.length;
|
|
2744
3583
|
const finalParts = parts.map((x) => {
|
|
2745
3584
|
if (isString(x)) {
|
|
2746
3585
|
return () => x;
|
|
2747
3586
|
}
|
|
2748
3587
|
sourceAspect = x.sourceAspect || sourceAspect;
|
|
2749
|
-
binding = x.dataBinding || binding;
|
|
2750
3588
|
isVolatile = isVolatile || x.dataBinding.isVolatile;
|
|
3589
|
+
bindingPolicy = bindingPolicy || x.dataBinding.policy;
|
|
2751
3590
|
return x.dataBinding.evaluate;
|
|
2752
3591
|
});
|
|
2753
3592
|
const expression = (scope, context) => {
|
|
@@ -2757,14 +3596,56 @@ const Compiler = {
|
|
|
2757
3596
|
}
|
|
2758
3597
|
return output;
|
|
2759
3598
|
};
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
const directive = new HTMLBindingDirective(binding);
|
|
2763
|
-
Aspect.assign(directive, sourceAspect);
|
|
3599
|
+
const directive = new HTMLBindingDirective(oneWay(expression, bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy, isVolatile));
|
|
3600
|
+
HTMLDirective.assignAspect(directive, sourceAspect);
|
|
2764
3601
|
return directive;
|
|
2765
3602
|
},
|
|
2766
3603
|
};
|
|
2767
3604
|
|
|
3605
|
+
// Much thanks to LitHTML for working this out!
|
|
3606
|
+
const lastAttributeNameRegex =
|
|
3607
|
+
/* eslint-disable-next-line no-control-regex, max-len */
|
|
3608
|
+
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
3609
|
+
const noFactories = Object.create(null);
|
|
3610
|
+
/**
|
|
3611
|
+
* Inlines a template into another template.
|
|
3612
|
+
* @public
|
|
3613
|
+
*/
|
|
3614
|
+
class InlineTemplateDirective {
|
|
3615
|
+
/**
|
|
3616
|
+
* Creates an instance of InlineTemplateDirective.
|
|
3617
|
+
* @param template - The template to inline.
|
|
3618
|
+
*/
|
|
3619
|
+
constructor(html, factories = noFactories) {
|
|
3620
|
+
this.html = html;
|
|
3621
|
+
this.factories = factories;
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Creates HTML to be used within a template.
|
|
3625
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
3626
|
+
*/
|
|
3627
|
+
createHTML(add) {
|
|
3628
|
+
const factories = this.factories;
|
|
3629
|
+
for (const key in factories) {
|
|
3630
|
+
add(factories[key]);
|
|
3631
|
+
}
|
|
3632
|
+
return this.html;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* An empty template partial.
|
|
3637
|
+
*/
|
|
3638
|
+
InlineTemplateDirective.empty = new InlineTemplateDirective("");
|
|
3639
|
+
HTMLDirective.define(InlineTemplateDirective);
|
|
3640
|
+
function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
|
|
3641
|
+
if (definition.aspected) {
|
|
3642
|
+
const match = lastAttributeNameRegex.exec(prevString);
|
|
3643
|
+
if (match !== null) {
|
|
3644
|
+
HTMLDirective.assignAspect(value, match[2]);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
return value.createHTML(add);
|
|
3648
|
+
}
|
|
2768
3649
|
/**
|
|
2769
3650
|
* A template capable of creating HTMLView instances or rendering directly to DOM.
|
|
2770
3651
|
* @public
|
|
@@ -2774,21 +3655,53 @@ class ViewTemplate {
|
|
|
2774
3655
|
* Creates an instance of ViewTemplate.
|
|
2775
3656
|
* @param html - The html representing what this template will instantiate, including placeholders for directives.
|
|
2776
3657
|
* @param factories - The directives that will be connected to placeholders in the html.
|
|
3658
|
+
* @param policy - The security policy to use when compiling this template.
|
|
2777
3659
|
*/
|
|
2778
|
-
constructor(html, factories) {
|
|
3660
|
+
constructor(html, factories = {}, policy) {
|
|
3661
|
+
this.policy = policy;
|
|
2779
3662
|
this.result = null;
|
|
2780
3663
|
this.html = html;
|
|
2781
3664
|
this.factories = factories;
|
|
2782
3665
|
}
|
|
3666
|
+
/**
|
|
3667
|
+
* @internal
|
|
3668
|
+
*/
|
|
3669
|
+
compile() {
|
|
3670
|
+
if (this.result === null) {
|
|
3671
|
+
this.result = Compiler.compile(this.html, this.factories, this.policy);
|
|
3672
|
+
}
|
|
3673
|
+
return this.result;
|
|
3674
|
+
}
|
|
2783
3675
|
/**
|
|
2784
3676
|
* Creates an HTMLView instance based on this template definition.
|
|
2785
3677
|
* @param hostBindingTarget - The element that host behaviors will be bound to.
|
|
2786
3678
|
*/
|
|
2787
3679
|
create(hostBindingTarget) {
|
|
2788
|
-
|
|
2789
|
-
|
|
3680
|
+
return this.compile().createView(hostBindingTarget);
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* Returns a directive that can inline the template.
|
|
3684
|
+
*/
|
|
3685
|
+
inline() {
|
|
3686
|
+
return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
|
|
3687
|
+
}
|
|
3688
|
+
/**
|
|
3689
|
+
* Sets the DOMPolicy for this template.
|
|
3690
|
+
* @param policy - The policy to associated with this template.
|
|
3691
|
+
* @returns The modified template instance.
|
|
3692
|
+
* @remarks
|
|
3693
|
+
* The DOMPolicy can only be set once for a template and cannot be
|
|
3694
|
+
* set after the template is compiled.
|
|
3695
|
+
*/
|
|
3696
|
+
withPolicy(policy) {
|
|
3697
|
+
if (this.result) {
|
|
3698
|
+
throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
|
|
2790
3699
|
}
|
|
2791
|
-
|
|
3700
|
+
if (this.policy) {
|
|
3701
|
+
throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
|
|
3702
|
+
}
|
|
3703
|
+
this.policy = policy;
|
|
3704
|
+
return this;
|
|
2792
3705
|
}
|
|
2793
3706
|
/**
|
|
2794
3707
|
* Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
|
|
@@ -2803,18 +3716,49 @@ class ViewTemplate {
|
|
|
2803
3716
|
view.appendTo(host);
|
|
2804
3717
|
return view;
|
|
2805
3718
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
3719
|
+
/**
|
|
3720
|
+
* Creates a template based on a set of static strings and dynamic values.
|
|
3721
|
+
* @param strings - The static strings to create the template with.
|
|
3722
|
+
* @param values - The dynamic values to create the template with.
|
|
3723
|
+
* @param policy - The DOMPolicy to associated with the template.
|
|
3724
|
+
* @returns A ViewTemplate.
|
|
3725
|
+
* @remarks
|
|
3726
|
+
* This API should not be used directly under normal circumstances because constructing
|
|
3727
|
+
* a template in this way, if not done properly, can open up the application to XSS
|
|
3728
|
+
* attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
|
|
3729
|
+
* and also be sure to manually sanitize all static strings particularly if they can
|
|
3730
|
+
* come from user input.
|
|
3731
|
+
*/
|
|
3732
|
+
static create(strings, values, policy) {
|
|
3733
|
+
let html = "";
|
|
3734
|
+
const factories = Object.create(null);
|
|
3735
|
+
const add = (factory) => {
|
|
3736
|
+
var _a;
|
|
3737
|
+
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
3738
|
+
factories[id] = factory;
|
|
3739
|
+
return id;
|
|
3740
|
+
};
|
|
3741
|
+
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
3742
|
+
const currentString = strings[i];
|
|
3743
|
+
let currentValue = values[i];
|
|
3744
|
+
let definition;
|
|
3745
|
+
html += currentString;
|
|
3746
|
+
if (isFunction(currentValue)) {
|
|
3747
|
+
currentValue = new HTMLBindingDirective(oneWay(currentValue));
|
|
3748
|
+
}
|
|
3749
|
+
else if (currentValue instanceof Binding) {
|
|
3750
|
+
currentValue = new HTMLBindingDirective(currentValue);
|
|
3751
|
+
}
|
|
3752
|
+
else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
|
|
3753
|
+
const staticValue = currentValue;
|
|
3754
|
+
currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
|
|
3755
|
+
}
|
|
3756
|
+
html += createHTML(currentValue, currentString, add, definition);
|
|
3757
|
+
}
|
|
3758
|
+
return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
|
|
2815
3759
|
}
|
|
2816
|
-
return value.createHTML(add);
|
|
2817
3760
|
}
|
|
3761
|
+
makeSerializationNoop(ViewTemplate);
|
|
2818
3762
|
/**
|
|
2819
3763
|
* Transforms a template literal string into a ViewTemplate.
|
|
2820
3764
|
* @param strings - The string fragments that are interpolated with the values.
|
|
@@ -2824,51 +3768,15 @@ function createAspectedHTML(value, prevString, add) {
|
|
|
2824
3768
|
* other template instances, and Directive instances.
|
|
2825
3769
|
* @public
|
|
2826
3770
|
*/
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
const add = (factory) => {
|
|
2831
|
-
var _a;
|
|
2832
|
-
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
2833
|
-
factories[id] = factory;
|
|
2834
|
-
return id;
|
|
2835
|
-
};
|
|
2836
|
-
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
2837
|
-
const currentString = strings[i];
|
|
2838
|
-
const currentValue = values[i];
|
|
2839
|
-
let definition;
|
|
2840
|
-
html += currentString;
|
|
2841
|
-
if (isFunction(currentValue)) {
|
|
2842
|
-
html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
|
|
2843
|
-
}
|
|
2844
|
-
else if (isString(currentValue)) {
|
|
2845
|
-
const match = lastAttributeNameRegex.exec(currentString);
|
|
2846
|
-
if (match !== null) {
|
|
2847
|
-
const directive = new HTMLBindingDirective(oneTime(() => currentValue));
|
|
2848
|
-
Aspect.assign(directive, match[2]);
|
|
2849
|
-
html += directive.createHTML(add);
|
|
2850
|
-
}
|
|
2851
|
-
else {
|
|
2852
|
-
html += currentValue;
|
|
2853
|
-
}
|
|
2854
|
-
}
|
|
2855
|
-
else if (currentValue instanceof Binding) {
|
|
2856
|
-
html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
|
|
2857
|
-
}
|
|
2858
|
-
else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
|
|
2859
|
-
html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
|
|
2860
|
-
}
|
|
2861
|
-
else {
|
|
2862
|
-
if (definition.aspected) {
|
|
2863
|
-
html += createAspectedHTML(currentValue, currentString, add);
|
|
2864
|
-
}
|
|
2865
|
-
else {
|
|
2866
|
-
html += currentValue.createHTML(add);
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
3771
|
+
const html = ((strings, ...values) => {
|
|
3772
|
+
if (Array.isArray(strings) && Array.isArray(strings.raw)) {
|
|
3773
|
+
return ViewTemplate.create(strings, values);
|
|
2869
3774
|
}
|
|
2870
|
-
|
|
2871
|
-
}
|
|
3775
|
+
throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
|
|
3776
|
+
});
|
|
3777
|
+
html.partial = (html) => {
|
|
3778
|
+
return new InlineTemplateDirective(html);
|
|
3779
|
+
};
|
|
2872
3780
|
|
|
2873
3781
|
/**
|
|
2874
3782
|
* The runtime behavior for template references.
|
|
@@ -2880,7 +3788,7 @@ class RefDirective extends StatelessAttachedAttributeDirective {
|
|
|
2880
3788
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2881
3789
|
*/
|
|
2882
3790
|
bind(controller) {
|
|
2883
|
-
controller.source[this.options] = controller.targets[this.
|
|
3791
|
+
controller.source[this.options] = controller.targets[this.targetNodeId];
|
|
2884
3792
|
}
|
|
2885
3793
|
}
|
|
2886
3794
|
HTMLDirective.define(RefDirective);
|
|
@@ -2891,19 +3799,26 @@ HTMLDirective.define(RefDirective);
|
|
|
2891
3799
|
*/
|
|
2892
3800
|
const ref = (propertyName) => new RefDirective(propertyName);
|
|
2893
3801
|
|
|
3802
|
+
const noTemplate = () => null;
|
|
3803
|
+
function normalizeBinding(value) {
|
|
3804
|
+
return value === undefined ? noTemplate : isFunction(value) ? value : () => value;
|
|
3805
|
+
}
|
|
2894
3806
|
/**
|
|
2895
3807
|
* A directive that enables basic conditional rendering in a template.
|
|
2896
3808
|
* @param condition - The condition to test for rendering.
|
|
2897
3809
|
* @param templateOrTemplateBinding - The template or a binding that gets
|
|
2898
3810
|
* the template to render when the condition is true.
|
|
3811
|
+
* @param elseTemplateOrTemplateBinding - Optional template or binding that that
|
|
3812
|
+
* gets the template to render when the conditional is false.
|
|
2899
3813
|
* @public
|
|
2900
3814
|
*/
|
|
2901
|
-
function when(condition, templateOrTemplateBinding) {
|
|
3815
|
+
function when(condition, templateOrTemplateBinding, elseTemplateOrTemplateBinding) {
|
|
2902
3816
|
const dataBinding = isFunction(condition) ? condition : () => condition;
|
|
2903
|
-
const templateBinding =
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3817
|
+
const templateBinding = normalizeBinding(templateOrTemplateBinding);
|
|
3818
|
+
const elseBinding = normalizeBinding(elseTemplateOrTemplateBinding);
|
|
3819
|
+
return (source, context) => dataBinding(source, context)
|
|
3820
|
+
? templateBinding(source, context)
|
|
3821
|
+
: elseBinding(source, context);
|
|
2907
3822
|
}
|
|
2908
3823
|
|
|
2909
3824
|
const defaultRepeatOptions = Object.freeze({
|
|
@@ -2922,6 +3837,19 @@ function bindWithPositioning(view, items, index, controller) {
|
|
|
2922
3837
|
view.context.index = index;
|
|
2923
3838
|
view.bind(items[index]);
|
|
2924
3839
|
}
|
|
3840
|
+
function isCommentNode(node) {
|
|
3841
|
+
return node.nodeType === Node.COMMENT_NODE;
|
|
3842
|
+
}
|
|
3843
|
+
class HydrationRepeatError extends Error {
|
|
3844
|
+
constructor(
|
|
3845
|
+
/**
|
|
3846
|
+
* The error message
|
|
3847
|
+
*/
|
|
3848
|
+
message, propertyBag) {
|
|
3849
|
+
super(message);
|
|
3850
|
+
this.propertyBag = propertyBag;
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
2925
3853
|
/**
|
|
2926
3854
|
* A behavior that renders a template for each item in an array.
|
|
2927
3855
|
* @public
|
|
@@ -2938,12 +3866,13 @@ class RepeatBehavior {
|
|
|
2938
3866
|
*/
|
|
2939
3867
|
constructor(directive) {
|
|
2940
3868
|
this.directive = directive;
|
|
2941
|
-
this.views = [];
|
|
2942
3869
|
this.items = null;
|
|
2943
3870
|
this.itemsObserver = null;
|
|
2944
3871
|
this.bindView = bindWithoutPositioning;
|
|
2945
|
-
|
|
2946
|
-
this.
|
|
3872
|
+
/** @internal */
|
|
3873
|
+
this.views = [];
|
|
3874
|
+
this.itemsBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
3875
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
2947
3876
|
if (directive.options.positioning) {
|
|
2948
3877
|
this.bindView = bindWithPositioning;
|
|
2949
3878
|
}
|
|
@@ -2953,12 +3882,19 @@ class RepeatBehavior {
|
|
|
2953
3882
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2954
3883
|
*/
|
|
2955
3884
|
bind(controller) {
|
|
2956
|
-
this.location = controller.targets[this.directive.
|
|
3885
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
2957
3886
|
this.controller = controller;
|
|
2958
3887
|
this.items = this.itemsBindingObserver.bind(controller);
|
|
2959
3888
|
this.template = this.templateBindingObserver.bind(controller);
|
|
2960
3889
|
this.observeItems(true);
|
|
2961
|
-
this.
|
|
3890
|
+
if (isHydratable(this.template) &&
|
|
3891
|
+
isHydratable(controller) &&
|
|
3892
|
+
controller.hydrationStage !== HydrationStage.hydrated) {
|
|
3893
|
+
this.hydrateViews(this.template);
|
|
3894
|
+
}
|
|
3895
|
+
else {
|
|
3896
|
+
this.refreshAllViews();
|
|
3897
|
+
}
|
|
2962
3898
|
controller.onUnbind(this);
|
|
2963
3899
|
}
|
|
2964
3900
|
/**
|
|
@@ -3059,10 +3995,10 @@ class RepeatBehavior {
|
|
|
3059
3995
|
leftoverViews[i].dispose();
|
|
3060
3996
|
}
|
|
3061
3997
|
if (this.directive.options.positioning) {
|
|
3062
|
-
for (let i = 0,
|
|
3998
|
+
for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
|
|
3063
3999
|
const context = views[i].context;
|
|
3064
|
-
context.length =
|
|
3065
|
-
context.index =
|
|
4000
|
+
context.length = viewsLength;
|
|
4001
|
+
context.index = i;
|
|
3066
4002
|
}
|
|
3067
4003
|
}
|
|
3068
4004
|
}
|
|
@@ -3096,6 +4032,18 @@ class RepeatBehavior {
|
|
|
3096
4032
|
for (; i < itemsLength; ++i) {
|
|
3097
4033
|
if (i < viewsLength) {
|
|
3098
4034
|
const view = views[i];
|
|
4035
|
+
if (!view) {
|
|
4036
|
+
const serializer = new XMLSerializer();
|
|
4037
|
+
throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
|
|
4038
|
+
index: i,
|
|
4039
|
+
hydrationStage: this.controller
|
|
4040
|
+
.hydrationStage,
|
|
4041
|
+
itemsLength,
|
|
4042
|
+
viewsState: views.map(v => (v ? "hydrated" : "empty")),
|
|
4043
|
+
viewTemplateString: serializer.serializeToString(template.create().fragment),
|
|
4044
|
+
rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
3099
4047
|
bindView(view, items, i, controller);
|
|
3100
4048
|
}
|
|
3101
4049
|
else {
|
|
@@ -3114,7 +4062,76 @@ class RepeatBehavior {
|
|
|
3114
4062
|
unbindAllViews() {
|
|
3115
4063
|
const views = this.views;
|
|
3116
4064
|
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
3117
|
-
views[i]
|
|
4065
|
+
const view = views[i];
|
|
4066
|
+
if (!view) {
|
|
4067
|
+
const serializer = new XMLSerializer();
|
|
4068
|
+
throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
|
|
4069
|
+
index: i,
|
|
4070
|
+
hydrationStage: this.controller.hydrationStage,
|
|
4071
|
+
viewsState: views.map(v => (v ? "hydrated" : "empty")),
|
|
4072
|
+
rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
|
|
4073
|
+
});
|
|
4074
|
+
}
|
|
4075
|
+
view.unbind();
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
hydrateViews(template) {
|
|
4079
|
+
if (!this.items) {
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
4082
|
+
this.views = new Array(this.items.length);
|
|
4083
|
+
let current = this.location.previousSibling;
|
|
4084
|
+
while (current !== null) {
|
|
4085
|
+
if (!isCommentNode(current)) {
|
|
4086
|
+
current = current.previousSibling;
|
|
4087
|
+
continue;
|
|
4088
|
+
}
|
|
4089
|
+
const index = HydrationMarkup.parseRepeatEndMarker(current.data);
|
|
4090
|
+
if (index === null) {
|
|
4091
|
+
current = current.previousSibling;
|
|
4092
|
+
continue;
|
|
4093
|
+
}
|
|
4094
|
+
current.data = "";
|
|
4095
|
+
// end of repeat is the previousSibling of end comment
|
|
4096
|
+
const end = current.previousSibling;
|
|
4097
|
+
if (!end) {
|
|
4098
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": end should never be null.`);
|
|
4099
|
+
}
|
|
4100
|
+
// find start marker
|
|
4101
|
+
let start = end;
|
|
4102
|
+
// How many unmatched end markers we've encountered
|
|
4103
|
+
let unmatchedEndMarkers = 0;
|
|
4104
|
+
while (start !== null) {
|
|
4105
|
+
if (isCommentNode(start)) {
|
|
4106
|
+
if (HydrationMarkup.isRepeatViewEndMarker(start.data)) {
|
|
4107
|
+
unmatchedEndMarkers++;
|
|
4108
|
+
}
|
|
4109
|
+
else if (HydrationMarkup.isRepeatViewStartMarker(start.data)) {
|
|
4110
|
+
if (unmatchedEndMarkers) {
|
|
4111
|
+
unmatchedEndMarkers--;
|
|
4112
|
+
}
|
|
4113
|
+
else {
|
|
4114
|
+
if (HydrationMarkup.parseRepeatStartMarker(start.data) !==
|
|
4115
|
+
index) {
|
|
4116
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host
|
|
4117
|
+
.nodeName}": Mismatched start and end markers.`);
|
|
4118
|
+
}
|
|
4119
|
+
start.data = "";
|
|
4120
|
+
current = start.previousSibling;
|
|
4121
|
+
// start of repeat content is the nextSibling of start comment
|
|
4122
|
+
start = start.nextSibling;
|
|
4123
|
+
const view = template.hydrate(start, end);
|
|
4124
|
+
this.views[index] = view;
|
|
4125
|
+
this.bindView(view, this.items, index, this.controller);
|
|
4126
|
+
break;
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
start = start.previousSibling;
|
|
4131
|
+
}
|
|
4132
|
+
if (!start) {
|
|
4133
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": start should never be null.`);
|
|
4134
|
+
}
|
|
3118
4135
|
}
|
|
3119
4136
|
}
|
|
3120
4137
|
}
|
|
@@ -3133,10 +4150,6 @@ class RepeatDirective {
|
|
|
3133
4150
|
this.dataBinding = dataBinding;
|
|
3134
4151
|
this.templateBinding = templateBinding;
|
|
3135
4152
|
this.options = options;
|
|
3136
|
-
/**
|
|
3137
|
-
* The unique id of the factory.
|
|
3138
|
-
*/
|
|
3139
|
-
this.id = nextId();
|
|
3140
4153
|
ArrayObserver.enable();
|
|
3141
4154
|
}
|
|
3142
4155
|
/**
|
|
@@ -3164,8 +4177,8 @@ HTMLDirective.define(RepeatDirective);
|
|
|
3164
4177
|
* @public
|
|
3165
4178
|
*/
|
|
3166
4179
|
function repeat(items, template, options = defaultRepeatOptions) {
|
|
3167
|
-
const dataBinding = normalizeBinding(items);
|
|
3168
|
-
const templateBinding = normalizeBinding(template);
|
|
4180
|
+
const dataBinding = normalizeBinding$1(items);
|
|
4181
|
+
const templateBinding = normalizeBinding$1(template);
|
|
3169
4182
|
return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
|
|
3170
4183
|
}
|
|
3171
4184
|
|
|
@@ -3185,9 +4198,15 @@ const elements = (selector) => selector
|
|
|
3185
4198
|
* Internally used by the SlottedDirective and the ChildrenDirective.
|
|
3186
4199
|
*/
|
|
3187
4200
|
class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
4201
|
+
/**
|
|
4202
|
+
* The unique id of the factory.
|
|
4203
|
+
*/
|
|
4204
|
+
get id() {
|
|
4205
|
+
return this._id;
|
|
4206
|
+
}
|
|
4207
|
+
set id(value) {
|
|
4208
|
+
this._id = value;
|
|
4209
|
+
this._controllerProperty = `${value}-c`;
|
|
3191
4210
|
}
|
|
3192
4211
|
/**
|
|
3193
4212
|
* Bind this behavior to the source.
|
|
@@ -3196,8 +4215,8 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3196
4215
|
* @param targets - The targets that behaviors in a view can attach to.
|
|
3197
4216
|
*/
|
|
3198
4217
|
bind(controller) {
|
|
3199
|
-
const target = controller.targets[this.
|
|
3200
|
-
target[this.
|
|
4218
|
+
const target = controller.targets[this.targetNodeId];
|
|
4219
|
+
target[this._controllerProperty] = controller;
|
|
3201
4220
|
this.updateTarget(controller.source, this.computeNodes(target));
|
|
3202
4221
|
this.observe(target);
|
|
3203
4222
|
controller.onUnbind(this);
|
|
@@ -3209,10 +4228,10 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3209
4228
|
* @param targets - The targets that behaviors in a view can attach to.
|
|
3210
4229
|
*/
|
|
3211
4230
|
unbind(controller) {
|
|
3212
|
-
const target = controller.targets[this.
|
|
4231
|
+
const target = controller.targets[this.targetNodeId];
|
|
3213
4232
|
this.updateTarget(controller.source, emptyArray);
|
|
3214
4233
|
this.disconnect(target);
|
|
3215
|
-
target[this.
|
|
4234
|
+
target[this._controllerProperty] = null;
|
|
3216
4235
|
}
|
|
3217
4236
|
/**
|
|
3218
4237
|
* Gets the data source for the target.
|
|
@@ -3220,7 +4239,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
|
3220
4239
|
* @returns The source.
|
|
3221
4240
|
*/
|
|
3222
4241
|
getSource(target) {
|
|
3223
|
-
return target[this.
|
|
4242
|
+
return target[this._controllerProperty].source;
|
|
3224
4243
|
}
|
|
3225
4244
|
/**
|
|
3226
4245
|
* Updates the source property with the computed nodes.
|
|
@@ -3304,7 +4323,7 @@ class ChildrenDirective extends NodeObservationDirective {
|
|
|
3304
4323
|
*/
|
|
3305
4324
|
constructor(options) {
|
|
3306
4325
|
super(options);
|
|
3307
|
-
this.observerProperty =
|
|
4326
|
+
this.observerProperty = Symbol();
|
|
3308
4327
|
this.handleEvent = (mutations, observer) => {
|
|
3309
4328
|
const target = observer.target;
|
|
3310
4329
|
this.updateTarget(this.getSource(target), this.computeNodes(target));
|
|
@@ -3316,8 +4335,12 @@ class ChildrenDirective extends NodeObservationDirective {
|
|
|
3316
4335
|
* @param target - The target to observe.
|
|
3317
4336
|
*/
|
|
3318
4337
|
observe(target) {
|
|
3319
|
-
|
|
3320
|
-
|
|
4338
|
+
let observer = target[this.observerProperty];
|
|
4339
|
+
if (!observer) {
|
|
4340
|
+
observer = new MutationObserver(this.handleEvent);
|
|
4341
|
+
observer.toJSON = noop;
|
|
4342
|
+
target[this.observerProperty] = observer;
|
|
4343
|
+
}
|
|
3321
4344
|
observer.target = target;
|
|
3322
4345
|
observer.observe(target, this.options);
|
|
3323
4346
|
}
|
|
@@ -3389,6 +4412,21 @@ const booleanConverter = {
|
|
|
3389
4412
|
: true;
|
|
3390
4413
|
},
|
|
3391
4414
|
};
|
|
4415
|
+
/**
|
|
4416
|
+
* A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`,
|
|
4417
|
+
* and `void` values are converted to `null`.
|
|
4418
|
+
* @public
|
|
4419
|
+
*/
|
|
4420
|
+
const nullableBooleanConverter = {
|
|
4421
|
+
toView(value) {
|
|
4422
|
+
return typeof value === "boolean" ? value.toString() : "";
|
|
4423
|
+
},
|
|
4424
|
+
fromView(value) {
|
|
4425
|
+
return [null, undefined, void 0].includes(value)
|
|
4426
|
+
? null
|
|
4427
|
+
: booleanConverter.fromView(value);
|
|
4428
|
+
},
|
|
4429
|
+
};
|
|
3392
4430
|
function toNumber(value) {
|
|
3393
4431
|
if (value === null || value === undefined) {
|
|
3394
4432
|
return null;
|
|
@@ -3554,7 +4592,8 @@ function attr(configOrTarget, prop) {
|
|
|
3554
4592
|
|
|
3555
4593
|
const defaultShadowOptions = { mode: "open" };
|
|
3556
4594
|
const defaultElementOptions = {};
|
|
3557
|
-
const
|
|
4595
|
+
const fastElementBaseTypes = new Set();
|
|
4596
|
+
const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
|
|
3558
4597
|
/**
|
|
3559
4598
|
* Defines metadata for a FASTElement.
|
|
3560
4599
|
* @public
|
|
@@ -3602,51 +4641,564 @@ class FASTElementDefinition {
|
|
|
3602
4641
|
this.styles = ElementStyles.normalize(nameOrConfig.styles);
|
|
3603
4642
|
fastElementRegistry.register(this);
|
|
3604
4643
|
}
|
|
3605
|
-
/**
|
|
3606
|
-
* Indicates if this element has been defined in at least one registry.
|
|
3607
|
-
*/
|
|
3608
|
-
get isDefined() {
|
|
3609
|
-
return this.platformDefined;
|
|
4644
|
+
/**
|
|
4645
|
+
* Indicates if this element has been defined in at least one registry.
|
|
4646
|
+
*/
|
|
4647
|
+
get isDefined() {
|
|
4648
|
+
return this.platformDefined;
|
|
4649
|
+
}
|
|
4650
|
+
/**
|
|
4651
|
+
* Defines a custom element based on this definition.
|
|
4652
|
+
* @param registry - The element registry to define the element in.
|
|
4653
|
+
* @remarks
|
|
4654
|
+
* This operation is idempotent per registry.
|
|
4655
|
+
*/
|
|
4656
|
+
define(registry = this.registry) {
|
|
4657
|
+
const type = this.type;
|
|
4658
|
+
if (!registry.get(this.name)) {
|
|
4659
|
+
this.platformDefined = true;
|
|
4660
|
+
registry.define(this.name, type, this.elementOptions);
|
|
4661
|
+
}
|
|
4662
|
+
return this;
|
|
4663
|
+
}
|
|
4664
|
+
/**
|
|
4665
|
+
* Creates an instance of FASTElementDefinition.
|
|
4666
|
+
* @param type - The type this definition is being created for.
|
|
4667
|
+
* @param nameOrDef - The name of the element to define or a config object
|
|
4668
|
+
* that describes the element to define.
|
|
4669
|
+
*/
|
|
4670
|
+
static compose(type, nameOrDef) {
|
|
4671
|
+
if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
|
|
4672
|
+
return new FASTElementDefinition(class extends type {
|
|
4673
|
+
}, nameOrDef);
|
|
4674
|
+
}
|
|
4675
|
+
return new FASTElementDefinition(type, nameOrDef);
|
|
4676
|
+
}
|
|
4677
|
+
/**
|
|
4678
|
+
* Registers a FASTElement base type.
|
|
4679
|
+
* @param type - The type to register as a base type.
|
|
4680
|
+
* @internal
|
|
4681
|
+
*/
|
|
4682
|
+
static registerBaseType(type) {
|
|
4683
|
+
fastElementBaseTypes.add(type);
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
/**
|
|
4687
|
+
* Gets the element definition associated with the specified type.
|
|
4688
|
+
* @param type - The custom element type to retrieve the definition for.
|
|
4689
|
+
*/
|
|
4690
|
+
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
4691
|
+
/**
|
|
4692
|
+
* Gets the element definition associated with the instance.
|
|
4693
|
+
* @param instance - The custom element instance to retrieve the definition for.
|
|
4694
|
+
*/
|
|
4695
|
+
FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
|
|
4696
|
+
|
|
4697
|
+
/**
|
|
4698
|
+
* A Behavior that enables advanced rendering.
|
|
4699
|
+
* @public
|
|
4700
|
+
*/
|
|
4701
|
+
class RenderBehavior {
|
|
4702
|
+
/**
|
|
4703
|
+
* Creates an instance of RenderBehavior.
|
|
4704
|
+
* @param directive - The render directive that created this behavior.
|
|
4705
|
+
*/
|
|
4706
|
+
constructor(directive) {
|
|
4707
|
+
this.directive = directive;
|
|
4708
|
+
this.location = null;
|
|
4709
|
+
this.controller = null;
|
|
4710
|
+
this.view = null;
|
|
4711
|
+
this.data = null;
|
|
4712
|
+
this.dataBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
4713
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
4714
|
+
}
|
|
4715
|
+
/**
|
|
4716
|
+
* Bind this behavior.
|
|
4717
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
4718
|
+
*/
|
|
4719
|
+
bind(controller) {
|
|
4720
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
4721
|
+
this.controller = controller;
|
|
4722
|
+
this.data = this.dataBindingObserver.bind(controller);
|
|
4723
|
+
this.template = this.templateBindingObserver.bind(controller);
|
|
4724
|
+
controller.onUnbind(this);
|
|
4725
|
+
if (isHydratable(this.template) &&
|
|
4726
|
+
isHydratable(controller) &&
|
|
4727
|
+
controller.hydrationStage !== HydrationStage.hydrated &&
|
|
4728
|
+
!this.view) {
|
|
4729
|
+
const viewNodes = controller.bindingViewBoundaries[this.directive.targetNodeId];
|
|
4730
|
+
if (viewNodes) {
|
|
4731
|
+
this.view = this.template.hydrate(viewNodes.first, viewNodes.last);
|
|
4732
|
+
this.bindView(this.view);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
else {
|
|
4736
|
+
this.refreshView();
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
/**
|
|
4740
|
+
* Unbinds this behavior.
|
|
4741
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
4742
|
+
*/
|
|
4743
|
+
unbind(controller) {
|
|
4744
|
+
const view = this.view;
|
|
4745
|
+
if (view !== null && view.isComposed) {
|
|
4746
|
+
view.unbind();
|
|
4747
|
+
view.needsBindOnly = true;
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
4750
|
+
/** @internal */
|
|
4751
|
+
handleChange(source, observer) {
|
|
4752
|
+
if (observer === this.dataBindingObserver) {
|
|
4753
|
+
this.data = this.dataBindingObserver.bind(this.controller);
|
|
4754
|
+
}
|
|
4755
|
+
if (this.directive.templateBindingDependsOnData ||
|
|
4756
|
+
observer === this.templateBindingObserver) {
|
|
4757
|
+
this.template = this.templateBindingObserver.bind(this.controller);
|
|
4758
|
+
}
|
|
4759
|
+
this.refreshView();
|
|
4760
|
+
}
|
|
4761
|
+
bindView(view) {
|
|
4762
|
+
// It's possible that the value is the same as the previous template
|
|
4763
|
+
// and that there's actually no need to compose it.
|
|
4764
|
+
if (!view.isComposed) {
|
|
4765
|
+
view.isComposed = true;
|
|
4766
|
+
view.bind(this.data);
|
|
4767
|
+
view.insertBefore(this.location);
|
|
4768
|
+
view.$fastTemplate = this.template;
|
|
4769
|
+
}
|
|
4770
|
+
else if (view.needsBindOnly) {
|
|
4771
|
+
view.needsBindOnly = false;
|
|
4772
|
+
view.bind(this.data);
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
refreshView() {
|
|
4776
|
+
let view = this.view;
|
|
4777
|
+
const template = this.template;
|
|
4778
|
+
if (view === null) {
|
|
4779
|
+
this.view = view = template.create();
|
|
4780
|
+
this.view.context.parent = this.controller.source;
|
|
4781
|
+
this.view.context.parentContext = this.controller.context;
|
|
4782
|
+
}
|
|
4783
|
+
else {
|
|
4784
|
+
// If there is a previous view, but it wasn't created
|
|
4785
|
+
// from the same template as the new value, then we
|
|
4786
|
+
// need to remove the old view if it's still in the DOM
|
|
4787
|
+
// and create a new view from the template.
|
|
4788
|
+
if (view.$fastTemplate !== template) {
|
|
4789
|
+
if (view.isComposed) {
|
|
4790
|
+
view.remove();
|
|
4791
|
+
view.unbind();
|
|
4792
|
+
}
|
|
4793
|
+
this.view = view = template.create();
|
|
4794
|
+
this.view.context.parent = this.controller.source;
|
|
4795
|
+
this.view.context.parentContext = this.controller.context;
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
this.bindView(view);
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
/**
|
|
4802
|
+
* A Directive that enables use of the RenderBehavior.
|
|
4803
|
+
* @public
|
|
4804
|
+
*/
|
|
4805
|
+
class RenderDirective {
|
|
4806
|
+
/**
|
|
4807
|
+
* Creates an instance of RenderDirective.
|
|
4808
|
+
* @param dataBinding - A binding expression that returns the data to render.
|
|
4809
|
+
* @param templateBinding - A binding expression that returns the template to use to render the data.
|
|
4810
|
+
*/
|
|
4811
|
+
constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
|
|
4812
|
+
this.dataBinding = dataBinding;
|
|
4813
|
+
this.templateBinding = templateBinding;
|
|
4814
|
+
this.templateBindingDependsOnData = templateBindingDependsOnData;
|
|
4815
|
+
}
|
|
4816
|
+
/**
|
|
4817
|
+
* Creates HTML to be used within a template.
|
|
4818
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
4819
|
+
*/
|
|
4820
|
+
createHTML(add) {
|
|
4821
|
+
return Markup.comment(add(this));
|
|
4822
|
+
}
|
|
4823
|
+
/**
|
|
4824
|
+
* Creates a behavior.
|
|
4825
|
+
* @param targets - The targets available for behaviors to be attached to.
|
|
4826
|
+
*/
|
|
4827
|
+
createBehavior() {
|
|
4828
|
+
return new RenderBehavior(this);
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
HTMLDirective.define(RenderDirective);
|
|
4832
|
+
function isElementRenderOptions(object) {
|
|
4833
|
+
return !!object.element || !!object.tagName;
|
|
4834
|
+
}
|
|
4835
|
+
const typeToInstructionLookup = new Map();
|
|
4836
|
+
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
4837
|
+
const defaultAttributes = { ":model": (x) => x };
|
|
4838
|
+
const brand = Symbol("RenderInstruction");
|
|
4839
|
+
const defaultViewName = "default-view";
|
|
4840
|
+
const nullTemplate = html `
|
|
4841
|
+
|
|
4842
|
+
`;
|
|
4843
|
+
function instructionToTemplate(def) {
|
|
4844
|
+
if (def === void 0) {
|
|
4845
|
+
return nullTemplate;
|
|
4846
|
+
}
|
|
4847
|
+
return def.template;
|
|
4848
|
+
}
|
|
4849
|
+
function createElementTemplate(tagName, options) {
|
|
4850
|
+
const markup = [];
|
|
4851
|
+
const values = [];
|
|
4852
|
+
const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
|
|
4853
|
+
markup.push(`<${tagName}`);
|
|
4854
|
+
if (attributes) {
|
|
4855
|
+
const attrNames = Object.getOwnPropertyNames(attributes);
|
|
4856
|
+
for (let i = 0, ii = attrNames.length; i < ii; ++i) {
|
|
4857
|
+
const name = attrNames[i];
|
|
4858
|
+
if (i === 0) {
|
|
4859
|
+
markup[0] = `${markup[0]} ${name}="`;
|
|
4860
|
+
}
|
|
4861
|
+
else {
|
|
4862
|
+
markup.push(`" ${name}="`);
|
|
4863
|
+
}
|
|
4864
|
+
values.push(attributes[name]);
|
|
4865
|
+
}
|
|
4866
|
+
markup.push(`"`);
|
|
4867
|
+
}
|
|
4868
|
+
if (directives) {
|
|
4869
|
+
markup[markup.length - 1] += " ";
|
|
4870
|
+
for (let i = 0, ii = directives.length; i < ii; ++i) {
|
|
4871
|
+
const directive = directives[i];
|
|
4872
|
+
markup.push(i > 0 ? "" : " ");
|
|
4873
|
+
values.push(directive);
|
|
4874
|
+
}
|
|
4875
|
+
}
|
|
4876
|
+
markup[markup.length - 1] += ">";
|
|
4877
|
+
if (content && isFunction(content.create)) {
|
|
4878
|
+
values.push(content);
|
|
4879
|
+
markup.push(`</${tagName}>`);
|
|
4880
|
+
}
|
|
4881
|
+
else {
|
|
4882
|
+
const lastIndex = markup.length - 1;
|
|
4883
|
+
markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
|
|
4884
|
+
}
|
|
4885
|
+
return ViewTemplate.create(markup, values, policy);
|
|
4886
|
+
}
|
|
4887
|
+
function create(options) {
|
|
4888
|
+
var _a;
|
|
4889
|
+
const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
|
|
4890
|
+
let template;
|
|
4891
|
+
if (isElementRenderOptions(options)) {
|
|
4892
|
+
let tagName = options.tagName;
|
|
4893
|
+
if (!tagName) {
|
|
4894
|
+
const def = FASTElementDefinition.getByType(options.element);
|
|
4895
|
+
if (def) {
|
|
4896
|
+
tagName = def.name;
|
|
4897
|
+
}
|
|
4898
|
+
else {
|
|
4899
|
+
throw new Error("Invalid element for model rendering.");
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
if (!options.attributes) {
|
|
4903
|
+
options.attributes = defaultAttributes;
|
|
4904
|
+
}
|
|
4905
|
+
template = createElementTemplate(tagName, options);
|
|
4906
|
+
}
|
|
4907
|
+
else {
|
|
4908
|
+
template = options.template;
|
|
4909
|
+
}
|
|
4910
|
+
return {
|
|
4911
|
+
brand,
|
|
4912
|
+
type: options.type,
|
|
4913
|
+
name,
|
|
4914
|
+
template,
|
|
4915
|
+
};
|
|
4916
|
+
}
|
|
4917
|
+
function instanceOf(object) {
|
|
4918
|
+
return object && object.brand === brand;
|
|
4919
|
+
}
|
|
4920
|
+
function register(optionsOrInstruction) {
|
|
4921
|
+
let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
|
|
4922
|
+
if (lookup === void 0) {
|
|
4923
|
+
typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
|
|
4924
|
+
}
|
|
4925
|
+
const instruction = instanceOf(optionsOrInstruction)
|
|
4926
|
+
? optionsOrInstruction
|
|
4927
|
+
: create(optionsOrInstruction);
|
|
4928
|
+
return (lookup[instruction.name] = instruction);
|
|
4929
|
+
}
|
|
4930
|
+
function getByType(type, name) {
|
|
4931
|
+
const entries = typeToInstructionLookup.get(type);
|
|
4932
|
+
if (entries === void 0) {
|
|
4933
|
+
return void 0;
|
|
4934
|
+
}
|
|
4935
|
+
return entries[name !== null && name !== void 0 ? name : defaultViewName];
|
|
4936
|
+
}
|
|
4937
|
+
function getForInstance(object, name) {
|
|
4938
|
+
if (object) {
|
|
4939
|
+
return getByType(object.constructor, name);
|
|
4940
|
+
}
|
|
4941
|
+
return void 0;
|
|
4942
|
+
}
|
|
4943
|
+
/**
|
|
4944
|
+
* Provides APIs for creating and interacting with render instructions.
|
|
4945
|
+
* @public
|
|
4946
|
+
*/
|
|
4947
|
+
Object.freeze({
|
|
4948
|
+
/**
|
|
4949
|
+
* Checks whether the provided object is a RenderInstruction.
|
|
4950
|
+
* @param object - The object to check.
|
|
4951
|
+
* @returns true if the object is a RenderInstruction; false otherwise
|
|
4952
|
+
*/
|
|
4953
|
+
instanceOf,
|
|
4954
|
+
/**
|
|
4955
|
+
* Creates a RenderInstruction for a set of options.
|
|
4956
|
+
* @param options - The options to use when creating the RenderInstruction.
|
|
4957
|
+
* @remarks
|
|
4958
|
+
* This API should be used with caution. When providing attributes or content,
|
|
4959
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
4960
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
4961
|
+
* content and attribute values particularly if they can come from user input.
|
|
4962
|
+
*/
|
|
4963
|
+
create,
|
|
4964
|
+
/**
|
|
4965
|
+
* Creates a template based on a tag name.
|
|
4966
|
+
* @param tagName - The tag name to use when creating the template.
|
|
4967
|
+
* @param attributes - The attributes to apply to the element.
|
|
4968
|
+
* @param content - The content to insert into the element.
|
|
4969
|
+
* @param policy - The DOMPolicy to create the template with.
|
|
4970
|
+
* @returns A template based on the provided specifications.
|
|
4971
|
+
* @remarks
|
|
4972
|
+
* This API should be used with caution. When providing attributes or content,
|
|
4973
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
4974
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
4975
|
+
* content and attribute values particularly if they can come from user input.
|
|
4976
|
+
*/
|
|
4977
|
+
createElementTemplate,
|
|
4978
|
+
/**
|
|
4979
|
+
* Creates and registers an instruction.
|
|
4980
|
+
* @param options The options to use when creating the RenderInstruction.
|
|
4981
|
+
* @remarks
|
|
4982
|
+
* A previously created RenderInstruction can also be registered.
|
|
4983
|
+
*/
|
|
4984
|
+
register,
|
|
4985
|
+
/**
|
|
4986
|
+
* Finds a previously registered RenderInstruction by type and optionally by name.
|
|
4987
|
+
* @param type - The type to retrieve the RenderInstruction for.
|
|
4988
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
4989
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
4990
|
+
*/
|
|
4991
|
+
getByType,
|
|
4992
|
+
/**
|
|
4993
|
+
* Finds a previously registered RenderInstruction for the instance's type and optionally by name.
|
|
4994
|
+
* @param object - The instance to retrieve the RenderInstruction for.
|
|
4995
|
+
* @param name - An optional name used in differentiating between multiple registered instructions.
|
|
4996
|
+
* @returns The located RenderInstruction that matches the criteria or undefined if none is found.
|
|
4997
|
+
*/
|
|
4998
|
+
getForInstance,
|
|
4999
|
+
});
|
|
5000
|
+
/**
|
|
5001
|
+
* @internal
|
|
5002
|
+
*/
|
|
5003
|
+
class NodeTemplate {
|
|
5004
|
+
constructor(node) {
|
|
5005
|
+
this.node = node;
|
|
5006
|
+
node.$fastTemplate = this;
|
|
5007
|
+
}
|
|
5008
|
+
get context() {
|
|
5009
|
+
// HACK
|
|
5010
|
+
return this;
|
|
5011
|
+
}
|
|
5012
|
+
bind(source) { }
|
|
5013
|
+
unbind() { }
|
|
5014
|
+
insertBefore(refNode) {
|
|
5015
|
+
refNode.parentNode.insertBefore(this.node, refNode);
|
|
5016
|
+
}
|
|
5017
|
+
remove() {
|
|
5018
|
+
this.node.parentNode.removeChild(this.node);
|
|
5019
|
+
}
|
|
5020
|
+
create() {
|
|
5021
|
+
return this;
|
|
5022
|
+
}
|
|
5023
|
+
hydrate(first, last) {
|
|
5024
|
+
return this;
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
/**
|
|
5028
|
+
* Creates a RenderDirective for use in advanced rendering scenarios.
|
|
5029
|
+
* @param value - The binding expression that returns the data to be rendered. The expression
|
|
5030
|
+
* can also return a Node to render directly.
|
|
5031
|
+
* @param template - A template to render the data with
|
|
5032
|
+
* or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
|
|
5033
|
+
* Expressions can also be provided to dynamically determine either the template or the name.
|
|
5034
|
+
* @returns A RenderDirective suitable for use in a template.
|
|
5035
|
+
* @remarks
|
|
5036
|
+
* If no binding is provided, then a default binding that returns the source is created.
|
|
5037
|
+
* If no template is provided, then a binding is created that will use registered
|
|
5038
|
+
* RenderInstructions to determine the view.
|
|
5039
|
+
* If the template binding returns a string, then it will be used to look up a
|
|
5040
|
+
* RenderInstruction to determine the view.
|
|
5041
|
+
* @public
|
|
5042
|
+
*/
|
|
5043
|
+
function render(value, template) {
|
|
5044
|
+
let dataBinding;
|
|
5045
|
+
if (value === void 0) {
|
|
5046
|
+
dataBinding = oneTime((source) => source);
|
|
5047
|
+
}
|
|
5048
|
+
else {
|
|
5049
|
+
dataBinding = normalizeBinding$1(value);
|
|
5050
|
+
}
|
|
5051
|
+
let templateBinding;
|
|
5052
|
+
let templateBindingDependsOnData = false;
|
|
5053
|
+
if (template === void 0) {
|
|
5054
|
+
templateBindingDependsOnData = true;
|
|
5055
|
+
templateBinding = oneTime((s, c) => {
|
|
5056
|
+
var _a;
|
|
5057
|
+
const data = dataBinding.evaluate(s, c);
|
|
5058
|
+
if (data instanceof Node) {
|
|
5059
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
5060
|
+
}
|
|
5061
|
+
return instructionToTemplate(getForInstance(data));
|
|
5062
|
+
});
|
|
5063
|
+
}
|
|
5064
|
+
else if (isFunction(template)) {
|
|
5065
|
+
templateBinding = oneWay((s, c) => {
|
|
5066
|
+
var _a;
|
|
5067
|
+
let result = template(s, c);
|
|
5068
|
+
if (isString(result)) {
|
|
5069
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
5070
|
+
}
|
|
5071
|
+
else if (result instanceof Node) {
|
|
5072
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
5073
|
+
}
|
|
5074
|
+
return result;
|
|
5075
|
+
}, void 0, true);
|
|
5076
|
+
}
|
|
5077
|
+
else if (isString(template)) {
|
|
5078
|
+
templateBindingDependsOnData = true;
|
|
5079
|
+
templateBinding = oneTime((s, c) => {
|
|
5080
|
+
var _a;
|
|
5081
|
+
const data = dataBinding.evaluate(s, c);
|
|
5082
|
+
if (data instanceof Node) {
|
|
5083
|
+
return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
|
|
5084
|
+
}
|
|
5085
|
+
return instructionToTemplate(getForInstance(data, template));
|
|
5086
|
+
});
|
|
5087
|
+
}
|
|
5088
|
+
else if (template instanceof Binding) {
|
|
5089
|
+
const evaluateTemplate = template.evaluate;
|
|
5090
|
+
template.evaluate = (s, c) => {
|
|
5091
|
+
var _a;
|
|
5092
|
+
let result = evaluateTemplate(s, c);
|
|
5093
|
+
if (isString(result)) {
|
|
5094
|
+
result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
|
|
5095
|
+
}
|
|
5096
|
+
else if (result instanceof Node) {
|
|
5097
|
+
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
5098
|
+
}
|
|
5099
|
+
return result;
|
|
5100
|
+
};
|
|
5101
|
+
templateBinding = template;
|
|
5102
|
+
}
|
|
5103
|
+
else {
|
|
5104
|
+
templateBinding = oneTime((s, c) => template);
|
|
3610
5105
|
}
|
|
5106
|
+
return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
/**
|
|
5110
|
+
* An extension of MutationObserver that supports unobserving nodes.
|
|
5111
|
+
* @internal
|
|
5112
|
+
*/
|
|
5113
|
+
class UnobservableMutationObserver extends MutationObserver {
|
|
3611
5114
|
/**
|
|
3612
|
-
*
|
|
3613
|
-
* @param
|
|
3614
|
-
* @remarks
|
|
3615
|
-
* This operation is idempotent per registry.
|
|
5115
|
+
* Creates an instance of UnobservableMutationObserver.
|
|
5116
|
+
* @param callback - The callback to invoke when observed nodes are changed.
|
|
3616
5117
|
*/
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
this.platformDefined = true;
|
|
3621
|
-
registry.define(this.name, type, this.elementOptions);
|
|
5118
|
+
constructor(callback) {
|
|
5119
|
+
function handler(mutations) {
|
|
5120
|
+
this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
|
|
3622
5121
|
}
|
|
3623
|
-
|
|
5122
|
+
super(handler);
|
|
5123
|
+
this.callback = callback;
|
|
5124
|
+
this.observedNodes = new Set();
|
|
3624
5125
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
if (found) {
|
|
3634
|
-
return new FASTElementDefinition(class extends type {
|
|
3635
|
-
}, nameOrDef);
|
|
5126
|
+
observe(target, options) {
|
|
5127
|
+
this.observedNodes.add(target);
|
|
5128
|
+
super.observe(target, options);
|
|
5129
|
+
}
|
|
5130
|
+
unobserve(target) {
|
|
5131
|
+
this.observedNodes.delete(target);
|
|
5132
|
+
if (this.observedNodes.size < 1) {
|
|
5133
|
+
this.disconnect();
|
|
3636
5134
|
}
|
|
3637
|
-
return new FASTElementDefinition(type, nameOrDef);
|
|
3638
5135
|
}
|
|
3639
5136
|
}
|
|
3640
5137
|
/**
|
|
3641
|
-
*
|
|
3642
|
-
*
|
|
3643
|
-
|
|
3644
|
-
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
3645
|
-
/**
|
|
3646
|
-
* Gets the element definition associated with the instance.
|
|
3647
|
-
* @param instance - The custom element instance to retrieve the definition for.
|
|
5138
|
+
* Bridges between ViewBehaviors and HostBehaviors, enabling a host to
|
|
5139
|
+
* control ViewBehaviors.
|
|
5140
|
+
* @public
|
|
3648
5141
|
*/
|
|
3649
|
-
|
|
5142
|
+
Object.freeze({
|
|
5143
|
+
/**
|
|
5144
|
+
* Creates a ViewBehaviorOrchestrator.
|
|
5145
|
+
* @param source - The source to to associate behaviors with.
|
|
5146
|
+
* @returns A ViewBehaviorOrchestrator.
|
|
5147
|
+
*/
|
|
5148
|
+
create(source) {
|
|
5149
|
+
const behaviors = [];
|
|
5150
|
+
const targets = {};
|
|
5151
|
+
let unbindables = null;
|
|
5152
|
+
let isConnected = false;
|
|
5153
|
+
return {
|
|
5154
|
+
source,
|
|
5155
|
+
context: ExecutionContext.default,
|
|
5156
|
+
targets,
|
|
5157
|
+
get isBound() {
|
|
5158
|
+
return isConnected;
|
|
5159
|
+
},
|
|
5160
|
+
addBehaviorFactory(factory, target) {
|
|
5161
|
+
var _a, _b, _c, _d;
|
|
5162
|
+
const compiled = factory;
|
|
5163
|
+
compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
|
|
5164
|
+
compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
|
|
5165
|
+
compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
|
|
5166
|
+
compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
|
|
5167
|
+
this.addTarget(compiled.targetNodeId, target);
|
|
5168
|
+
this.addBehavior(compiled.createBehavior());
|
|
5169
|
+
},
|
|
5170
|
+
addTarget(nodeId, target) {
|
|
5171
|
+
targets[nodeId] = target;
|
|
5172
|
+
},
|
|
5173
|
+
addBehavior(behavior) {
|
|
5174
|
+
behaviors.push(behavior);
|
|
5175
|
+
if (isConnected) {
|
|
5176
|
+
behavior.bind(this);
|
|
5177
|
+
}
|
|
5178
|
+
},
|
|
5179
|
+
onUnbind(unbindable) {
|
|
5180
|
+
if (unbindables === null) {
|
|
5181
|
+
unbindables = [];
|
|
5182
|
+
}
|
|
5183
|
+
unbindables.push(unbindable);
|
|
5184
|
+
},
|
|
5185
|
+
connectedCallback(controller) {
|
|
5186
|
+
if (!isConnected) {
|
|
5187
|
+
isConnected = true;
|
|
5188
|
+
behaviors.forEach(x => x.bind(this));
|
|
5189
|
+
}
|
|
5190
|
+
},
|
|
5191
|
+
disconnectedCallback(controller) {
|
|
5192
|
+
if (isConnected) {
|
|
5193
|
+
isConnected = false;
|
|
5194
|
+
if (unbindables !== null) {
|
|
5195
|
+
unbindables.forEach(x => x.unbind(this));
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
},
|
|
5199
|
+
};
|
|
5200
|
+
},
|
|
5201
|
+
});
|
|
3650
5202
|
|
|
3651
5203
|
const defaultEventOptions = {
|
|
3652
5204
|
bubbles: true,
|
|
@@ -3659,6 +5211,7 @@ function getShadowRoot(element) {
|
|
|
3659
5211
|
var _a, _b;
|
|
3660
5212
|
return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
|
|
3661
5213
|
}
|
|
5214
|
+
let elementControllerStrategy;
|
|
3662
5215
|
/**
|
|
3663
5216
|
* Controls the lifecycle and rendering of a `FASTElement`.
|
|
3664
5217
|
* @public
|
|
@@ -3677,8 +5230,19 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3677
5230
|
this.needsInitialization = true;
|
|
3678
5231
|
this.hasExistingShadowRoot = false;
|
|
3679
5232
|
this._template = null;
|
|
3680
|
-
this.
|
|
5233
|
+
this.stage = 3 /* Stages.disconnected */;
|
|
5234
|
+
/**
|
|
5235
|
+
* A guard against connecting behaviors multiple times
|
|
5236
|
+
* during connect in scenarios where a behavior adds
|
|
5237
|
+
* another behavior during it's connectedCallback
|
|
5238
|
+
*/
|
|
5239
|
+
this.guardBehaviorConnection = false;
|
|
3681
5240
|
this.behaviors = null;
|
|
5241
|
+
/**
|
|
5242
|
+
* Tracks whether behaviors are connected so that
|
|
5243
|
+
* behaviors cant be connected multiple times
|
|
5244
|
+
*/
|
|
5245
|
+
this.behaviorsConnected = false;
|
|
3682
5246
|
this._mainStyles = null;
|
|
3683
5247
|
/**
|
|
3684
5248
|
* This allows Observable.getNotifier(...) to return the Controller
|
|
@@ -3733,11 +5297,28 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3733
5297
|
*/
|
|
3734
5298
|
get isConnected() {
|
|
3735
5299
|
Observable.track(this, isConnectedPropertyName);
|
|
3736
|
-
return this.
|
|
5300
|
+
return this.stage === 1 /* Stages.connected */;
|
|
3737
5301
|
}
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
5302
|
+
/**
|
|
5303
|
+
* The context the expression is evaluated against.
|
|
5304
|
+
*/
|
|
5305
|
+
get context() {
|
|
5306
|
+
var _a, _b;
|
|
5307
|
+
return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
|
|
5308
|
+
}
|
|
5309
|
+
/**
|
|
5310
|
+
* Indicates whether the controller is bound.
|
|
5311
|
+
*/
|
|
5312
|
+
get isBound() {
|
|
5313
|
+
var _a, _b;
|
|
5314
|
+
return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
|
|
5315
|
+
}
|
|
5316
|
+
/**
|
|
5317
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
5318
|
+
*/
|
|
5319
|
+
get sourceLifetime() {
|
|
5320
|
+
var _a;
|
|
5321
|
+
return (_a = this.view) === null || _a === void 0 ? void 0 : _a.sourceLifetime;
|
|
3741
5322
|
}
|
|
3742
5323
|
/**
|
|
3743
5324
|
* Gets/sets the template used to render the component.
|
|
@@ -3801,6 +5382,14 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3801
5382
|
this.addStyles(value);
|
|
3802
5383
|
}
|
|
3803
5384
|
}
|
|
5385
|
+
/**
|
|
5386
|
+
* Registers an unbind handler with the controller.
|
|
5387
|
+
* @param behavior - An object to call when the controller unbinds.
|
|
5388
|
+
*/
|
|
5389
|
+
onUnbind(behavior) {
|
|
5390
|
+
var _a;
|
|
5391
|
+
(_a = this.view) === null || _a === void 0 ? void 0 : _a.onUnbind(behavior);
|
|
5392
|
+
}
|
|
3804
5393
|
/**
|
|
3805
5394
|
* Adds the behavior to the component.
|
|
3806
5395
|
* @param behavior - The behavior to add.
|
|
@@ -3812,7 +5401,9 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3812
5401
|
if (count === 0) {
|
|
3813
5402
|
targetBehaviors.set(behavior, 1);
|
|
3814
5403
|
behavior.addedCallback && behavior.addedCallback(this);
|
|
3815
|
-
if (behavior.connectedCallback &&
|
|
5404
|
+
if (behavior.connectedCallback &&
|
|
5405
|
+
!this.guardBehaviorConnection &&
|
|
5406
|
+
(this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
|
|
3816
5407
|
behavior.connectedCallback(this);
|
|
3817
5408
|
}
|
|
3818
5409
|
}
|
|
@@ -3836,7 +5427,7 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3836
5427
|
}
|
|
3837
5428
|
if (count === 1 || force) {
|
|
3838
5429
|
targetBehaviors.delete(behavior);
|
|
3839
|
-
if (behavior.disconnectedCallback && this.
|
|
5430
|
+
if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
|
|
3840
5431
|
behavior.disconnectedCallback(this);
|
|
3841
5432
|
}
|
|
3842
5433
|
behavior.removedCallback && behavior.removedCallback(this);
|
|
@@ -3855,13 +5446,13 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3855
5446
|
return;
|
|
3856
5447
|
}
|
|
3857
5448
|
const source = this.source;
|
|
3858
|
-
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3859
5449
|
if (styles instanceof HTMLElement) {
|
|
5450
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
|
|
3860
5451
|
target.append(styles);
|
|
3861
5452
|
}
|
|
3862
|
-
else if (!styles.isAttachedTo(
|
|
5453
|
+
else if (!styles.isAttachedTo(source)) {
|
|
3863
5454
|
const sourceBehaviors = styles.behaviors;
|
|
3864
|
-
styles.addStylesTo(
|
|
5455
|
+
styles.addStylesTo(source);
|
|
3865
5456
|
if (sourceBehaviors !== null) {
|
|
3866
5457
|
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3867
5458
|
this.addBehavior(sourceBehaviors[i]);
|
|
@@ -3879,16 +5470,16 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3879
5470
|
return;
|
|
3880
5471
|
}
|
|
3881
5472
|
const source = this.source;
|
|
3882
|
-
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3883
5473
|
if (styles instanceof HTMLElement) {
|
|
5474
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
|
|
3884
5475
|
target.removeChild(styles);
|
|
3885
5476
|
}
|
|
3886
|
-
else if (styles.isAttachedTo(
|
|
5477
|
+
else if (styles.isAttachedTo(source)) {
|
|
3887
5478
|
const sourceBehaviors = styles.behaviors;
|
|
3888
|
-
styles.removeStylesFrom(
|
|
5479
|
+
styles.removeStylesFrom(source);
|
|
3889
5480
|
if (sourceBehaviors !== null) {
|
|
3890
5481
|
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3891
|
-
this.
|
|
5482
|
+
this.removeBehavior(sourceBehaviors[i]);
|
|
3892
5483
|
}
|
|
3893
5484
|
}
|
|
3894
5485
|
}
|
|
@@ -3897,40 +5488,73 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3897
5488
|
* Runs connected lifecycle behavior on the associated element.
|
|
3898
5489
|
*/
|
|
3899
5490
|
connect() {
|
|
3900
|
-
if (this.
|
|
5491
|
+
if (this.stage !== 3 /* Stages.disconnected */) {
|
|
3901
5492
|
return;
|
|
3902
5493
|
}
|
|
5494
|
+
this.stage = 0 /* Stages.connecting */;
|
|
5495
|
+
this.bindObservables();
|
|
5496
|
+
this.connectBehaviors();
|
|
3903
5497
|
if (this.needsInitialization) {
|
|
3904
|
-
this.
|
|
5498
|
+
this.renderTemplate(this.template);
|
|
5499
|
+
this.addStyles(this.mainStyles);
|
|
5500
|
+
this.needsInitialization = false;
|
|
3905
5501
|
}
|
|
3906
5502
|
else if (this.view !== null) {
|
|
3907
5503
|
this.view.bind(this.source);
|
|
3908
5504
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
5505
|
+
this.stage = 1 /* Stages.connected */;
|
|
5506
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
5507
|
+
}
|
|
5508
|
+
bindObservables() {
|
|
5509
|
+
if (this.boundObservables !== null) {
|
|
5510
|
+
const element = this.source;
|
|
5511
|
+
const boundObservables = this.boundObservables;
|
|
5512
|
+
const propertyNames = Object.keys(boundObservables);
|
|
5513
|
+
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
5514
|
+
const propertyName = propertyNames[i];
|
|
5515
|
+
element[propertyName] = boundObservables[propertyName];
|
|
5516
|
+
}
|
|
5517
|
+
this.boundObservables = null;
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
connectBehaviors() {
|
|
5521
|
+
if (this.behaviorsConnected === false) {
|
|
5522
|
+
const behaviors = this.behaviors;
|
|
5523
|
+
if (behaviors !== null) {
|
|
5524
|
+
this.guardBehaviorConnection = true;
|
|
5525
|
+
for (const key of behaviors.keys()) {
|
|
5526
|
+
key.connectedCallback && key.connectedCallback(this);
|
|
5527
|
+
}
|
|
5528
|
+
this.guardBehaviorConnection = false;
|
|
5529
|
+
}
|
|
5530
|
+
this.behaviorsConnected = true;
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
disconnectBehaviors() {
|
|
5534
|
+
if (this.behaviorsConnected === true) {
|
|
5535
|
+
const behaviors = this.behaviors;
|
|
5536
|
+
if (behaviors !== null) {
|
|
5537
|
+
for (const key of behaviors.keys()) {
|
|
5538
|
+
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
5539
|
+
}
|
|
3913
5540
|
}
|
|
5541
|
+
this.behaviorsConnected = false;
|
|
3914
5542
|
}
|
|
3915
|
-
this.setIsConnected(true);
|
|
3916
5543
|
}
|
|
3917
5544
|
/**
|
|
3918
5545
|
* Runs disconnected lifecycle behavior on the associated element.
|
|
3919
5546
|
*/
|
|
3920
5547
|
disconnect() {
|
|
3921
|
-
if (
|
|
5548
|
+
if (this.stage !== 1 /* Stages.connected */) {
|
|
3922
5549
|
return;
|
|
3923
5550
|
}
|
|
3924
|
-
this.
|
|
5551
|
+
this.stage = 2 /* Stages.disconnecting */;
|
|
5552
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
3925
5553
|
if (this.view !== null) {
|
|
3926
5554
|
this.view.unbind();
|
|
3927
5555
|
}
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
for (const key of behaviors.keys()) {
|
|
3931
|
-
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
3932
|
-
}
|
|
3933
|
-
}
|
|
5556
|
+
this.disconnectBehaviors();
|
|
5557
|
+
this.stage = 3 /* Stages.disconnected */;
|
|
3934
5558
|
}
|
|
3935
5559
|
/**
|
|
3936
5560
|
* Runs the attribute changed callback for the associated element.
|
|
@@ -3953,27 +5577,11 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
3953
5577
|
* Only emits events if connected.
|
|
3954
5578
|
*/
|
|
3955
5579
|
emit(type, detail, options) {
|
|
3956
|
-
if (this.
|
|
5580
|
+
if (this.stage === 1 /* Stages.connected */) {
|
|
3957
5581
|
return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
|
|
3958
5582
|
}
|
|
3959
5583
|
return false;
|
|
3960
5584
|
}
|
|
3961
|
-
finishInitialization() {
|
|
3962
|
-
const element = this.source;
|
|
3963
|
-
const boundObservables = this.boundObservables;
|
|
3964
|
-
// If we have any observables that were bound, re-apply their values.
|
|
3965
|
-
if (boundObservables !== null) {
|
|
3966
|
-
const propertyNames = Object.keys(boundObservables);
|
|
3967
|
-
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
3968
|
-
const propertyName = propertyNames[i];
|
|
3969
|
-
element[propertyName] = boundObservables[propertyName];
|
|
3970
|
-
}
|
|
3971
|
-
this.boundObservables = null;
|
|
3972
|
-
}
|
|
3973
|
-
this.renderTemplate(this.template);
|
|
3974
|
-
this.addStyles(this.mainStyles);
|
|
3975
|
-
this.needsInitialization = false;
|
|
3976
|
-
}
|
|
3977
5585
|
renderTemplate(template) {
|
|
3978
5586
|
var _a;
|
|
3979
5587
|
// When getting the host to render to, we start by looking
|
|
@@ -4017,13 +5625,224 @@ class ElementController extends PropertyChangeNotifier {
|
|
|
4017
5625
|
if (definition === void 0) {
|
|
4018
5626
|
throw FAST.error(1401 /* Message.missingElementDefinition */);
|
|
4019
5627
|
}
|
|
4020
|
-
return (element.$fastController = new
|
|
5628
|
+
return (element.$fastController = new elementControllerStrategy(element, definition));
|
|
5629
|
+
}
|
|
5630
|
+
/**
|
|
5631
|
+
* Sets the strategy that ElementController.forCustomElement uses to construct
|
|
5632
|
+
* ElementController instances for an element.
|
|
5633
|
+
* @param strategy - The strategy to use.
|
|
5634
|
+
*/
|
|
5635
|
+
static setStrategy(strategy) {
|
|
5636
|
+
elementControllerStrategy = strategy;
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
makeSerializationNoop(ElementController);
|
|
5640
|
+
// Set default strategy for ElementController
|
|
5641
|
+
ElementController.setStrategy(ElementController);
|
|
5642
|
+
/**
|
|
5643
|
+
* Converts a styleTarget into the operative target. When the provided target is an Element
|
|
5644
|
+
* that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
|
|
5645
|
+
* it will return the root node for the element.
|
|
5646
|
+
* @param target
|
|
5647
|
+
* @returns
|
|
5648
|
+
*/
|
|
5649
|
+
function normalizeStyleTarget(target) {
|
|
5650
|
+
var _a;
|
|
5651
|
+
if ("adoptedStyleSheets" in target) {
|
|
5652
|
+
return target;
|
|
5653
|
+
}
|
|
5654
|
+
else {
|
|
5655
|
+
return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5658
|
+
// Default StyleStrategy implementations are defined in this module because they
|
|
5659
|
+
// require access to element shadowRoots, and we don't want to leak shadowRoot
|
|
5660
|
+
// objects out of this module.
|
|
5661
|
+
/**
|
|
5662
|
+
* https://wicg.github.io/construct-stylesheets/
|
|
5663
|
+
* https://developers.google.com/web/updates/2019/02/constructable-stylesheets
|
|
5664
|
+
*
|
|
5665
|
+
* @internal
|
|
5666
|
+
*/
|
|
5667
|
+
class AdoptedStyleSheetsStrategy {
|
|
5668
|
+
constructor(styles) {
|
|
5669
|
+
const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
|
|
5670
|
+
this.sheets = styles.map((x) => {
|
|
5671
|
+
if (x instanceof CSSStyleSheet) {
|
|
5672
|
+
return x;
|
|
5673
|
+
}
|
|
5674
|
+
let sheet = styleSheetCache.get(x);
|
|
5675
|
+
if (sheet === void 0) {
|
|
5676
|
+
sheet = new CSSStyleSheet();
|
|
5677
|
+
sheet.replaceSync(x);
|
|
5678
|
+
styleSheetCache.set(x, sheet);
|
|
5679
|
+
}
|
|
5680
|
+
return sheet;
|
|
5681
|
+
});
|
|
5682
|
+
}
|
|
5683
|
+
addStylesTo(target) {
|
|
5684
|
+
addAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
|
|
5685
|
+
}
|
|
5686
|
+
removeStylesFrom(target) {
|
|
5687
|
+
removeAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
|
|
5691
|
+
let id = 0;
|
|
5692
|
+
const nextStyleId = () => `fast-${++id}`;
|
|
5693
|
+
function usableStyleTarget(target) {
|
|
5694
|
+
return target === document ? document.body : target;
|
|
5695
|
+
}
|
|
5696
|
+
/**
|
|
5697
|
+
* @internal
|
|
5698
|
+
*/
|
|
5699
|
+
class StyleElementStrategy {
|
|
5700
|
+
constructor(styles) {
|
|
5701
|
+
this.styles = styles;
|
|
5702
|
+
this.styleClass = nextStyleId();
|
|
5703
|
+
}
|
|
5704
|
+
addStylesTo(target) {
|
|
5705
|
+
target = usableStyleTarget(normalizeStyleTarget(target));
|
|
5706
|
+
const styles = this.styles;
|
|
5707
|
+
const styleClass = this.styleClass;
|
|
5708
|
+
for (let i = 0; i < styles.length; i++) {
|
|
5709
|
+
const element = document.createElement("style");
|
|
5710
|
+
element.innerHTML = styles[i];
|
|
5711
|
+
element.className = styleClass;
|
|
5712
|
+
target.append(element);
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
removeStylesFrom(target) {
|
|
5716
|
+
target = usableStyleTarget(normalizeStyleTarget(target));
|
|
5717
|
+
const styles = target.querySelectorAll(`.${this.styleClass}`);
|
|
5718
|
+
for (let i = 0, ii = styles.length; i < ii; ++i) {
|
|
5719
|
+
target.removeChild(styles[i]);
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
let addAdoptedStyleSheets = (target, sheets) => {
|
|
5724
|
+
target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...sheets];
|
|
5725
|
+
};
|
|
5726
|
+
let removeAdoptedStyleSheets = (target, sheets) => {
|
|
5727
|
+
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
|
|
5728
|
+
};
|
|
5729
|
+
if (ElementStyles.supportsAdoptedStyleSheets) {
|
|
5730
|
+
try {
|
|
5731
|
+
// Test if browser implementation uses FrozenArray.
|
|
5732
|
+
// If not, use push / splice to alter the stylesheets
|
|
5733
|
+
// in place. This circumvents a bug in Safari 16.4 where
|
|
5734
|
+
// periodically, assigning the array would previously
|
|
5735
|
+
// cause sheets to be removed.
|
|
5736
|
+
document.adoptedStyleSheets.push();
|
|
5737
|
+
document.adoptedStyleSheets.splice();
|
|
5738
|
+
addAdoptedStyleSheets = (target, sheets) => {
|
|
5739
|
+
target.adoptedStyleSheets.push(...sheets);
|
|
5740
|
+
};
|
|
5741
|
+
removeAdoptedStyleSheets = (target, sheets) => {
|
|
5742
|
+
for (const sheet of sheets) {
|
|
5743
|
+
const index = target.adoptedStyleSheets.indexOf(sheet);
|
|
5744
|
+
if (index !== -1) {
|
|
5745
|
+
target.adoptedStyleSheets.splice(index, 1);
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
};
|
|
5749
|
+
}
|
|
5750
|
+
catch (e) {
|
|
5751
|
+
// Do nothing if an error is thrown, the default
|
|
5752
|
+
// case handles FrozenArray.
|
|
5753
|
+
}
|
|
5754
|
+
ElementStyles.setDefaultStrategy(AdoptedStyleSheetsStrategy);
|
|
5755
|
+
}
|
|
5756
|
+
else {
|
|
5757
|
+
ElementStyles.setDefaultStrategy(StyleElementStrategy);
|
|
5758
|
+
}
|
|
5759
|
+
const deferHydrationAttribute = "defer-hydration";
|
|
5760
|
+
const needsHydrationAttribute = "needs-hydration";
|
|
5761
|
+
/**
|
|
5762
|
+
* An ElementController capable of hydrating FAST elements from
|
|
5763
|
+
* Declarative Shadow DOM.
|
|
5764
|
+
*
|
|
5765
|
+
* @beta
|
|
5766
|
+
*/
|
|
5767
|
+
class HydratableElementController extends ElementController {
|
|
5768
|
+
static hydrationObserverHandler(records) {
|
|
5769
|
+
for (const record of records) {
|
|
5770
|
+
HydratableElementController.hydrationObserver.unobserve(record.target);
|
|
5771
|
+
record.target.$fastController.connect();
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
connect() {
|
|
5775
|
+
var _a, _b;
|
|
5776
|
+
// Initialize needsHydration on first connect
|
|
5777
|
+
if (this.needsHydration === undefined) {
|
|
5778
|
+
this.needsHydration =
|
|
5779
|
+
this.source.getAttribute(needsHydrationAttribute) !== null;
|
|
5780
|
+
}
|
|
5781
|
+
// If the `defer-hydration` attribute exists on the source,
|
|
5782
|
+
// wait for it to be removed before continuing connection behavior.
|
|
5783
|
+
if (this.source.hasAttribute(deferHydrationAttribute)) {
|
|
5784
|
+
HydratableElementController.hydrationObserver.observe(this.source, {
|
|
5785
|
+
attributeFilter: [deferHydrationAttribute],
|
|
5786
|
+
});
|
|
5787
|
+
return;
|
|
5788
|
+
}
|
|
5789
|
+
// If the controller does not need to be hydrated, defer connection behavior
|
|
5790
|
+
// to the base-class. This case handles element re-connection and initial connection
|
|
5791
|
+
// of elements that did not get declarative shadow-dom emitted, as well as if an extending
|
|
5792
|
+
// class
|
|
5793
|
+
if (!this.needsHydration) {
|
|
5794
|
+
super.connect();
|
|
5795
|
+
return;
|
|
5796
|
+
}
|
|
5797
|
+
if (this.stage !== 3 /* Stages.disconnected */) {
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
this.stage = 0 /* Stages.connecting */;
|
|
5801
|
+
this.bindObservables();
|
|
5802
|
+
this.connectBehaviors();
|
|
5803
|
+
const element = this.source;
|
|
5804
|
+
const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
|
|
5805
|
+
if (this.template) {
|
|
5806
|
+
if (isHydratable(this.template)) {
|
|
5807
|
+
let firstChild = host.firstChild;
|
|
5808
|
+
let lastChild = host.lastChild;
|
|
5809
|
+
if (element.shadowRoot === null) {
|
|
5810
|
+
// handle element boundary markers when shadowRoot is not present
|
|
5811
|
+
if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
|
|
5812
|
+
firstChild.data = "";
|
|
5813
|
+
firstChild = firstChild.nextSibling;
|
|
5814
|
+
}
|
|
5815
|
+
if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
|
|
5816
|
+
lastChild.data = "";
|
|
5817
|
+
lastChild = lastChild.previousSibling;
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
this.view = this.template.hydrate(firstChild, lastChild, element);
|
|
5821
|
+
(_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
|
|
5822
|
+
}
|
|
5823
|
+
else {
|
|
5824
|
+
this.renderTemplate(this.template);
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
this.addStyles(this.mainStyles);
|
|
5828
|
+
this.stage = 1 /* Stages.connected */;
|
|
5829
|
+
this.source.removeAttribute(needsHydrationAttribute);
|
|
5830
|
+
this.needsInitialization = this.needsHydration = false;
|
|
5831
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
5832
|
+
}
|
|
5833
|
+
disconnect() {
|
|
5834
|
+
super.disconnect();
|
|
5835
|
+
HydratableElementController.hydrationObserver.unobserve(this.source);
|
|
5836
|
+
}
|
|
5837
|
+
static install() {
|
|
5838
|
+
ElementController.setStrategy(HydratableElementController);
|
|
4021
5839
|
}
|
|
4022
5840
|
}
|
|
5841
|
+
HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
|
|
4023
5842
|
|
|
4024
5843
|
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
4025
5844
|
function createFASTElement(BaseType) {
|
|
4026
|
-
|
|
5845
|
+
const type = class extends BaseType {
|
|
4027
5846
|
constructor() {
|
|
4028
5847
|
/* eslint-disable-next-line */
|
|
4029
5848
|
super();
|
|
@@ -4042,6 +5861,8 @@ function createFASTElement(BaseType) {
|
|
|
4042
5861
|
this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
|
|
4043
5862
|
}
|
|
4044
5863
|
};
|
|
5864
|
+
FASTElementDefinition.registerBaseType(type);
|
|
5865
|
+
return type;
|
|
4045
5866
|
}
|
|
4046
5867
|
function compose(type, nameOrDef) {
|
|
4047
5868
|
if (isFunction(type)) {
|
|
@@ -4096,4 +5917,6 @@ function customElement(nameOrDef) {
|
|
|
4096
5917
|
};
|
|
4097
5918
|
}
|
|
4098
5919
|
|
|
4099
|
-
|
|
5920
|
+
DOM.setPolicy(DOMPolicy.create());
|
|
5921
|
+
|
|
5922
|
+
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 };
|