@microsoft/fast-element 1.10.5 → 2.0.0-beta.10
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 -12
- package/CHANGELOG.json +629 -6
- package/CHANGELOG.md +152 -5
- package/dist/dts/components/attributes.d.ts +14 -1
- package/dist/dts/components/{controller.d.ts → element-controller.d.ts} +32 -32
- package/dist/dts/components/fast-definitions.d.ts +51 -11
- package/dist/dts/components/fast-element.d.ts +18 -23
- package/dist/dts/context.d.ts +157 -0
- package/dist/{esm/observation/behavior.js → dts/debug.d.ts} +0 -0
- package/dist/dts/di/di.d.ts +899 -0
- package/dist/dts/index.d.ts +17 -16
- package/dist/dts/index.debug.d.ts +2 -0
- package/dist/dts/index.rollup.d.ts +2 -0
- package/dist/dts/index.rollup.debug.d.ts +3 -0
- package/dist/dts/interfaces.d.ts +176 -0
- package/dist/dts/metadata.d.ts +25 -0
- package/dist/dts/observation/arrays.d.ts +207 -0
- package/dist/dts/observation/notifier.d.ts +18 -18
- package/dist/dts/observation/observable.d.ts +117 -34
- package/dist/dts/observation/update-queue.d.ts +40 -0
- package/dist/dts/pending-task.d.ts +20 -0
- package/dist/dts/platform.d.ts +23 -66
- package/dist/dts/polyfills.d.ts +8 -0
- package/dist/dts/state/exports.d.ts +3 -0
- package/dist/dts/state/reactive.d.ts +8 -0
- package/dist/dts/state/state.d.ts +141 -0
- package/dist/dts/state/visitor.d.ts +6 -0
- package/dist/dts/state/watch.d.ts +10 -0
- package/dist/dts/styles/css-directive.d.ts +44 -6
- package/dist/dts/styles/css.d.ts +19 -3
- package/dist/dts/styles/element-styles.d.ts +49 -63
- package/dist/dts/styles/host.d.ts +68 -0
- package/dist/dts/templating/binding-signal.d.ts +21 -0
- package/dist/dts/templating/binding-two-way.d.ts +39 -0
- package/dist/dts/templating/binding.d.ts +101 -70
- package/dist/dts/templating/children.d.ts +18 -15
- package/dist/dts/templating/compiler.d.ts +46 -28
- package/dist/dts/templating/dom.d.ts +41 -0
- package/dist/dts/templating/html-directive.d.ts +239 -45
- package/dist/dts/templating/markup.d.ts +48 -0
- package/dist/dts/templating/node-observation.d.ts +45 -30
- package/dist/dts/templating/ref.d.ts +6 -20
- package/dist/dts/templating/render.d.ts +272 -0
- package/dist/dts/templating/repeat.d.ts +36 -33
- package/dist/dts/templating/slotted.d.ts +13 -14
- package/dist/dts/templating/template.d.ts +28 -22
- package/dist/dts/templating/view.d.ts +82 -24
- package/dist/dts/templating/when.d.ts +3 -3
- package/dist/dts/testing/exports.d.ts +3 -0
- package/dist/dts/testing/fakes.d.ts +4 -0
- package/dist/dts/testing/fixture.d.ts +84 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/{tsdoc-metadata.json → dts/tsdoc-metadata.json} +1 -1
- package/dist/dts/utilities.d.ts +22 -0
- package/dist/esm/components/attributes.js +38 -28
- package/dist/esm/components/{controller.js → element-controller.js} +150 -140
- package/dist/esm/components/fast-definitions.js +48 -46
- package/dist/esm/components/fast-element.js +31 -12
- package/dist/esm/context.js +163 -0
- package/dist/esm/debug.js +61 -0
- package/dist/esm/di/di.js +1435 -0
- package/dist/esm/index.debug.js +2 -0
- package/dist/esm/index.js +20 -14
- package/dist/esm/index.rollup.debug.js +3 -0
- package/dist/esm/index.rollup.js +2 -0
- package/dist/esm/interfaces.js +12 -1
- package/dist/esm/metadata.js +60 -0
- package/dist/esm/observation/arrays.js +570 -0
- package/dist/esm/observation/notifier.js +27 -35
- package/dist/esm/observation/observable.js +116 -149
- package/dist/esm/observation/update-queue.js +67 -0
- package/dist/esm/pending-task.js +16 -0
- package/dist/esm/platform.js +60 -42
- package/dist/esm/polyfills.js +85 -0
- package/dist/esm/state/exports.js +3 -0
- package/dist/esm/state/reactive.js +34 -0
- package/dist/esm/state/state.js +148 -0
- package/dist/esm/state/visitor.js +28 -0
- package/dist/esm/state/watch.js +36 -0
- package/dist/esm/styles/css-directive.js +29 -13
- package/dist/esm/styles/css.js +29 -42
- package/dist/esm/styles/element-styles.js +79 -104
- package/dist/esm/styles/host.js +1 -0
- package/dist/esm/templating/binding-signal.js +83 -0
- package/dist/esm/templating/binding-two-way.js +103 -0
- package/dist/esm/templating/binding.js +189 -159
- package/dist/esm/templating/children.js +33 -23
- package/dist/esm/templating/compiler.js +258 -152
- package/dist/esm/templating/dom.js +49 -0
- package/dist/esm/templating/html-directive.js +193 -36
- package/dist/esm/templating/markup.js +75 -0
- package/dist/esm/templating/node-observation.js +51 -45
- package/dist/esm/templating/ref.js +8 -25
- package/dist/esm/templating/render.js +391 -0
- package/dist/esm/templating/repeat.js +83 -79
- package/dist/esm/templating/slotted.js +23 -20
- package/dist/esm/templating/template.js +51 -93
- package/dist/esm/templating/view.js +125 -46
- package/dist/esm/templating/when.js +6 -4
- package/dist/esm/testing/exports.js +3 -0
- package/dist/esm/testing/fakes.js +76 -0
- package/dist/esm/testing/fixture.js +86 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/utilities.js +44 -0
- package/dist/fast-element.api.json +12153 -5373
- package/dist/fast-element.d.ts +1448 -696
- package/dist/fast-element.debug.js +4107 -0
- package/dist/fast-element.debug.min.js +1 -0
- package/dist/fast-element.js +3817 -4029
- package/dist/fast-element.min.js +1 -1
- package/dist/fast-element.untrimmed.d.ts +2814 -0
- package/docs/api-report.md +567 -254
- package/docs/fast-element-2-changes.md +15 -0
- package/karma.conf.cjs +6 -17
- package/package.json +76 -15
- package/dist/dts/dom.d.ts +0 -112
- package/dist/dts/observation/array-change-records.d.ts +0 -48
- package/dist/dts/observation/array-observer.d.ts +0 -9
- package/dist/dts/observation/behavior.d.ts +0 -19
- package/dist/esm/dom.js +0 -207
- package/dist/esm/observation/array-change-records.js +0 -326
- package/dist/esm/observation/array-observer.js +0 -177
|
@@ -0,0 +1,4107 @@
|
|
|
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
|
+
if (globalThis.FAST === void 0) {
|
|
88
|
+
Reflect.defineProperty(globalThis, "FAST", {
|
|
89
|
+
value: Object.create(null),
|
|
90
|
+
configurable: false,
|
|
91
|
+
enumerable: false,
|
|
92
|
+
writable: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const FAST$1 = globalThis.FAST;
|
|
96
|
+
const debugMessages = {
|
|
97
|
+
[1101 /* needsArrayObservation */]: "Must call enableArrayObservation before observing arrays.",
|
|
98
|
+
[1201 /* onlySetHTMLPolicyOnce */]: "The HTML policy can only be set once.",
|
|
99
|
+
[1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
|
|
100
|
+
[1203 /* twoWayBindingRequiresObservables */]: "View=>Model update skipped. To use twoWay binding, the target property must be observable.",
|
|
101
|
+
[1204 /* hostBindingWithoutHost */]: "No host element is present. Cannot bind host with ${name}.",
|
|
102
|
+
[1205 /* unsupportedBindingBehavior */]: "The requested binding behavior is not supported by the binding engine.",
|
|
103
|
+
[1401 /* missingElementDefinition */]: "Missing FASTElement definition.",
|
|
104
|
+
[1501 /* noRegistrationForContext */]: "No registration for Context/Interface '${name}'.",
|
|
105
|
+
[1502 /* noFactoryForResolver */]: "Dependency injection resolver for '${key}' returned a null factory.",
|
|
106
|
+
[1503 /* invalidResolverStrategy */]: "Invalid dependency injection resolver strategy specified '${strategy}'.",
|
|
107
|
+
[1504 /* cannotAutoregisterDependency */]: "Unable to autoregister dependency.",
|
|
108
|
+
[1505 /* cannotResolveKey */]: "Unable to resolve dependency injection key '${key}'.",
|
|
109
|
+
[1506 /* cannotConstructNativeFunction */]: "'${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
|
+
[1507 /* cannotJITRegisterNonConstructor */]: "Attempted to jitRegister something that is not a constructor '${value}'. Did you forget to register this dependency?",
|
|
111
|
+
[1508 /* cannotJITRegisterIntrinsic */]: "Attempted to jitRegister an intrinsic type '${value}'. Did you forget to add @inject(Key)?",
|
|
112
|
+
[1509 /* cannotJITRegisterInterface */]: "Attempted to jitRegister an interface '${value}'.",
|
|
113
|
+
[1510 /* invalidResolver */]: "A valid resolver was not returned from the register method.",
|
|
114
|
+
[1511 /* invalidKey */]: "Key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?",
|
|
115
|
+
[1512 /* noDefaultResolver */]: "'${key}' not registered. Did you forget to add @singleton()?",
|
|
116
|
+
[1513 /* cyclicDependency */]: "Cyclic dependency found '${name}'.",
|
|
117
|
+
[1514 /* connectUpdateRequiresController */]: "Injected properties that are updated on changes to DOM connectivity require the target object to be an instance of FASTElement.",
|
|
118
|
+
};
|
|
119
|
+
const allPlaceholders = /(\$\{\w+?})/g;
|
|
120
|
+
const placeholder = /\$\{(\w+?)}/g;
|
|
121
|
+
const noValues = Object.freeze({});
|
|
122
|
+
function formatMessage(message, values) {
|
|
123
|
+
return message
|
|
124
|
+
.split(allPlaceholders)
|
|
125
|
+
.map(v => {
|
|
126
|
+
var _a;
|
|
127
|
+
const replaced = v.replace(placeholder, "$1");
|
|
128
|
+
return String((_a = values[replaced]) !== null && _a !== void 0 ? _a : v);
|
|
129
|
+
})
|
|
130
|
+
.join("");
|
|
131
|
+
}
|
|
132
|
+
Object.assign(FAST$1, {
|
|
133
|
+
addMessages(messages) {
|
|
134
|
+
Object.assign(debugMessages, messages);
|
|
135
|
+
},
|
|
136
|
+
warn(code, values = noValues) {
|
|
137
|
+
var _a;
|
|
138
|
+
const message = (_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Warning";
|
|
139
|
+
console.warn(formatMessage(message, values));
|
|
140
|
+
},
|
|
141
|
+
error(code, values = noValues) {
|
|
142
|
+
var _a;
|
|
143
|
+
const message = (_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Error";
|
|
144
|
+
return new Error(formatMessage(message, values));
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ensure FAST global - duplicated in polyfills.ts and debug.ts
|
|
149
|
+
const propConfig = {
|
|
150
|
+
configurable: false,
|
|
151
|
+
enumerable: false,
|
|
152
|
+
writable: false,
|
|
153
|
+
};
|
|
154
|
+
if (globalThis.FAST === void 0) {
|
|
155
|
+
Reflect.defineProperty(globalThis, "FAST", Object.assign({ value: Object.create(null) }, propConfig));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The FAST global.
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
const FAST = globalThis.FAST;
|
|
162
|
+
if (FAST.getById === void 0) {
|
|
163
|
+
const storage = Object.create(null);
|
|
164
|
+
Reflect.defineProperty(FAST, "getById", Object.assign({ value(id, initialize) {
|
|
165
|
+
let found = storage[id];
|
|
166
|
+
if (found === void 0) {
|
|
167
|
+
found = initialize ? (storage[id] = initialize()) : null;
|
|
168
|
+
}
|
|
169
|
+
return found;
|
|
170
|
+
} }, propConfig));
|
|
171
|
+
}
|
|
172
|
+
if (FAST.error === void 0) {
|
|
173
|
+
Object.assign(FAST, {
|
|
174
|
+
warn() { },
|
|
175
|
+
error(code) {
|
|
176
|
+
return new Error(`Error ${code}`);
|
|
177
|
+
},
|
|
178
|
+
addMessages() { },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A readonly, empty array.
|
|
183
|
+
* @remarks
|
|
184
|
+
* Typically returned by APIs that return arrays when there are
|
|
185
|
+
* no actual items to return.
|
|
186
|
+
* @public
|
|
187
|
+
*/
|
|
188
|
+
const emptyArray = Object.freeze([]);
|
|
189
|
+
/**
|
|
190
|
+
* Do not change. Part of shared kernel contract.
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function createTypeRegistry() {
|
|
194
|
+
const typeToDefinition = new Map();
|
|
195
|
+
return Object.freeze({
|
|
196
|
+
register(definition) {
|
|
197
|
+
if (typeToDefinition.has(definition.type)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
typeToDefinition.set(definition.type, definition);
|
|
201
|
+
return true;
|
|
202
|
+
},
|
|
203
|
+
getByType(key) {
|
|
204
|
+
return typeToDefinition.get(key);
|
|
205
|
+
},
|
|
206
|
+
getForInstance(object) {
|
|
207
|
+
if (object === null || object === void 0) {
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
return typeToDefinition.get(object.constructor);
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Creates a function capable of locating metadata associated with a type.
|
|
216
|
+
* @returns A metadata locator function.
|
|
217
|
+
* @internal
|
|
218
|
+
*/
|
|
219
|
+
function createMetadataLocator() {
|
|
220
|
+
const metadataLookup = new WeakMap();
|
|
221
|
+
return function (target) {
|
|
222
|
+
let metadata = metadataLookup.get(target);
|
|
223
|
+
if (metadata === void 0) {
|
|
224
|
+
let currentTarget = Reflect.getPrototypeOf(target);
|
|
225
|
+
while (metadata === void 0 && currentTarget !== null) {
|
|
226
|
+
metadata = metadataLookup.get(currentTarget);
|
|
227
|
+
currentTarget = Reflect.getPrototypeOf(currentTarget);
|
|
228
|
+
}
|
|
229
|
+
metadata = metadata === void 0 ? [] : metadata.slice(0);
|
|
230
|
+
metadataLookup.set(target, metadata);
|
|
231
|
+
}
|
|
232
|
+
return metadata;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
239
|
+
const isFunction = (object) => typeof object === "function";
|
|
240
|
+
/**
|
|
241
|
+
* @internal
|
|
242
|
+
*/
|
|
243
|
+
const isString = (object) => typeof object === "string";
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The default UpdateQueue.
|
|
247
|
+
* @public
|
|
248
|
+
*/
|
|
249
|
+
const Updates = FAST.getById(1 /* KernelServiceId.updateQueue */, () => {
|
|
250
|
+
const tasks = [];
|
|
251
|
+
const pendingErrors = [];
|
|
252
|
+
const rAF = globalThis.requestAnimationFrame;
|
|
253
|
+
let updateAsync = true;
|
|
254
|
+
function throwFirstError() {
|
|
255
|
+
if (pendingErrors.length) {
|
|
256
|
+
throw pendingErrors.shift();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function tryRunTask(task) {
|
|
260
|
+
try {
|
|
261
|
+
task.call();
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
if (updateAsync) {
|
|
265
|
+
pendingErrors.push(error);
|
|
266
|
+
setTimeout(throwFirstError, 0);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
tasks.length = 0;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function process() {
|
|
275
|
+
const capacity = 1024;
|
|
276
|
+
let index = 0;
|
|
277
|
+
while (index < tasks.length) {
|
|
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
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* An implementation of {@link Notifier} that efficiently keeps track of
|
|
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.
|
|
320
|
+
* @public
|
|
321
|
+
*/
|
|
322
|
+
class SubscriberSet {
|
|
323
|
+
/**
|
|
324
|
+
* Creates an instance of SubscriberSet for the specified subject.
|
|
325
|
+
* @param subject - The subject that subscribers will receive notifications from.
|
|
326
|
+
* @param initialSubscriber - An initial subscriber to changes.
|
|
327
|
+
*/
|
|
328
|
+
constructor(subject, initialSubscriber) {
|
|
329
|
+
this.sub1 = void 0;
|
|
330
|
+
this.sub2 = void 0;
|
|
331
|
+
this.spillover = void 0;
|
|
332
|
+
this.subject = subject;
|
|
333
|
+
this.sub1 = initialSubscriber;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Checks whether the provided subscriber has been added to this set.
|
|
337
|
+
* @param subscriber - The subscriber to test for inclusion in this set.
|
|
338
|
+
*/
|
|
339
|
+
has(subscriber) {
|
|
340
|
+
return this.spillover === void 0
|
|
341
|
+
? this.sub1 === subscriber || this.sub2 === subscriber
|
|
342
|
+
: this.spillover.indexOf(subscriber) !== -1;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Subscribes to notification of changes in an object's state.
|
|
346
|
+
* @param subscriber - The object that is subscribing for change notification.
|
|
347
|
+
*/
|
|
348
|
+
subscribe(subscriber) {
|
|
349
|
+
const spillover = this.spillover;
|
|
350
|
+
if (spillover === void 0) {
|
|
351
|
+
if (this.has(subscriber)) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (this.sub1 === void 0) {
|
|
355
|
+
this.sub1 = subscriber;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (this.sub2 === void 0) {
|
|
359
|
+
this.sub2 = subscriber;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.spillover = [this.sub1, this.sub2, subscriber];
|
|
363
|
+
this.sub1 = void 0;
|
|
364
|
+
this.sub2 = void 0;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
const index = spillover.indexOf(subscriber);
|
|
368
|
+
if (index === -1) {
|
|
369
|
+
spillover.push(subscriber);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Unsubscribes from notification of changes in an object's state.
|
|
375
|
+
* @param subscriber - The object that is unsubscribing from change notification.
|
|
376
|
+
*/
|
|
377
|
+
unsubscribe(subscriber) {
|
|
378
|
+
const spillover = this.spillover;
|
|
379
|
+
if (spillover === void 0) {
|
|
380
|
+
if (this.sub1 === subscriber) {
|
|
381
|
+
this.sub1 = void 0;
|
|
382
|
+
}
|
|
383
|
+
else if (this.sub2 === subscriber) {
|
|
384
|
+
this.sub2 = void 0;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
const index = spillover.indexOf(subscriber);
|
|
389
|
+
if (index !== -1) {
|
|
390
|
+
spillover.splice(index, 1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Notifies all subscribers.
|
|
396
|
+
* @param args - Data passed along to subscribers during notification.
|
|
397
|
+
*/
|
|
398
|
+
notify(args) {
|
|
399
|
+
const spillover = this.spillover;
|
|
400
|
+
const subject = this.subject;
|
|
401
|
+
if (spillover === void 0) {
|
|
402
|
+
const sub1 = this.sub1;
|
|
403
|
+
const sub2 = this.sub2;
|
|
404
|
+
if (sub1 !== void 0) {
|
|
405
|
+
sub1.handleChange(subject, args);
|
|
406
|
+
}
|
|
407
|
+
if (sub2 !== void 0) {
|
|
408
|
+
sub2.handleChange(subject, args);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
for (let i = 0, ii = spillover.length; i < ii; ++i) {
|
|
413
|
+
spillover[i].handleChange(subject, args);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* An implementation of Notifier that allows subscribers to be notified
|
|
420
|
+
* of individual property changes on an object.
|
|
421
|
+
* @public
|
|
422
|
+
*/
|
|
423
|
+
class PropertyChangeNotifier {
|
|
424
|
+
/**
|
|
425
|
+
* Creates an instance of PropertyChangeNotifier for the specified subject.
|
|
426
|
+
* @param subject - The object that subscribers will receive notifications for.
|
|
427
|
+
*/
|
|
428
|
+
constructor(subject) {
|
|
429
|
+
this.subscribers = {};
|
|
430
|
+
this.subjectSubscribers = null;
|
|
431
|
+
this.subject = subject;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Notifies all subscribers, based on the specified property.
|
|
435
|
+
* @param propertyName - The property name, passed along to subscribers during notification.
|
|
436
|
+
*/
|
|
437
|
+
notify(propertyName) {
|
|
438
|
+
var _a, _b;
|
|
439
|
+
(_a = this.subscribers[propertyName]) === null || _a === void 0 ? void 0 : _a.notify(propertyName);
|
|
440
|
+
(_b = this.subjectSubscribers) === null || _b === void 0 ? void 0 : _b.notify(propertyName);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Subscribes to notification of changes in an object's state.
|
|
444
|
+
* @param subscriber - The object that is subscribing for change notification.
|
|
445
|
+
* @param propertyToWatch - The name of the property that the subscriber is interested in watching for changes.
|
|
446
|
+
*/
|
|
447
|
+
subscribe(subscriber, propertyToWatch) {
|
|
448
|
+
var _a, _b;
|
|
449
|
+
let subscribers;
|
|
450
|
+
if (propertyToWatch) {
|
|
451
|
+
subscribers =
|
|
452
|
+
(_a = this.subscribers[propertyToWatch]) !== null && _a !== void 0 ? _a : (this.subscribers[propertyToWatch] = new SubscriberSet(this.subject));
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
subscribers =
|
|
456
|
+
(_b = this.subjectSubscribers) !== null && _b !== void 0 ? _b : (this.subjectSubscribers = new SubscriberSet(this.subject));
|
|
457
|
+
}
|
|
458
|
+
subscribers.subscribe(subscriber);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Unsubscribes from notification of changes in an object's state.
|
|
462
|
+
* @param subscriber - The object that is unsubscribing from change notification.
|
|
463
|
+
* @param propertyToUnwatch - The name of the property that the subscriber is no longer interested in watching.
|
|
464
|
+
*/
|
|
465
|
+
unsubscribe(subscriber, propertyToUnwatch) {
|
|
466
|
+
var _a, _b;
|
|
467
|
+
if (propertyToUnwatch) {
|
|
468
|
+
(_a = this.subscribers[propertyToUnwatch]) === null || _a === void 0 ? void 0 : _a.unsubscribe(subscriber);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
(_b = this.subjectSubscribers) === null || _b === void 0 ? void 0 : _b.unsubscribe(subscriber);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Describes how the source's lifetime relates to its controller's lifetime.
|
|
478
|
+
* @public
|
|
479
|
+
*/
|
|
480
|
+
const SourceLifetime = Object.freeze({
|
|
481
|
+
/**
|
|
482
|
+
* The source to controller lifetime relationship is unknown.
|
|
483
|
+
*/
|
|
484
|
+
unknown: void 0,
|
|
485
|
+
/**
|
|
486
|
+
* The source and controller lifetimes are coupled to one another.
|
|
487
|
+
* They can/will be GC'd together.
|
|
488
|
+
*/
|
|
489
|
+
coupled: 1,
|
|
490
|
+
});
|
|
491
|
+
/**
|
|
492
|
+
* Common Observable APIs.
|
|
493
|
+
* @public
|
|
494
|
+
*/
|
|
495
|
+
const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
|
|
496
|
+
const queueUpdate = Updates.enqueue;
|
|
497
|
+
const volatileRegex = /(:|&&|\|\||if)/;
|
|
498
|
+
const notifierLookup = new WeakMap();
|
|
499
|
+
let watcher = void 0;
|
|
500
|
+
let createArrayObserver = (array) => {
|
|
501
|
+
throw FAST.error(1101 /* Message.needsArrayObservation */);
|
|
502
|
+
};
|
|
503
|
+
function getNotifier(source) {
|
|
504
|
+
var _a;
|
|
505
|
+
let found = (_a = source.$fastController) !== null && _a !== void 0 ? _a : notifierLookup.get(source);
|
|
506
|
+
if (found === void 0) {
|
|
507
|
+
Array.isArray(source)
|
|
508
|
+
? (found = createArrayObserver(source))
|
|
509
|
+
: notifierLookup.set(source, (found = new PropertyChangeNotifier(source)));
|
|
510
|
+
}
|
|
511
|
+
return found;
|
|
512
|
+
}
|
|
513
|
+
const getAccessors = createMetadataLocator();
|
|
514
|
+
class DefaultObservableAccessor {
|
|
515
|
+
constructor(name) {
|
|
516
|
+
this.name = name;
|
|
517
|
+
this.field = `_${name}`;
|
|
518
|
+
this.callback = `${name}Changed`;
|
|
519
|
+
}
|
|
520
|
+
getValue(source) {
|
|
521
|
+
if (watcher !== void 0) {
|
|
522
|
+
watcher.watch(source, this.name);
|
|
523
|
+
}
|
|
524
|
+
return source[this.field];
|
|
525
|
+
}
|
|
526
|
+
setValue(source, newValue) {
|
|
527
|
+
const field = this.field;
|
|
528
|
+
const oldValue = source[field];
|
|
529
|
+
if (oldValue !== newValue) {
|
|
530
|
+
source[field] = newValue;
|
|
531
|
+
const callback = source[this.callback];
|
|
532
|
+
if (isFunction(callback)) {
|
|
533
|
+
callback.call(source, oldValue, newValue);
|
|
534
|
+
}
|
|
535
|
+
getNotifier(source).notify(this.name);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
class ExpressionNotifierImplementation extends SubscriberSet {
|
|
540
|
+
constructor(expression, initialSubscriber, isVolatileBinding = false) {
|
|
541
|
+
super(expression, initialSubscriber);
|
|
542
|
+
this.expression = expression;
|
|
543
|
+
this.isVolatileBinding = isVolatileBinding;
|
|
544
|
+
this.needsRefresh = true;
|
|
545
|
+
this.needsQueue = true;
|
|
546
|
+
this.isAsync = true;
|
|
547
|
+
this.first = this;
|
|
548
|
+
this.last = null;
|
|
549
|
+
this.propertySource = void 0;
|
|
550
|
+
this.propertyName = void 0;
|
|
551
|
+
this.notifier = void 0;
|
|
552
|
+
this.next = void 0;
|
|
553
|
+
}
|
|
554
|
+
setMode(isAsync) {
|
|
555
|
+
this.isAsync = this.needsQueue = isAsync;
|
|
556
|
+
}
|
|
557
|
+
bind(controller) {
|
|
558
|
+
this.controller = controller;
|
|
559
|
+
const value = this.observe(controller.source, controller.context);
|
|
560
|
+
if (!controller.isBound && this.requiresUnbind(controller)) {
|
|
561
|
+
controller.onUnbind(this);
|
|
562
|
+
}
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
requiresUnbind(controller) {
|
|
566
|
+
return (controller.sourceLifetime !== SourceLifetime.coupled ||
|
|
567
|
+
this.first !== this.last ||
|
|
568
|
+
this.first.propertySource !== controller.source);
|
|
569
|
+
}
|
|
570
|
+
unbind(controller) {
|
|
571
|
+
this.dispose();
|
|
572
|
+
}
|
|
573
|
+
observe(source, context) {
|
|
574
|
+
if (this.needsRefresh && this.last !== null) {
|
|
575
|
+
this.dispose();
|
|
576
|
+
}
|
|
577
|
+
const previousWatcher = watcher;
|
|
578
|
+
watcher = this.needsRefresh ? this : void 0;
|
|
579
|
+
this.needsRefresh = this.isVolatileBinding;
|
|
580
|
+
let result;
|
|
581
|
+
try {
|
|
582
|
+
result = this.expression(source, context);
|
|
583
|
+
}
|
|
584
|
+
finally {
|
|
585
|
+
watcher = previousWatcher;
|
|
586
|
+
}
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
// backwards compat with v1 kernel
|
|
590
|
+
disconnect() {
|
|
591
|
+
this.dispose();
|
|
592
|
+
}
|
|
593
|
+
dispose() {
|
|
594
|
+
if (this.last !== null) {
|
|
595
|
+
let current = this.first;
|
|
596
|
+
while (current !== void 0) {
|
|
597
|
+
current.notifier.unsubscribe(this, current.propertyName);
|
|
598
|
+
current = current.next;
|
|
599
|
+
}
|
|
600
|
+
this.last = null;
|
|
601
|
+
this.needsRefresh = this.needsQueue = this.isAsync;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
watch(propertySource, propertyName) {
|
|
605
|
+
const prev = this.last;
|
|
606
|
+
const notifier = getNotifier(propertySource);
|
|
607
|
+
const current = prev === null ? this.first : {};
|
|
608
|
+
current.propertySource = propertySource;
|
|
609
|
+
current.propertyName = propertyName;
|
|
610
|
+
current.notifier = notifier;
|
|
611
|
+
notifier.subscribe(this, propertyName);
|
|
612
|
+
if (prev !== null) {
|
|
613
|
+
if (!this.needsRefresh) {
|
|
614
|
+
// Declaring the variable prior to assignment below circumvents
|
|
615
|
+
// a bug in Angular's optimization process causing infinite recursion
|
|
616
|
+
// of this watch() method. Details https://github.com/microsoft/fast/issues/4969
|
|
617
|
+
let prevValue;
|
|
618
|
+
watcher = void 0;
|
|
619
|
+
/* eslint-disable-next-line */
|
|
620
|
+
prevValue = prev.propertySource[prev.propertyName];
|
|
621
|
+
/* eslint-disable-next-line */
|
|
622
|
+
watcher = this;
|
|
623
|
+
if (propertySource === prevValue) {
|
|
624
|
+
this.needsRefresh = true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
prev.next = current;
|
|
628
|
+
}
|
|
629
|
+
this.last = current;
|
|
630
|
+
}
|
|
631
|
+
handleChange() {
|
|
632
|
+
if (this.needsQueue) {
|
|
633
|
+
this.needsQueue = false;
|
|
634
|
+
queueUpdate(this);
|
|
635
|
+
}
|
|
636
|
+
else if (!this.isAsync) {
|
|
637
|
+
this.call();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
call() {
|
|
641
|
+
if (this.last !== null) {
|
|
642
|
+
this.needsQueue = this.isAsync;
|
|
643
|
+
this.notify(this);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
*records() {
|
|
647
|
+
let next = this.first;
|
|
648
|
+
while (next !== void 0) {
|
|
649
|
+
yield next;
|
|
650
|
+
next = next.next;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return Object.freeze({
|
|
655
|
+
/**
|
|
656
|
+
* @internal
|
|
657
|
+
* @param factory - The factory used to create array observers.
|
|
658
|
+
*/
|
|
659
|
+
setArrayObserverFactory(factory) {
|
|
660
|
+
createArrayObserver = factory;
|
|
661
|
+
},
|
|
662
|
+
/**
|
|
663
|
+
* Gets a notifier for an object or Array.
|
|
664
|
+
* @param source - The object or Array to get the notifier for.
|
|
665
|
+
*/
|
|
666
|
+
getNotifier,
|
|
667
|
+
/**
|
|
668
|
+
* Records a property change for a source object.
|
|
669
|
+
* @param source - The object to record the change against.
|
|
670
|
+
* @param propertyName - The property to track as changed.
|
|
671
|
+
*/
|
|
672
|
+
track(source, propertyName) {
|
|
673
|
+
watcher && watcher.watch(source, propertyName);
|
|
674
|
+
},
|
|
675
|
+
/**
|
|
676
|
+
* Notifies watchers that the currently executing property getter or function is volatile
|
|
677
|
+
* with respect to its observable dependencies.
|
|
678
|
+
*/
|
|
679
|
+
trackVolatile() {
|
|
680
|
+
watcher && (watcher.needsRefresh = true);
|
|
681
|
+
},
|
|
682
|
+
/**
|
|
683
|
+
* Notifies subscribers of a source object of changes.
|
|
684
|
+
* @param source - the object to notify of changes.
|
|
685
|
+
* @param args - The change args to pass to subscribers.
|
|
686
|
+
*/
|
|
687
|
+
notify(source, args) {
|
|
688
|
+
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
689
|
+
getNotifier(source).notify(args);
|
|
690
|
+
},
|
|
691
|
+
/**
|
|
692
|
+
* Defines an observable property on an object or prototype.
|
|
693
|
+
* @param target - The target object to define the observable on.
|
|
694
|
+
* @param nameOrAccessor - The name of the property to define as observable;
|
|
695
|
+
* or a custom accessor that specifies the property name and accessor implementation.
|
|
696
|
+
*/
|
|
697
|
+
defineProperty(target, nameOrAccessor) {
|
|
698
|
+
if (isString(nameOrAccessor)) {
|
|
699
|
+
nameOrAccessor = new DefaultObservableAccessor(nameOrAccessor);
|
|
700
|
+
}
|
|
701
|
+
getAccessors(target).push(nameOrAccessor);
|
|
702
|
+
Reflect.defineProperty(target, nameOrAccessor.name, {
|
|
703
|
+
enumerable: true,
|
|
704
|
+
get() {
|
|
705
|
+
return nameOrAccessor.getValue(this);
|
|
706
|
+
},
|
|
707
|
+
set(newValue) {
|
|
708
|
+
nameOrAccessor.setValue(this, newValue);
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
/**
|
|
713
|
+
* Finds all the observable accessors defined on the target,
|
|
714
|
+
* including its prototype chain.
|
|
715
|
+
* @param target - The target object to search for accessor on.
|
|
716
|
+
*/
|
|
717
|
+
getAccessors,
|
|
718
|
+
/**
|
|
719
|
+
* Creates a {@link ExpressionNotifier} that can watch the
|
|
720
|
+
* provided {@link Expression} for changes.
|
|
721
|
+
* @param expression - The binding to observe.
|
|
722
|
+
* @param initialSubscriber - An initial subscriber to changes in the binding value.
|
|
723
|
+
* @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
|
|
724
|
+
*/
|
|
725
|
+
binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
|
|
726
|
+
return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
|
|
727
|
+
},
|
|
728
|
+
/**
|
|
729
|
+
* Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
|
|
730
|
+
* on every evaluation of the value.
|
|
731
|
+
* @param expression - The binding to inspect.
|
|
732
|
+
*/
|
|
733
|
+
isVolatileBinding(expression) {
|
|
734
|
+
return volatileRegex.test(expression.toString());
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
/**
|
|
739
|
+
* Decorator: Defines an observable property on the target.
|
|
740
|
+
* @param target - The target to define the observable on.
|
|
741
|
+
* @param nameOrAccessor - The property name or accessor to define the observable as.
|
|
742
|
+
* @public
|
|
743
|
+
*/
|
|
744
|
+
function observable(target, nameOrAccessor) {
|
|
745
|
+
Observable.defineProperty(target, nameOrAccessor);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Decorator: Marks a property getter as having volatile observable dependencies.
|
|
749
|
+
* @param target - The target that the property is defined on.
|
|
750
|
+
* @param name - The property name.
|
|
751
|
+
* @param name - The existing descriptor.
|
|
752
|
+
* @public
|
|
753
|
+
*/
|
|
754
|
+
function volatile(target, name, descriptor) {
|
|
755
|
+
return Object.assign({}, descriptor, {
|
|
756
|
+
get() {
|
|
757
|
+
Observable.trackVolatile();
|
|
758
|
+
return descriptor.get.apply(this);
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
|
|
763
|
+
let current = null;
|
|
764
|
+
return {
|
|
765
|
+
get() {
|
|
766
|
+
return current;
|
|
767
|
+
},
|
|
768
|
+
set(event) {
|
|
769
|
+
current = event;
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
});
|
|
773
|
+
/**
|
|
774
|
+
* Provides additional contextual information available to behaviors and expressions.
|
|
775
|
+
* @public
|
|
776
|
+
*/
|
|
777
|
+
const ExecutionContext = Object.freeze({
|
|
778
|
+
/**
|
|
779
|
+
* A default execution context.
|
|
780
|
+
*/
|
|
781
|
+
default: {
|
|
782
|
+
index: 0,
|
|
783
|
+
length: 0,
|
|
784
|
+
get event() {
|
|
785
|
+
return ExecutionContext.getEvent();
|
|
786
|
+
},
|
|
787
|
+
eventDetail() {
|
|
788
|
+
return this.event.detail;
|
|
789
|
+
},
|
|
790
|
+
eventTarget() {
|
|
791
|
+
return this.event.target;
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
/**
|
|
795
|
+
* Gets the current event.
|
|
796
|
+
* @returns An event object.
|
|
797
|
+
*/
|
|
798
|
+
getEvent() {
|
|
799
|
+
return contextEvent.get();
|
|
800
|
+
},
|
|
801
|
+
/**
|
|
802
|
+
* Sets the current event.
|
|
803
|
+
* @param event - An event object.
|
|
804
|
+
*/
|
|
805
|
+
setEvent(event) {
|
|
806
|
+
contextEvent.set(event);
|
|
807
|
+
},
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* A splice map is a representation of how a previous array of items
|
|
812
|
+
* was transformed into a new array of items. Conceptually it is a list of
|
|
813
|
+
* tuples of
|
|
814
|
+
*
|
|
815
|
+
* (index, removed, addedCount)
|
|
816
|
+
*
|
|
817
|
+
* which are kept in ascending index order of. The tuple represents that at
|
|
818
|
+
* the |index|, |removed| sequence of items were removed, and counting forward
|
|
819
|
+
* from |index|, |addedCount| items were added.
|
|
820
|
+
* @public
|
|
821
|
+
*/
|
|
822
|
+
class Splice {
|
|
823
|
+
/**
|
|
824
|
+
* Creates a splice.
|
|
825
|
+
* @param index - The index that the splice occurs at.
|
|
826
|
+
* @param removed - The items that were removed.
|
|
827
|
+
* @param addedCount - The number of items that were added.
|
|
828
|
+
*/
|
|
829
|
+
constructor(index, removed, addedCount) {
|
|
830
|
+
this.index = index;
|
|
831
|
+
this.removed = removed;
|
|
832
|
+
this.addedCount = addedCount;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Adjusts the splice index based on the provided array.
|
|
836
|
+
* @param array - The array to adjust to.
|
|
837
|
+
* @returns The same splice, mutated based on the reference array.
|
|
838
|
+
*/
|
|
839
|
+
adjustTo(array) {
|
|
840
|
+
let index = this.index;
|
|
841
|
+
const arrayLength = array.length;
|
|
842
|
+
if (index > arrayLength) {
|
|
843
|
+
index = arrayLength - this.addedCount;
|
|
844
|
+
}
|
|
845
|
+
else if (index < 0) {
|
|
846
|
+
index = arrayLength + this.removed.length + index - this.addedCount;
|
|
847
|
+
}
|
|
848
|
+
this.index = index < 0 ? 0 : index;
|
|
849
|
+
return this;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Indicates what level of feature support the splice
|
|
854
|
+
* strategy provides.
|
|
855
|
+
* @public
|
|
856
|
+
*/
|
|
857
|
+
const SpliceStrategySupport = Object.freeze({
|
|
858
|
+
/**
|
|
859
|
+
* Only supports resets.
|
|
860
|
+
*/
|
|
861
|
+
reset: 1,
|
|
862
|
+
/**
|
|
863
|
+
* Supports tracking splices and resets.
|
|
864
|
+
*/
|
|
865
|
+
splice: 2,
|
|
866
|
+
/**
|
|
867
|
+
* Supports tracking splices and resets, while applying some form
|
|
868
|
+
* of optimization, such as merging, to the splices.
|
|
869
|
+
*/
|
|
870
|
+
optimized: 3,
|
|
871
|
+
});
|
|
872
|
+
const reset = new Splice(0, emptyArray, 0);
|
|
873
|
+
reset.reset = true;
|
|
874
|
+
const resetSplices = [reset];
|
|
875
|
+
// Note: This function is *based* on the computation of the Levenshtein
|
|
876
|
+
// "edit" distance. The one change is that "updates" are treated as two
|
|
877
|
+
// edits - not one. With Array splices, an update is really a delete
|
|
878
|
+
// followed by an add. By retaining this, we optimize for "keeping" the
|
|
879
|
+
// maximum array items in the original array. For example:
|
|
880
|
+
//
|
|
881
|
+
// 'xxxx123' to '123yyyy'
|
|
882
|
+
//
|
|
883
|
+
// With 1-edit updates, the shortest path would be just to update all seven
|
|
884
|
+
// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
|
|
885
|
+
// leaves the substring '123' intact.
|
|
886
|
+
function calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd) {
|
|
887
|
+
// "Deletion" columns
|
|
888
|
+
const rowCount = oldEnd - oldStart + 1;
|
|
889
|
+
const columnCount = currentEnd - currentStart + 1;
|
|
890
|
+
const distances = new Array(rowCount);
|
|
891
|
+
let north;
|
|
892
|
+
let west;
|
|
893
|
+
// "Addition" rows. Initialize null column.
|
|
894
|
+
for (let i = 0; i < rowCount; ++i) {
|
|
895
|
+
distances[i] = new Array(columnCount);
|
|
896
|
+
distances[i][0] = i;
|
|
897
|
+
}
|
|
898
|
+
// Initialize null row
|
|
899
|
+
for (let j = 0; j < columnCount; ++j) {
|
|
900
|
+
distances[0][j] = j;
|
|
901
|
+
}
|
|
902
|
+
for (let i = 1; i < rowCount; ++i) {
|
|
903
|
+
for (let j = 1; j < columnCount; ++j) {
|
|
904
|
+
if (current[currentStart + j - 1] === old[oldStart + i - 1]) {
|
|
905
|
+
distances[i][j] = distances[i - 1][j - 1];
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
north = distances[i - 1][j] + 1;
|
|
909
|
+
west = distances[i][j - 1] + 1;
|
|
910
|
+
distances[i][j] = north < west ? north : west;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return distances;
|
|
915
|
+
}
|
|
916
|
+
// This starts at the final weight, and walks "backward" by finding
|
|
917
|
+
// the minimum previous weight recursively until the origin of the weight
|
|
918
|
+
// matrix.
|
|
919
|
+
function spliceOperationsFromEditDistances(distances) {
|
|
920
|
+
let i = distances.length - 1;
|
|
921
|
+
let j = distances[0].length - 1;
|
|
922
|
+
let current = distances[i][j];
|
|
923
|
+
const edits = [];
|
|
924
|
+
while (i > 0 || j > 0) {
|
|
925
|
+
if (i === 0) {
|
|
926
|
+
edits.push(2 /* Edit.add */);
|
|
927
|
+
j--;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (j === 0) {
|
|
931
|
+
edits.push(3 /* Edit.delete */);
|
|
932
|
+
i--;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const northWest = distances[i - 1][j - 1];
|
|
936
|
+
const west = distances[i - 1][j];
|
|
937
|
+
const north = distances[i][j - 1];
|
|
938
|
+
let min;
|
|
939
|
+
if (west < north) {
|
|
940
|
+
min = west < northWest ? west : northWest;
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
min = north < northWest ? north : northWest;
|
|
944
|
+
}
|
|
945
|
+
if (min === northWest) {
|
|
946
|
+
if (northWest === current) {
|
|
947
|
+
edits.push(0 /* Edit.leave */);
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
edits.push(1 /* Edit.update */);
|
|
951
|
+
current = northWest;
|
|
952
|
+
}
|
|
953
|
+
i--;
|
|
954
|
+
j--;
|
|
955
|
+
}
|
|
956
|
+
else if (min === west) {
|
|
957
|
+
edits.push(3 /* Edit.delete */);
|
|
958
|
+
i--;
|
|
959
|
+
current = west;
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
edits.push(2 /* Edit.add */);
|
|
963
|
+
j--;
|
|
964
|
+
current = north;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return edits.reverse();
|
|
968
|
+
}
|
|
969
|
+
function sharedPrefix(current, old, searchLength) {
|
|
970
|
+
for (let i = 0; i < searchLength; ++i) {
|
|
971
|
+
if (current[i] !== old[i]) {
|
|
972
|
+
return i;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return searchLength;
|
|
976
|
+
}
|
|
977
|
+
function sharedSuffix(current, old, searchLength) {
|
|
978
|
+
let index1 = current.length;
|
|
979
|
+
let index2 = old.length;
|
|
980
|
+
let count = 0;
|
|
981
|
+
while (count < searchLength && current[--index1] === old[--index2]) {
|
|
982
|
+
count++;
|
|
983
|
+
}
|
|
984
|
+
return count;
|
|
985
|
+
}
|
|
986
|
+
function intersect(start1, end1, start2, end2) {
|
|
987
|
+
// Disjoint
|
|
988
|
+
if (end1 < start2 || end2 < start1) {
|
|
989
|
+
return -1;
|
|
990
|
+
}
|
|
991
|
+
// Adjacent
|
|
992
|
+
if (end1 === start2 || end2 === start1) {
|
|
993
|
+
return 0;
|
|
994
|
+
}
|
|
995
|
+
// Non-zero intersect, span1 first
|
|
996
|
+
if (start1 < start2) {
|
|
997
|
+
if (end1 < end2) {
|
|
998
|
+
return end1 - start2; // Overlap
|
|
999
|
+
}
|
|
1000
|
+
return end2 - start2; // Contained
|
|
1001
|
+
}
|
|
1002
|
+
// Non-zero intersect, span2 first
|
|
1003
|
+
if (end2 < end1) {
|
|
1004
|
+
return end2 - start1; // Overlap
|
|
1005
|
+
}
|
|
1006
|
+
return end1 - start1; // Contained
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* @remarks
|
|
1010
|
+
* Lacking individual splice mutation information, the minimal set of
|
|
1011
|
+
* splices can be synthesized given the previous state and final state of an
|
|
1012
|
+
* array. The basic approach is to calculate the edit distance matrix and
|
|
1013
|
+
* choose the shortest path through it.
|
|
1014
|
+
*
|
|
1015
|
+
* Complexity: O(l * p)
|
|
1016
|
+
* l: The length of the current array
|
|
1017
|
+
* p: The length of the old array
|
|
1018
|
+
*/
|
|
1019
|
+
function calc(current, currentStart, currentEnd, old, oldStart, oldEnd) {
|
|
1020
|
+
let prefixCount = 0;
|
|
1021
|
+
let suffixCount = 0;
|
|
1022
|
+
const minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
|
|
1023
|
+
if (currentStart === 0 && oldStart === 0) {
|
|
1024
|
+
prefixCount = sharedPrefix(current, old, minLength);
|
|
1025
|
+
}
|
|
1026
|
+
if (currentEnd === current.length && oldEnd === old.length) {
|
|
1027
|
+
suffixCount = sharedSuffix(current, old, minLength - prefixCount);
|
|
1028
|
+
}
|
|
1029
|
+
currentStart += prefixCount;
|
|
1030
|
+
oldStart += prefixCount;
|
|
1031
|
+
currentEnd -= suffixCount;
|
|
1032
|
+
oldEnd -= suffixCount;
|
|
1033
|
+
if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) {
|
|
1034
|
+
return emptyArray;
|
|
1035
|
+
}
|
|
1036
|
+
if (currentStart === currentEnd) {
|
|
1037
|
+
const splice = new Splice(currentStart, [], 0);
|
|
1038
|
+
while (oldStart < oldEnd) {
|
|
1039
|
+
splice.removed.push(old[oldStart++]);
|
|
1040
|
+
}
|
|
1041
|
+
return [splice];
|
|
1042
|
+
}
|
|
1043
|
+
else if (oldStart === oldEnd) {
|
|
1044
|
+
return [new Splice(currentStart, [], currentEnd - currentStart)];
|
|
1045
|
+
}
|
|
1046
|
+
const ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
|
|
1047
|
+
const splices = [];
|
|
1048
|
+
let splice = void 0;
|
|
1049
|
+
let index = currentStart;
|
|
1050
|
+
let oldIndex = oldStart;
|
|
1051
|
+
for (let i = 0; i < ops.length; ++i) {
|
|
1052
|
+
switch (ops[i]) {
|
|
1053
|
+
case 0 /* Edit.leave */:
|
|
1054
|
+
if (splice !== void 0) {
|
|
1055
|
+
splices.push(splice);
|
|
1056
|
+
splice = void 0;
|
|
1057
|
+
}
|
|
1058
|
+
index++;
|
|
1059
|
+
oldIndex++;
|
|
1060
|
+
break;
|
|
1061
|
+
case 1 /* Edit.update */:
|
|
1062
|
+
if (splice === void 0) {
|
|
1063
|
+
splice = new Splice(index, [], 0);
|
|
1064
|
+
}
|
|
1065
|
+
splice.addedCount++;
|
|
1066
|
+
index++;
|
|
1067
|
+
splice.removed.push(old[oldIndex]);
|
|
1068
|
+
oldIndex++;
|
|
1069
|
+
break;
|
|
1070
|
+
case 2 /* Edit.add */:
|
|
1071
|
+
if (splice === void 0) {
|
|
1072
|
+
splice = new Splice(index, [], 0);
|
|
1073
|
+
}
|
|
1074
|
+
splice.addedCount++;
|
|
1075
|
+
index++;
|
|
1076
|
+
break;
|
|
1077
|
+
case 3 /* Edit.delete */:
|
|
1078
|
+
if (splice === void 0) {
|
|
1079
|
+
splice = new Splice(index, [], 0);
|
|
1080
|
+
}
|
|
1081
|
+
splice.removed.push(old[oldIndex]);
|
|
1082
|
+
oldIndex++;
|
|
1083
|
+
break;
|
|
1084
|
+
// no default
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (splice !== void 0) {
|
|
1088
|
+
splices.push(splice);
|
|
1089
|
+
}
|
|
1090
|
+
return splices;
|
|
1091
|
+
}
|
|
1092
|
+
function merge(splice, splices) {
|
|
1093
|
+
let inserted = false;
|
|
1094
|
+
let insertionOffset = 0;
|
|
1095
|
+
for (let i = 0; i < splices.length; i++) {
|
|
1096
|
+
const current = splices[i];
|
|
1097
|
+
current.index += insertionOffset;
|
|
1098
|
+
if (inserted) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
const intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount);
|
|
1102
|
+
if (intersectCount >= 0) {
|
|
1103
|
+
// Merge the two splices
|
|
1104
|
+
splices.splice(i, 1);
|
|
1105
|
+
i--;
|
|
1106
|
+
insertionOffset -= current.addedCount - current.removed.length;
|
|
1107
|
+
splice.addedCount += current.addedCount - intersectCount;
|
|
1108
|
+
const deleteCount = splice.removed.length + current.removed.length - intersectCount;
|
|
1109
|
+
if (!splice.addedCount && !deleteCount) {
|
|
1110
|
+
// merged splice is a noop. discard.
|
|
1111
|
+
inserted = true;
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
let currentRemoved = current.removed;
|
|
1115
|
+
if (splice.index < current.index) {
|
|
1116
|
+
// some prefix of splice.removed is prepended to current.removed.
|
|
1117
|
+
const prepend = splice.removed.slice(0, current.index - splice.index);
|
|
1118
|
+
prepend.push(...currentRemoved);
|
|
1119
|
+
currentRemoved = prepend;
|
|
1120
|
+
}
|
|
1121
|
+
if (splice.index + splice.removed.length >
|
|
1122
|
+
current.index + current.addedCount) {
|
|
1123
|
+
// some suffix of splice.removed is appended to current.removed.
|
|
1124
|
+
const append = splice.removed.slice(current.index + current.addedCount - splice.index);
|
|
1125
|
+
currentRemoved.push(...append);
|
|
1126
|
+
}
|
|
1127
|
+
splice.removed = currentRemoved;
|
|
1128
|
+
if (current.index < splice.index) {
|
|
1129
|
+
splice.index = current.index;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
else if (splice.index < current.index) {
|
|
1134
|
+
// Insert splice here.
|
|
1135
|
+
inserted = true;
|
|
1136
|
+
splices.splice(i, 0, splice);
|
|
1137
|
+
i++;
|
|
1138
|
+
const offset = splice.addedCount - splice.removed.length;
|
|
1139
|
+
current.index += offset;
|
|
1140
|
+
insertionOffset += offset;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
if (!inserted) {
|
|
1144
|
+
splices.push(splice);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function project(array, changes) {
|
|
1148
|
+
let splices = [];
|
|
1149
|
+
const initialSplices = [];
|
|
1150
|
+
for (let i = 0, ii = changes.length; i < ii; i++) {
|
|
1151
|
+
merge(changes[i], initialSplices);
|
|
1152
|
+
}
|
|
1153
|
+
for (let i = 0, ii = initialSplices.length; i < ii; ++i) {
|
|
1154
|
+
const splice = initialSplices[i];
|
|
1155
|
+
if (splice.addedCount === 1 && splice.removed.length === 1) {
|
|
1156
|
+
if (splice.removed[0] !== array[splice.index]) {
|
|
1157
|
+
splices.push(splice);
|
|
1158
|
+
}
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
splices = splices.concat(calc(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length));
|
|
1162
|
+
}
|
|
1163
|
+
return splices;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* A SpliceStrategy that attempts to merge all splices into the minimal set of
|
|
1167
|
+
* splices needed to represent the change from the old array to the new array.
|
|
1168
|
+
* @public
|
|
1169
|
+
*/
|
|
1170
|
+
let defaultSpliceStrategy = Object.freeze({
|
|
1171
|
+
support: SpliceStrategySupport.optimized,
|
|
1172
|
+
normalize(previous, current, changes) {
|
|
1173
|
+
if (previous === void 0) {
|
|
1174
|
+
if (changes === void 0) {
|
|
1175
|
+
return emptyArray;
|
|
1176
|
+
}
|
|
1177
|
+
return changes.length > 1 ? project(current, changes) : changes;
|
|
1178
|
+
}
|
|
1179
|
+
return resetSplices;
|
|
1180
|
+
},
|
|
1181
|
+
pop(array, observer, pop, args) {
|
|
1182
|
+
const notEmpty = array.length > 0;
|
|
1183
|
+
const result = pop.apply(array, args);
|
|
1184
|
+
if (notEmpty) {
|
|
1185
|
+
observer.addSplice(new Splice(array.length, [result], 0));
|
|
1186
|
+
}
|
|
1187
|
+
return result;
|
|
1188
|
+
},
|
|
1189
|
+
push(array, observer, push, args) {
|
|
1190
|
+
const result = push.apply(array, args);
|
|
1191
|
+
observer.addSplice(new Splice(array.length - args.length, [], args.length).adjustTo(array));
|
|
1192
|
+
return result;
|
|
1193
|
+
},
|
|
1194
|
+
reverse(array, observer, reverse, args) {
|
|
1195
|
+
const result = reverse.apply(array, args);
|
|
1196
|
+
observer.reset(array);
|
|
1197
|
+
return result;
|
|
1198
|
+
},
|
|
1199
|
+
shift(array, observer, shift, args) {
|
|
1200
|
+
const notEmpty = array.length > 0;
|
|
1201
|
+
const result = shift.apply(array, args);
|
|
1202
|
+
if (notEmpty) {
|
|
1203
|
+
observer.addSplice(new Splice(0, [result], 0));
|
|
1204
|
+
}
|
|
1205
|
+
return result;
|
|
1206
|
+
},
|
|
1207
|
+
sort(array, observer, sort, args) {
|
|
1208
|
+
const result = sort.apply(array, args);
|
|
1209
|
+
observer.reset(array);
|
|
1210
|
+
return result;
|
|
1211
|
+
},
|
|
1212
|
+
splice(array, observer, splice, args) {
|
|
1213
|
+
const result = splice.apply(array, args);
|
|
1214
|
+
observer.addSplice(new Splice(+args[0], result, args.length > 2 ? args.length - 2 : 0).adjustTo(array));
|
|
1215
|
+
return result;
|
|
1216
|
+
},
|
|
1217
|
+
unshift(array, observer, unshift, args) {
|
|
1218
|
+
const result = unshift.apply(array, args);
|
|
1219
|
+
observer.addSplice(new Splice(0, [], args.length).adjustTo(array));
|
|
1220
|
+
return result;
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
/**
|
|
1224
|
+
* Functionality related to tracking changes in arrays.
|
|
1225
|
+
* @public
|
|
1226
|
+
*/
|
|
1227
|
+
const SpliceStrategy = Object.freeze({
|
|
1228
|
+
/**
|
|
1229
|
+
* A set of changes that represent a full array reset.
|
|
1230
|
+
*/
|
|
1231
|
+
reset: resetSplices,
|
|
1232
|
+
/**
|
|
1233
|
+
* Sets the default strategy to use for array observers.
|
|
1234
|
+
* @param strategy - The splice strategy to use.
|
|
1235
|
+
*/
|
|
1236
|
+
setDefaultStrategy(strategy) {
|
|
1237
|
+
defaultSpliceStrategy = strategy;
|
|
1238
|
+
},
|
|
1239
|
+
});
|
|
1240
|
+
function setNonEnumerable(target, property, value) {
|
|
1241
|
+
Reflect.defineProperty(target, property, {
|
|
1242
|
+
value,
|
|
1243
|
+
enumerable: false,
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
class DefaultArrayObserver extends SubscriberSet {
|
|
1247
|
+
constructor(subject) {
|
|
1248
|
+
super(subject);
|
|
1249
|
+
this.oldCollection = void 0;
|
|
1250
|
+
this.splices = void 0;
|
|
1251
|
+
this.needsQueue = true;
|
|
1252
|
+
this._strategy = null;
|
|
1253
|
+
this._lengthObserver = void 0;
|
|
1254
|
+
this.call = this.flush;
|
|
1255
|
+
setNonEnumerable(subject, "$fastController", this);
|
|
1256
|
+
}
|
|
1257
|
+
get strategy() {
|
|
1258
|
+
return this._strategy;
|
|
1259
|
+
}
|
|
1260
|
+
set strategy(value) {
|
|
1261
|
+
this._strategy = value;
|
|
1262
|
+
}
|
|
1263
|
+
get lengthObserver() {
|
|
1264
|
+
let observer = this._lengthObserver;
|
|
1265
|
+
if (observer === void 0) {
|
|
1266
|
+
const array = this.subject;
|
|
1267
|
+
this._lengthObserver = observer = {
|
|
1268
|
+
length: array.length,
|
|
1269
|
+
handleChange() {
|
|
1270
|
+
if (this.length !== array.length) {
|
|
1271
|
+
this.length = array.length;
|
|
1272
|
+
Observable.notify(observer, "length");
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
};
|
|
1276
|
+
this.subscribe(observer);
|
|
1277
|
+
}
|
|
1278
|
+
return observer;
|
|
1279
|
+
}
|
|
1280
|
+
subscribe(subscriber) {
|
|
1281
|
+
this.flush();
|
|
1282
|
+
super.subscribe(subscriber);
|
|
1283
|
+
}
|
|
1284
|
+
addSplice(splice) {
|
|
1285
|
+
if (this.splices === void 0) {
|
|
1286
|
+
this.splices = [splice];
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
this.splices.push(splice);
|
|
1290
|
+
}
|
|
1291
|
+
this.enqueue();
|
|
1292
|
+
}
|
|
1293
|
+
reset(oldCollection) {
|
|
1294
|
+
this.oldCollection = oldCollection;
|
|
1295
|
+
this.enqueue();
|
|
1296
|
+
}
|
|
1297
|
+
flush() {
|
|
1298
|
+
var _a;
|
|
1299
|
+
const splices = this.splices;
|
|
1300
|
+
const oldCollection = this.oldCollection;
|
|
1301
|
+
if (splices === void 0 && oldCollection === void 0) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
this.needsQueue = true;
|
|
1305
|
+
this.splices = void 0;
|
|
1306
|
+
this.oldCollection = void 0;
|
|
1307
|
+
this.notify(((_a = this._strategy) !== null && _a !== void 0 ? _a : defaultSpliceStrategy).normalize(oldCollection, this.subject, splices));
|
|
1308
|
+
}
|
|
1309
|
+
enqueue() {
|
|
1310
|
+
if (this.needsQueue) {
|
|
1311
|
+
this.needsQueue = false;
|
|
1312
|
+
Updates.enqueue(this);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
let enabled = false;
|
|
1317
|
+
/**
|
|
1318
|
+
* An observer for arrays.
|
|
1319
|
+
* @public
|
|
1320
|
+
*/
|
|
1321
|
+
const ArrayObserver = Object.freeze({
|
|
1322
|
+
/**
|
|
1323
|
+
* Enables the array observation mechanism.
|
|
1324
|
+
* @remarks
|
|
1325
|
+
* Array observation is enabled automatically when using the
|
|
1326
|
+
* {@link RepeatDirective}, so calling this API manually is
|
|
1327
|
+
* not typically necessary.
|
|
1328
|
+
*/
|
|
1329
|
+
enable() {
|
|
1330
|
+
if (enabled) {
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
enabled = true;
|
|
1334
|
+
Observable.setArrayObserverFactory((collection) => new DefaultArrayObserver(collection));
|
|
1335
|
+
const proto = Array.prototype;
|
|
1336
|
+
if (!proto.$fastPatch) {
|
|
1337
|
+
setNonEnumerable(proto, "$fastPatch", 1);
|
|
1338
|
+
[
|
|
1339
|
+
proto.pop,
|
|
1340
|
+
proto.push,
|
|
1341
|
+
proto.reverse,
|
|
1342
|
+
proto.shift,
|
|
1343
|
+
proto.sort,
|
|
1344
|
+
proto.splice,
|
|
1345
|
+
proto.unshift,
|
|
1346
|
+
].forEach(method => {
|
|
1347
|
+
proto[method.name] = function (...args) {
|
|
1348
|
+
var _a;
|
|
1349
|
+
const o = this.$fastController;
|
|
1350
|
+
return o === void 0
|
|
1351
|
+
? method.apply(this, args)
|
|
1352
|
+
: ((_a = o.strategy) !== null && _a !== void 0 ? _a : defaultSpliceStrategy)[method.name](this, o, method, args);
|
|
1353
|
+
};
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
});
|
|
1358
|
+
/**
|
|
1359
|
+
* Enables observing the length of an array.
|
|
1360
|
+
* @param array - The array to observe the length of.
|
|
1361
|
+
* @returns The length of the array.
|
|
1362
|
+
* @public
|
|
1363
|
+
*/
|
|
1364
|
+
function lengthOf(array) {
|
|
1365
|
+
if (!array) {
|
|
1366
|
+
return 0;
|
|
1367
|
+
}
|
|
1368
|
+
let arrayObserver = array.$fastController;
|
|
1369
|
+
if (arrayObserver === void 0) {
|
|
1370
|
+
ArrayObserver.enable();
|
|
1371
|
+
arrayObserver = Observable.getNotifier(array);
|
|
1372
|
+
}
|
|
1373
|
+
Observable.track(arrayObserver.lengthObserver, "length");
|
|
1374
|
+
return array.length;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const styleSheetCache = new Map();
|
|
1378
|
+
let DefaultStyleStrategy;
|
|
1379
|
+
function reduceStyles(styles) {
|
|
1380
|
+
return styles
|
|
1381
|
+
.map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
|
|
1382
|
+
.reduce((prev, curr) => prev.concat(curr), []);
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Represents styles that can be applied to a custom element.
|
|
1386
|
+
* @public
|
|
1387
|
+
*/
|
|
1388
|
+
class ElementStyles {
|
|
1389
|
+
/**
|
|
1390
|
+
* Creates an instance of ElementStyles.
|
|
1391
|
+
* @param styles - The styles that will be associated with elements.
|
|
1392
|
+
*/
|
|
1393
|
+
constructor(styles) {
|
|
1394
|
+
this.styles = styles;
|
|
1395
|
+
this.targets = new WeakSet();
|
|
1396
|
+
this._strategy = null;
|
|
1397
|
+
this.behaviors = styles
|
|
1398
|
+
.map((x) => x instanceof ElementStyles ? x.behaviors : null)
|
|
1399
|
+
.reduce((prev, curr) => (curr === null ? prev : prev === null ? curr : prev.concat(curr)), null);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Gets the StyleStrategy associated with these element styles.
|
|
1403
|
+
*/
|
|
1404
|
+
get strategy() {
|
|
1405
|
+
if (this._strategy === null) {
|
|
1406
|
+
this.withStrategy(DefaultStyleStrategy);
|
|
1407
|
+
}
|
|
1408
|
+
return this._strategy;
|
|
1409
|
+
}
|
|
1410
|
+
/** @internal */
|
|
1411
|
+
addStylesTo(target) {
|
|
1412
|
+
this.strategy.addStylesTo(target);
|
|
1413
|
+
this.targets.add(target);
|
|
1414
|
+
}
|
|
1415
|
+
/** @internal */
|
|
1416
|
+
removeStylesFrom(target) {
|
|
1417
|
+
this.strategy.removeStylesFrom(target);
|
|
1418
|
+
this.targets.delete(target);
|
|
1419
|
+
}
|
|
1420
|
+
/** @internal */
|
|
1421
|
+
isAttachedTo(target) {
|
|
1422
|
+
return this.targets.has(target);
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Associates behaviors with this set of styles.
|
|
1426
|
+
* @param behaviors - The behaviors to associate.
|
|
1427
|
+
*/
|
|
1428
|
+
withBehaviors(...behaviors) {
|
|
1429
|
+
this.behaviors =
|
|
1430
|
+
this.behaviors === null ? behaviors : this.behaviors.concat(behaviors);
|
|
1431
|
+
return this;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Sets the strategy that handles adding/removing these styles for an element.
|
|
1435
|
+
* @param strategy - The strategy to use.
|
|
1436
|
+
*/
|
|
1437
|
+
withStrategy(Strategy) {
|
|
1438
|
+
this._strategy = new Strategy(reduceStyles(this.styles));
|
|
1439
|
+
return this;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Sets the default strategy type to use when creating style strategies.
|
|
1443
|
+
* @param Strategy - The strategy type to construct.
|
|
1444
|
+
*/
|
|
1445
|
+
static setDefaultStrategy(Strategy) {
|
|
1446
|
+
DefaultStyleStrategy = Strategy;
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Normalizes a set of composable style options.
|
|
1450
|
+
* @param styles - The style options to normalize.
|
|
1451
|
+
* @returns A singular ElementStyles instance or undefined.
|
|
1452
|
+
*/
|
|
1453
|
+
static normalize(styles) {
|
|
1454
|
+
return styles === void 0
|
|
1455
|
+
? void 0
|
|
1456
|
+
: Array.isArray(styles)
|
|
1457
|
+
? new ElementStyles(styles)
|
|
1458
|
+
: styles instanceof ElementStyles
|
|
1459
|
+
? styles
|
|
1460
|
+
: new ElementStyles([styles]);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Indicates whether the DOM supports the adoptedStyleSheets feature.
|
|
1465
|
+
*/
|
|
1466
|
+
ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
|
|
1467
|
+
"replace" in CSSStyleSheet.prototype;
|
|
1468
|
+
/**
|
|
1469
|
+
* https://wicg.github.io/construct-stylesheets/
|
|
1470
|
+
* https://developers.google.com/web/updates/2019/02/constructable-stylesheets
|
|
1471
|
+
*
|
|
1472
|
+
* @internal
|
|
1473
|
+
*/
|
|
1474
|
+
class AdoptedStyleSheetsStrategy {
|
|
1475
|
+
constructor(styles) {
|
|
1476
|
+
this.sheets = styles.map((x) => {
|
|
1477
|
+
if (x instanceof CSSStyleSheet) {
|
|
1478
|
+
return x;
|
|
1479
|
+
}
|
|
1480
|
+
let sheet = styleSheetCache.get(x);
|
|
1481
|
+
if (sheet === void 0) {
|
|
1482
|
+
sheet = new CSSStyleSheet();
|
|
1483
|
+
sheet.replaceSync(x);
|
|
1484
|
+
styleSheetCache.set(x, sheet);
|
|
1485
|
+
}
|
|
1486
|
+
return sheet;
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
addStylesTo(target) {
|
|
1490
|
+
target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
|
|
1491
|
+
}
|
|
1492
|
+
removeStylesFrom(target) {
|
|
1493
|
+
const sheets = this.sheets;
|
|
1494
|
+
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
|
|
1498
|
+
|
|
1499
|
+
const registry$1 = createTypeRegistry();
|
|
1500
|
+
/**
|
|
1501
|
+
* Instructs the css engine to provide dynamic styles or
|
|
1502
|
+
* associate behaviors with styles.
|
|
1503
|
+
* @public
|
|
1504
|
+
*/
|
|
1505
|
+
const CSSDirective = Object.freeze({
|
|
1506
|
+
/**
|
|
1507
|
+
* Gets the directive definition associated with the instance.
|
|
1508
|
+
* @param instance - The directive instance to retrieve the definition for.
|
|
1509
|
+
*/
|
|
1510
|
+
getForInstance: registry$1.getForInstance,
|
|
1511
|
+
/**
|
|
1512
|
+
* Gets the directive definition associated with the specified type.
|
|
1513
|
+
* @param type - The directive type to retrieve the definition for.
|
|
1514
|
+
*/
|
|
1515
|
+
getByType: registry$1.getByType,
|
|
1516
|
+
/**
|
|
1517
|
+
* Defines a CSSDirective.
|
|
1518
|
+
* @param type - The type to define as a directive.
|
|
1519
|
+
*/
|
|
1520
|
+
define(type) {
|
|
1521
|
+
registry$1.register({ type });
|
|
1522
|
+
return type;
|
|
1523
|
+
},
|
|
1524
|
+
});
|
|
1525
|
+
/**
|
|
1526
|
+
* Decorator: Defines a CSSDirective.
|
|
1527
|
+
* @public
|
|
1528
|
+
*/
|
|
1529
|
+
function cssDirective() {
|
|
1530
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
1531
|
+
return function (type) {
|
|
1532
|
+
CSSDirective.define(type);
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function collectStyles(strings, values) {
|
|
1537
|
+
const styles = [];
|
|
1538
|
+
let cssString = "";
|
|
1539
|
+
const behaviors = [];
|
|
1540
|
+
const add = (behavior) => {
|
|
1541
|
+
behaviors.push(behavior);
|
|
1542
|
+
};
|
|
1543
|
+
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
1544
|
+
cssString += strings[i];
|
|
1545
|
+
let value = values[i];
|
|
1546
|
+
if (CSSDirective.getForInstance(value) !== void 0) {
|
|
1547
|
+
value = value.createCSS(add);
|
|
1548
|
+
}
|
|
1549
|
+
if (value instanceof ElementStyles || value instanceof CSSStyleSheet) {
|
|
1550
|
+
if (cssString.trim() !== "") {
|
|
1551
|
+
styles.push(cssString);
|
|
1552
|
+
cssString = "";
|
|
1553
|
+
}
|
|
1554
|
+
styles.push(value);
|
|
1555
|
+
}
|
|
1556
|
+
else {
|
|
1557
|
+
cssString += value;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
cssString += strings[strings.length - 1];
|
|
1561
|
+
if (cssString.trim() !== "") {
|
|
1562
|
+
styles.push(cssString);
|
|
1563
|
+
}
|
|
1564
|
+
return {
|
|
1565
|
+
styles,
|
|
1566
|
+
behaviors,
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Transforms a template literal string into styles.
|
|
1571
|
+
* @param strings - The string fragments that are interpolated with the values.
|
|
1572
|
+
* @param values - The values that are interpolated with the string fragments.
|
|
1573
|
+
* @remarks
|
|
1574
|
+
* The css helper supports interpolation of strings and ElementStyle instances.
|
|
1575
|
+
* @public
|
|
1576
|
+
*/
|
|
1577
|
+
const css = ((strings, ...values) => {
|
|
1578
|
+
const { styles, behaviors } = collectStyles(strings, values);
|
|
1579
|
+
const elementStyles = new ElementStyles(styles);
|
|
1580
|
+
return behaviors.length ? elementStyles.withBehaviors(...behaviors) : elementStyles;
|
|
1581
|
+
});
|
|
1582
|
+
class CSSPartial {
|
|
1583
|
+
constructor(styles, behaviors) {
|
|
1584
|
+
this.behaviors = behaviors;
|
|
1585
|
+
this.css = "";
|
|
1586
|
+
const stylesheets = styles.reduce((accumulated, current) => {
|
|
1587
|
+
if (isString(current)) {
|
|
1588
|
+
this.css += current;
|
|
1589
|
+
}
|
|
1590
|
+
else {
|
|
1591
|
+
accumulated.push(current);
|
|
1592
|
+
}
|
|
1593
|
+
return accumulated;
|
|
1594
|
+
}, []);
|
|
1595
|
+
if (stylesheets.length) {
|
|
1596
|
+
this.styles = new ElementStyles(stylesheets);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
createCSS(add) {
|
|
1600
|
+
this.behaviors.forEach(add);
|
|
1601
|
+
if (this.styles) {
|
|
1602
|
+
add(this);
|
|
1603
|
+
}
|
|
1604
|
+
return this.css;
|
|
1605
|
+
}
|
|
1606
|
+
addedCallback(controller) {
|
|
1607
|
+
controller.addStyles(this.styles);
|
|
1608
|
+
}
|
|
1609
|
+
removedCallback(controller) {
|
|
1610
|
+
controller.removeStyles(this.styles);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
CSSDirective.define(CSSPartial);
|
|
1614
|
+
css.partial = (strings, ...values) => {
|
|
1615
|
+
const { styles, behaviors } = collectStyles(strings, values);
|
|
1616
|
+
return new CSSPartial(styles, behaviors);
|
|
1617
|
+
};
|
|
1618
|
+
/**
|
|
1619
|
+
* @deprecated Use css.partial instead.
|
|
1620
|
+
* @public
|
|
1621
|
+
*/
|
|
1622
|
+
const cssPartial = css.partial;
|
|
1623
|
+
|
|
1624
|
+
/**
|
|
1625
|
+
* Common DOM APIs.
|
|
1626
|
+
* @public
|
|
1627
|
+
*/
|
|
1628
|
+
const DOM = Object.freeze({
|
|
1629
|
+
/**
|
|
1630
|
+
* @deprecated
|
|
1631
|
+
* Use Updates.enqueue().
|
|
1632
|
+
*/
|
|
1633
|
+
queueUpdate: Updates.enqueue,
|
|
1634
|
+
/**
|
|
1635
|
+
* @deprecated
|
|
1636
|
+
* Use Updates.next()
|
|
1637
|
+
*/
|
|
1638
|
+
nextUpdate: Updates.next,
|
|
1639
|
+
/**
|
|
1640
|
+
* @deprecated
|
|
1641
|
+
* Use Updates.process()
|
|
1642
|
+
*/
|
|
1643
|
+
processUpdates: Updates.process,
|
|
1644
|
+
/**
|
|
1645
|
+
* Sets an attribute value on an element.
|
|
1646
|
+
* @param element - The element to set the attribute value on.
|
|
1647
|
+
* @param attributeName - The attribute name to set.
|
|
1648
|
+
* @param value - The value of the attribute to set.
|
|
1649
|
+
* @remarks
|
|
1650
|
+
* If the value is `null` or `undefined`, the attribute is removed, otherwise
|
|
1651
|
+
* it is set to the provided value using the standard `setAttribute` API.
|
|
1652
|
+
*/
|
|
1653
|
+
setAttribute(element, attributeName, value) {
|
|
1654
|
+
value === null || value === undefined
|
|
1655
|
+
? element.removeAttribute(attributeName)
|
|
1656
|
+
: element.setAttribute(attributeName, value);
|
|
1657
|
+
},
|
|
1658
|
+
/**
|
|
1659
|
+
* Sets a boolean attribute value.
|
|
1660
|
+
* @param element - The element to set the boolean attribute value on.
|
|
1661
|
+
* @param attributeName - The attribute name to set.
|
|
1662
|
+
* @param value - The value of the attribute to set.
|
|
1663
|
+
* @remarks
|
|
1664
|
+
* If the value is true, the attribute is added; otherwise it is removed.
|
|
1665
|
+
*/
|
|
1666
|
+
setBooleanAttribute(element, attributeName, value) {
|
|
1667
|
+
value
|
|
1668
|
+
? element.setAttribute(attributeName, "")
|
|
1669
|
+
: element.removeAttribute(attributeName);
|
|
1670
|
+
},
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
|
|
1674
|
+
const interpolationStart = `${marker}{`;
|
|
1675
|
+
const interpolationEnd = `}${marker}`;
|
|
1676
|
+
const interpolationEndLength = interpolationEnd.length;
|
|
1677
|
+
let id = 0;
|
|
1678
|
+
/** @internal */
|
|
1679
|
+
const nextId = () => `${marker}-${++id}`;
|
|
1680
|
+
/**
|
|
1681
|
+
* Common APIs related to markup generation.
|
|
1682
|
+
* @public
|
|
1683
|
+
*/
|
|
1684
|
+
const Markup = Object.freeze({
|
|
1685
|
+
/**
|
|
1686
|
+
* Creates a placeholder string suitable for marking out a location *within*
|
|
1687
|
+
* an attribute value or HTML content.
|
|
1688
|
+
* @param index - The directive index to create the placeholder for.
|
|
1689
|
+
* @remarks
|
|
1690
|
+
* Used internally by binding directives.
|
|
1691
|
+
*/
|
|
1692
|
+
interpolation: (id) => `${interpolationStart}${id}${interpolationEnd}`,
|
|
1693
|
+
/**
|
|
1694
|
+
* Creates a placeholder that manifests itself as an attribute on an
|
|
1695
|
+
* element.
|
|
1696
|
+
* @param attributeName - The name of the custom attribute.
|
|
1697
|
+
* @param index - The directive index to create the placeholder for.
|
|
1698
|
+
* @remarks
|
|
1699
|
+
* Used internally by attribute directives such as `ref`, `slotted`, and `children`.
|
|
1700
|
+
*/
|
|
1701
|
+
attribute: (id) => `${nextId()}="${interpolationStart}${id}${interpolationEnd}"`,
|
|
1702
|
+
/**
|
|
1703
|
+
* Creates a placeholder that manifests itself as a marker within the DOM structure.
|
|
1704
|
+
* @param index - The directive index to create the placeholder for.
|
|
1705
|
+
* @remarks
|
|
1706
|
+
* Used internally by structural directives such as `repeat`.
|
|
1707
|
+
*/
|
|
1708
|
+
comment: (id) => `<!--${interpolationStart}${id}${interpolationEnd}-->`,
|
|
1709
|
+
});
|
|
1710
|
+
/**
|
|
1711
|
+
* Common APIs related to content parsing.
|
|
1712
|
+
* @public
|
|
1713
|
+
*/
|
|
1714
|
+
const Parser = Object.freeze({
|
|
1715
|
+
/**
|
|
1716
|
+
* Parses text content or HTML attribute content, separating out the static strings
|
|
1717
|
+
* from the directives.
|
|
1718
|
+
* @param value - The content or attribute string to parse.
|
|
1719
|
+
* @param factories - A list of directives to search for in the string.
|
|
1720
|
+
* @returns A heterogeneous array of static strings interspersed with
|
|
1721
|
+
* directives or null if no directives are found in the string.
|
|
1722
|
+
*/
|
|
1723
|
+
parse(value, factories) {
|
|
1724
|
+
const parts = value.split(interpolationStart);
|
|
1725
|
+
if (parts.length === 1) {
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
const result = [];
|
|
1729
|
+
for (let i = 0, ii = parts.length; i < ii; ++i) {
|
|
1730
|
+
const current = parts[i];
|
|
1731
|
+
const index = current.indexOf(interpolationEnd);
|
|
1732
|
+
let literal;
|
|
1733
|
+
if (index === -1) {
|
|
1734
|
+
literal = current;
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
const factoryId = current.substring(0, index);
|
|
1738
|
+
result.push(factories[factoryId]);
|
|
1739
|
+
literal = current.substring(index + interpolationEndLength);
|
|
1740
|
+
}
|
|
1741
|
+
if (literal !== "") {
|
|
1742
|
+
result.push(literal);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return result;
|
|
1746
|
+
},
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
/**
|
|
1750
|
+
* Bridges between ViewBehaviors and HostBehaviors, enabling a host to
|
|
1751
|
+
* control ViewBehaviors.
|
|
1752
|
+
* @public
|
|
1753
|
+
*/
|
|
1754
|
+
const ViewBehaviorOrchestrator = Object.freeze({
|
|
1755
|
+
/**
|
|
1756
|
+
* Creates a ViewBehaviorOrchestrator.
|
|
1757
|
+
* @param source - The source to to associate behaviors with.
|
|
1758
|
+
* @returns A ViewBehaviorOrchestrator.
|
|
1759
|
+
*/
|
|
1760
|
+
create(source) {
|
|
1761
|
+
const behaviors = [];
|
|
1762
|
+
const targets = {};
|
|
1763
|
+
let unbindables = null;
|
|
1764
|
+
let isConnected = false;
|
|
1765
|
+
return {
|
|
1766
|
+
source,
|
|
1767
|
+
context: ExecutionContext.default,
|
|
1768
|
+
targets,
|
|
1769
|
+
get isBound() {
|
|
1770
|
+
return isConnected;
|
|
1771
|
+
},
|
|
1772
|
+
addBehaviorFactory(factory, target) {
|
|
1773
|
+
const nodeId = factory.nodeId || (factory.nodeId = nextId());
|
|
1774
|
+
factory.id || (factory.id = nextId());
|
|
1775
|
+
this.addTarget(nodeId, target);
|
|
1776
|
+
this.addBehavior(factory.createBehavior());
|
|
1777
|
+
},
|
|
1778
|
+
addTarget(nodeId, target) {
|
|
1779
|
+
targets[nodeId] = target;
|
|
1780
|
+
},
|
|
1781
|
+
addBehavior(behavior) {
|
|
1782
|
+
behaviors.push(behavior);
|
|
1783
|
+
if (isConnected) {
|
|
1784
|
+
behavior.bind(this);
|
|
1785
|
+
}
|
|
1786
|
+
},
|
|
1787
|
+
onUnbind(unbindable) {
|
|
1788
|
+
if (unbindables === null) {
|
|
1789
|
+
unbindables = [];
|
|
1790
|
+
}
|
|
1791
|
+
unbindables.push(unbindable);
|
|
1792
|
+
},
|
|
1793
|
+
connectedCallback(controller) {
|
|
1794
|
+
if (!isConnected) {
|
|
1795
|
+
isConnected = true;
|
|
1796
|
+
behaviors.forEach(x => x.bind(this));
|
|
1797
|
+
}
|
|
1798
|
+
},
|
|
1799
|
+
disconnectedCallback(controller) {
|
|
1800
|
+
if (isConnected) {
|
|
1801
|
+
isConnected = false;
|
|
1802
|
+
if (unbindables !== null) {
|
|
1803
|
+
unbindables.forEach(x => x.unbind(this));
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
},
|
|
1807
|
+
};
|
|
1808
|
+
},
|
|
1809
|
+
});
|
|
1810
|
+
const registry = createTypeRegistry();
|
|
1811
|
+
/**
|
|
1812
|
+
* Instructs the template engine to apply behavior to a node.
|
|
1813
|
+
* @public
|
|
1814
|
+
*/
|
|
1815
|
+
const HTMLDirective = Object.freeze({
|
|
1816
|
+
/**
|
|
1817
|
+
* Gets the directive definition associated with the instance.
|
|
1818
|
+
* @param instance - The directive instance to retrieve the definition for.
|
|
1819
|
+
*/
|
|
1820
|
+
getForInstance: registry.getForInstance,
|
|
1821
|
+
/**
|
|
1822
|
+
* Gets the directive definition associated with the specified type.
|
|
1823
|
+
* @param type - The directive type to retrieve the definition for.
|
|
1824
|
+
*/
|
|
1825
|
+
getByType: registry.getByType,
|
|
1826
|
+
/**
|
|
1827
|
+
* Defines an HTMLDirective based on the options.
|
|
1828
|
+
* @param type - The type to define as a directive.
|
|
1829
|
+
* @param options - Options that specify the directive's application.
|
|
1830
|
+
*/
|
|
1831
|
+
define(type, options) {
|
|
1832
|
+
options = options || {};
|
|
1833
|
+
options.type = type;
|
|
1834
|
+
registry.register(options);
|
|
1835
|
+
return type;
|
|
1836
|
+
},
|
|
1837
|
+
});
|
|
1838
|
+
/**
|
|
1839
|
+
* Decorator: Defines an HTMLDirective.
|
|
1840
|
+
* @param options - Provides options that specify the directive's application.
|
|
1841
|
+
* @public
|
|
1842
|
+
*/
|
|
1843
|
+
function htmlDirective(options) {
|
|
1844
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
1845
|
+
return function (type) {
|
|
1846
|
+
HTMLDirective.define(type, options);
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Captures a binding expression along with related information and capabilities.
|
|
1851
|
+
*
|
|
1852
|
+
* @public
|
|
1853
|
+
*/
|
|
1854
|
+
class Binding {
|
|
1855
|
+
/**
|
|
1856
|
+
* Creates a binding.
|
|
1857
|
+
* @param evaluate - Evaluates the binding.
|
|
1858
|
+
* @param isVolatile - Indicates whether the binding is volatile.
|
|
1859
|
+
*/
|
|
1860
|
+
constructor(evaluate, isVolatile = false) {
|
|
1861
|
+
this.evaluate = evaluate;
|
|
1862
|
+
this.isVolatile = isVolatile;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* The type of HTML aspect to target.
|
|
1867
|
+
* @public
|
|
1868
|
+
*/
|
|
1869
|
+
const Aspect = Object.freeze({
|
|
1870
|
+
/**
|
|
1871
|
+
* Not aspected.
|
|
1872
|
+
*/
|
|
1873
|
+
none: 0,
|
|
1874
|
+
/**
|
|
1875
|
+
* An attribute.
|
|
1876
|
+
*/
|
|
1877
|
+
attribute: 1,
|
|
1878
|
+
/**
|
|
1879
|
+
* A boolean attribute.
|
|
1880
|
+
*/
|
|
1881
|
+
booleanAttribute: 2,
|
|
1882
|
+
/**
|
|
1883
|
+
* A property.
|
|
1884
|
+
*/
|
|
1885
|
+
property: 3,
|
|
1886
|
+
/**
|
|
1887
|
+
* Content
|
|
1888
|
+
*/
|
|
1889
|
+
content: 4,
|
|
1890
|
+
/**
|
|
1891
|
+
* A token list.
|
|
1892
|
+
*/
|
|
1893
|
+
tokenList: 5,
|
|
1894
|
+
/**
|
|
1895
|
+
* An event.
|
|
1896
|
+
*/
|
|
1897
|
+
event: 6,
|
|
1898
|
+
/**
|
|
1899
|
+
*
|
|
1900
|
+
* @param directive - The directive to assign the aspect to.
|
|
1901
|
+
* @param value - The value to base the aspect determination on.
|
|
1902
|
+
* @remarks
|
|
1903
|
+
* If a falsy value is provided, then the content aspect will be assigned.
|
|
1904
|
+
*/
|
|
1905
|
+
assign(directive, value) {
|
|
1906
|
+
if (!value) {
|
|
1907
|
+
directive.aspectType = Aspect.content;
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
directive.sourceAspect = value;
|
|
1911
|
+
switch (value[0]) {
|
|
1912
|
+
case ":":
|
|
1913
|
+
directive.targetAspect = value.substring(1);
|
|
1914
|
+
directive.aspectType =
|
|
1915
|
+
directive.targetAspect === "classList"
|
|
1916
|
+
? Aspect.tokenList
|
|
1917
|
+
: Aspect.property;
|
|
1918
|
+
break;
|
|
1919
|
+
case "?":
|
|
1920
|
+
directive.targetAspect = value.substring(1);
|
|
1921
|
+
directive.aspectType = Aspect.booleanAttribute;
|
|
1922
|
+
break;
|
|
1923
|
+
case "@":
|
|
1924
|
+
directive.targetAspect = value.substring(1);
|
|
1925
|
+
directive.aspectType = Aspect.event;
|
|
1926
|
+
break;
|
|
1927
|
+
default:
|
|
1928
|
+
directive.targetAspect = value;
|
|
1929
|
+
directive.aspectType = Aspect.attribute;
|
|
1930
|
+
break;
|
|
1931
|
+
}
|
|
1932
|
+
},
|
|
1933
|
+
});
|
|
1934
|
+
/**
|
|
1935
|
+
* A base class used for attribute directives that don't need internal state.
|
|
1936
|
+
* @public
|
|
1937
|
+
*/
|
|
1938
|
+
class StatelessAttachedAttributeDirective {
|
|
1939
|
+
/**
|
|
1940
|
+
* Creates an instance of RefDirective.
|
|
1941
|
+
* @param options - The options to use in configuring the directive.
|
|
1942
|
+
*/
|
|
1943
|
+
constructor(options) {
|
|
1944
|
+
this.options = options;
|
|
1945
|
+
/**
|
|
1946
|
+
* The unique id of the factory.
|
|
1947
|
+
*/
|
|
1948
|
+
this.id = nextId();
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Creates a placeholder string based on the directive's index within the template.
|
|
1952
|
+
* @param index - The index of the directive within the template.
|
|
1953
|
+
* @remarks
|
|
1954
|
+
* Creates a custom attribute placeholder.
|
|
1955
|
+
*/
|
|
1956
|
+
createHTML(add) {
|
|
1957
|
+
return Markup.attribute(add(this));
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Creates a behavior.
|
|
1961
|
+
* @param targets - The targets available for behaviors to be attached to.
|
|
1962
|
+
*/
|
|
1963
|
+
createBehavior() {
|
|
1964
|
+
return this;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
const createInnerHTMLBinding = globalThis.TrustedHTML
|
|
1969
|
+
? (binding) => (s, c) => {
|
|
1970
|
+
const value = binding(s, c);
|
|
1971
|
+
if (value instanceof TrustedHTML) {
|
|
1972
|
+
return value;
|
|
1973
|
+
}
|
|
1974
|
+
throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
|
|
1975
|
+
}
|
|
1976
|
+
: (binding) => binding;
|
|
1977
|
+
class OnChangeBinding extends Binding {
|
|
1978
|
+
createObserver(_, subscriber) {
|
|
1979
|
+
return Observable.binding(this.evaluate, subscriber, this.isVolatile);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
class OneTimeBinding extends Binding {
|
|
1983
|
+
createObserver() {
|
|
1984
|
+
return this;
|
|
1985
|
+
}
|
|
1986
|
+
bind(controller) {
|
|
1987
|
+
return this.evaluate(controller.source, controller.context);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
function updateContent(target, aspect, value, controller) {
|
|
1991
|
+
// If there's no actual value, then this equates to the
|
|
1992
|
+
// empty string for the purposes of content bindings.
|
|
1993
|
+
if (value === null || value === undefined) {
|
|
1994
|
+
value = "";
|
|
1995
|
+
}
|
|
1996
|
+
// If the value has a "create" method, then it's a ContentTemplate.
|
|
1997
|
+
if (value.create) {
|
|
1998
|
+
target.textContent = "";
|
|
1999
|
+
let view = target.$fastView;
|
|
2000
|
+
// If there's no previous view that we might be able to
|
|
2001
|
+
// reuse then create a new view from the template.
|
|
2002
|
+
if (view === void 0) {
|
|
2003
|
+
view = value.create();
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
// If there is a previous view, but it wasn't created
|
|
2007
|
+
// from the same template as the new value, then we
|
|
2008
|
+
// need to remove the old view if it's still in the DOM
|
|
2009
|
+
// and create a new view from the template.
|
|
2010
|
+
if (target.$fastTemplate !== value) {
|
|
2011
|
+
if (view.isComposed) {
|
|
2012
|
+
view.remove();
|
|
2013
|
+
view.unbind();
|
|
2014
|
+
}
|
|
2015
|
+
view = value.create();
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
// It's possible that the value is the same as the previous template
|
|
2019
|
+
// and that there's actually no need to compose it.
|
|
2020
|
+
if (!view.isComposed) {
|
|
2021
|
+
view.isComposed = true;
|
|
2022
|
+
view.bind(controller.source, controller.context);
|
|
2023
|
+
view.insertBefore(target);
|
|
2024
|
+
target.$fastView = view;
|
|
2025
|
+
target.$fastTemplate = value;
|
|
2026
|
+
}
|
|
2027
|
+
else if (view.needsBindOnly) {
|
|
2028
|
+
view.needsBindOnly = false;
|
|
2029
|
+
view.bind(controller.source, controller.context);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
else {
|
|
2033
|
+
const view = target.$fastView;
|
|
2034
|
+
// If there is a view and it's currently composed into
|
|
2035
|
+
// the DOM, then we need to remove it.
|
|
2036
|
+
if (view !== void 0 && view.isComposed) {
|
|
2037
|
+
view.isComposed = false;
|
|
2038
|
+
view.remove();
|
|
2039
|
+
if (view.needsBindOnly) {
|
|
2040
|
+
view.needsBindOnly = false;
|
|
2041
|
+
}
|
|
2042
|
+
else {
|
|
2043
|
+
view.unbind();
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
target.textContent = value;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function updateTokenList(target, aspect, value) {
|
|
2050
|
+
var _a;
|
|
2051
|
+
const lookup = `${this.id}-t`;
|
|
2052
|
+
const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
|
|
2053
|
+
const versions = state.v;
|
|
2054
|
+
let currentVersion = state.c;
|
|
2055
|
+
const tokenList = target[aspect];
|
|
2056
|
+
// Add the classes, tracking the version at which they were added.
|
|
2057
|
+
if (value !== null && value !== undefined && value.length) {
|
|
2058
|
+
const names = value.split(/\s+/);
|
|
2059
|
+
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
2060
|
+
const currentName = names[i];
|
|
2061
|
+
if (currentName === "") {
|
|
2062
|
+
continue;
|
|
2063
|
+
}
|
|
2064
|
+
versions[currentName] = currentVersion;
|
|
2065
|
+
tokenList.add(currentName);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
state.v = currentVersion + 1;
|
|
2069
|
+
// If this is the first call to add classes, there's no need to remove old ones.
|
|
2070
|
+
if (currentVersion === 0) {
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
// Remove classes from the previous version.
|
|
2074
|
+
currentVersion -= 1;
|
|
2075
|
+
for (const name in versions) {
|
|
2076
|
+
if (versions[name] === currentVersion) {
|
|
2077
|
+
tokenList.remove(name);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
const setProperty = (t, a, v) => (t[a] = v);
|
|
2082
|
+
const eventTarget = () => void 0;
|
|
2083
|
+
/**
|
|
2084
|
+
* A directive that applies bindings.
|
|
2085
|
+
* @public
|
|
2086
|
+
*/
|
|
2087
|
+
class HTMLBindingDirective {
|
|
2088
|
+
/**
|
|
2089
|
+
* Creates an instance of HTMLBindingDirective.
|
|
2090
|
+
* @param dataBinding - The binding configuration to apply.
|
|
2091
|
+
*/
|
|
2092
|
+
constructor(dataBinding) {
|
|
2093
|
+
this.dataBinding = dataBinding;
|
|
2094
|
+
this.updateTarget = null;
|
|
2095
|
+
/**
|
|
2096
|
+
* The unique id of the factory.
|
|
2097
|
+
*/
|
|
2098
|
+
this.id = nextId();
|
|
2099
|
+
/**
|
|
2100
|
+
* The type of aspect to target.
|
|
2101
|
+
*/
|
|
2102
|
+
this.aspectType = Aspect.content;
|
|
2103
|
+
/** @internal */
|
|
2104
|
+
this.bind = this.bindDefault;
|
|
2105
|
+
this.data = `${this.id}-d`;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Creates HTML to be used within a template.
|
|
2109
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
2110
|
+
*/
|
|
2111
|
+
createHTML(add) {
|
|
2112
|
+
return Markup.interpolation(add(this));
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Creates a behavior.
|
|
2116
|
+
*/
|
|
2117
|
+
createBehavior() {
|
|
2118
|
+
if (this.updateTarget === null) {
|
|
2119
|
+
if (this.targetAspect === "innerHTML") {
|
|
2120
|
+
this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
|
|
2121
|
+
}
|
|
2122
|
+
switch (this.aspectType) {
|
|
2123
|
+
case 1:
|
|
2124
|
+
this.updateTarget = DOM.setAttribute;
|
|
2125
|
+
break;
|
|
2126
|
+
case 2:
|
|
2127
|
+
this.updateTarget = DOM.setBooleanAttribute;
|
|
2128
|
+
break;
|
|
2129
|
+
case 3:
|
|
2130
|
+
this.updateTarget = setProperty;
|
|
2131
|
+
break;
|
|
2132
|
+
case 4:
|
|
2133
|
+
this.bind = this.bindContent;
|
|
2134
|
+
this.updateTarget = updateContent;
|
|
2135
|
+
break;
|
|
2136
|
+
case 5:
|
|
2137
|
+
this.updateTarget = updateTokenList;
|
|
2138
|
+
break;
|
|
2139
|
+
case 6:
|
|
2140
|
+
this.bind = this.bindEvent;
|
|
2141
|
+
this.updateTarget = eventTarget;
|
|
2142
|
+
break;
|
|
2143
|
+
default:
|
|
2144
|
+
throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
return this;
|
|
2148
|
+
}
|
|
2149
|
+
/** @internal */
|
|
2150
|
+
bindDefault(controller) {
|
|
2151
|
+
var _a;
|
|
2152
|
+
const target = controller.targets[this.nodeId];
|
|
2153
|
+
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
2154
|
+
observer.target = target;
|
|
2155
|
+
observer.controller = controller;
|
|
2156
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
2157
|
+
if (this.updateTarget === updateContent) {
|
|
2158
|
+
controller.onUnbind(this);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
/** @internal */
|
|
2162
|
+
bindContent(controller) {
|
|
2163
|
+
this.bindDefault(controller);
|
|
2164
|
+
controller.onUnbind(this);
|
|
2165
|
+
}
|
|
2166
|
+
/** @internal */
|
|
2167
|
+
bindEvent(controller) {
|
|
2168
|
+
const target = controller.targets[this.nodeId];
|
|
2169
|
+
target[this.data] = controller;
|
|
2170
|
+
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
2171
|
+
}
|
|
2172
|
+
/** @internal */
|
|
2173
|
+
unbind(controller) {
|
|
2174
|
+
const target = controller.targets[this.nodeId];
|
|
2175
|
+
const view = target.$fastView;
|
|
2176
|
+
if (view !== void 0 && view.isComposed) {
|
|
2177
|
+
view.unbind();
|
|
2178
|
+
view.needsBindOnly = true;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
/** @internal */
|
|
2182
|
+
handleEvent(event) {
|
|
2183
|
+
const target = event.currentTarget;
|
|
2184
|
+
ExecutionContext.setEvent(event);
|
|
2185
|
+
const controller = target[this.data];
|
|
2186
|
+
const result = this.dataBinding.evaluate(controller.source, controller.context);
|
|
2187
|
+
ExecutionContext.setEvent(null);
|
|
2188
|
+
if (result !== true) {
|
|
2189
|
+
event.preventDefault();
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
/** @internal */
|
|
2193
|
+
handleChange(binding, observer) {
|
|
2194
|
+
const target = observer.target;
|
|
2195
|
+
const controller = observer.controller;
|
|
2196
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
2200
|
+
/**
|
|
2201
|
+
* Creates an standard binding.
|
|
2202
|
+
* @param binding - The binding to refresh when changed.
|
|
2203
|
+
* @param isVolatile - Indicates whether the binding is volatile or not.
|
|
2204
|
+
* @returns A binding configuration.
|
|
2205
|
+
* @public
|
|
2206
|
+
*/
|
|
2207
|
+
function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
|
|
2208
|
+
return new OnChangeBinding(binding, isVolatile);
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Creates a one time binding
|
|
2212
|
+
* @param binding - The binding to refresh when signaled.
|
|
2213
|
+
* @returns A binding configuration.
|
|
2214
|
+
* @public
|
|
2215
|
+
*/
|
|
2216
|
+
function oneTime(binding) {
|
|
2217
|
+
return new OneTimeBinding(binding);
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Creates an event listener binding.
|
|
2221
|
+
* @param binding - The binding to invoke when the event is raised.
|
|
2222
|
+
* @param options - Event listener options.
|
|
2223
|
+
* @returns A binding configuration.
|
|
2224
|
+
* @public
|
|
2225
|
+
*/
|
|
2226
|
+
function listener(binding, options) {
|
|
2227
|
+
const config = new OnChangeBinding(binding, false);
|
|
2228
|
+
config.options = options;
|
|
2229
|
+
return config;
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Normalizes the input value into a binding.
|
|
2233
|
+
* @param value - The value to create the default binding for.
|
|
2234
|
+
* @returns A binding configuration for the provided value.
|
|
2235
|
+
* @public
|
|
2236
|
+
*/
|
|
2237
|
+
function normalizeBinding(value) {
|
|
2238
|
+
return isFunction(value)
|
|
2239
|
+
? bind(value)
|
|
2240
|
+
: value instanceof Binding
|
|
2241
|
+
? value
|
|
2242
|
+
: oneTime(() => value);
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
function removeNodeSequence(firstNode, lastNode) {
|
|
2246
|
+
const parent = firstNode.parentNode;
|
|
2247
|
+
let current = firstNode;
|
|
2248
|
+
let next;
|
|
2249
|
+
while (current !== lastNode) {
|
|
2250
|
+
next = current.nextSibling;
|
|
2251
|
+
parent.removeChild(current);
|
|
2252
|
+
current = next;
|
|
2253
|
+
}
|
|
2254
|
+
parent.removeChild(lastNode);
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* The standard View implementation, which also implements ElementView and SyntheticView.
|
|
2258
|
+
* @public
|
|
2259
|
+
*/
|
|
2260
|
+
class HTMLView {
|
|
2261
|
+
/**
|
|
2262
|
+
* Constructs an instance of HTMLView.
|
|
2263
|
+
* @param fragment - The html fragment that contains the nodes for this view.
|
|
2264
|
+
* @param behaviors - The behaviors to be applied to this view.
|
|
2265
|
+
*/
|
|
2266
|
+
constructor(fragment, factories, targets) {
|
|
2267
|
+
this.fragment = fragment;
|
|
2268
|
+
this.factories = factories;
|
|
2269
|
+
this.targets = targets;
|
|
2270
|
+
this.behaviors = null;
|
|
2271
|
+
this.unbindables = [];
|
|
2272
|
+
/**
|
|
2273
|
+
* The data that the view is bound to.
|
|
2274
|
+
*/
|
|
2275
|
+
this.source = null;
|
|
2276
|
+
/**
|
|
2277
|
+
* Indicates whether the controller is bound.
|
|
2278
|
+
*/
|
|
2279
|
+
this.isBound = false;
|
|
2280
|
+
/**
|
|
2281
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
2282
|
+
*/
|
|
2283
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
2284
|
+
/**
|
|
2285
|
+
* The execution context the view is running within.
|
|
2286
|
+
*/
|
|
2287
|
+
this.context = this;
|
|
2288
|
+
/**
|
|
2289
|
+
* The index of the current item within a repeat context.
|
|
2290
|
+
*/
|
|
2291
|
+
this.index = 0;
|
|
2292
|
+
/**
|
|
2293
|
+
* The length of the current collection within a repeat context.
|
|
2294
|
+
*/
|
|
2295
|
+
this.length = 0;
|
|
2296
|
+
this.firstChild = fragment.firstChild;
|
|
2297
|
+
this.lastChild = fragment.lastChild;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* The current event within an event handler.
|
|
2301
|
+
*/
|
|
2302
|
+
get event() {
|
|
2303
|
+
return ExecutionContext.getEvent();
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Indicates whether the current item within a repeat context
|
|
2307
|
+
* has an even index.
|
|
2308
|
+
*/
|
|
2309
|
+
get isEven() {
|
|
2310
|
+
return this.index % 2 === 0;
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Indicates whether the current item within a repeat context
|
|
2314
|
+
* has an odd index.
|
|
2315
|
+
*/
|
|
2316
|
+
get isOdd() {
|
|
2317
|
+
return this.index % 2 !== 0;
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Indicates whether the current item within a repeat context
|
|
2321
|
+
* is the first item in the collection.
|
|
2322
|
+
*/
|
|
2323
|
+
get isFirst() {
|
|
2324
|
+
return this.index === 0;
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Indicates whether the current item within a repeat context
|
|
2328
|
+
* is somewhere in the middle of the collection.
|
|
2329
|
+
*/
|
|
2330
|
+
get isInMiddle() {
|
|
2331
|
+
return !this.isFirst && !this.isLast;
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Indicates whether the current item within a repeat context
|
|
2335
|
+
* is the last item in the collection.
|
|
2336
|
+
*/
|
|
2337
|
+
get isLast() {
|
|
2338
|
+
return this.index === this.length - 1;
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Returns the typed event detail of a custom event.
|
|
2342
|
+
*/
|
|
2343
|
+
eventDetail() {
|
|
2344
|
+
return this.event.detail;
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Returns the typed event target of the event.
|
|
2348
|
+
*/
|
|
2349
|
+
eventTarget() {
|
|
2350
|
+
return this.event.target;
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Appends the view's DOM nodes to the referenced node.
|
|
2354
|
+
* @param node - The parent node to append the view's DOM nodes to.
|
|
2355
|
+
*/
|
|
2356
|
+
appendTo(node) {
|
|
2357
|
+
node.appendChild(this.fragment);
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Inserts the view's DOM nodes before the referenced node.
|
|
2361
|
+
* @param node - The node to insert the view's DOM before.
|
|
2362
|
+
*/
|
|
2363
|
+
insertBefore(node) {
|
|
2364
|
+
if (this.fragment.hasChildNodes()) {
|
|
2365
|
+
node.parentNode.insertBefore(this.fragment, node);
|
|
2366
|
+
}
|
|
2367
|
+
else {
|
|
2368
|
+
const end = this.lastChild;
|
|
2369
|
+
if (node.previousSibling === end)
|
|
2370
|
+
return;
|
|
2371
|
+
const parentNode = node.parentNode;
|
|
2372
|
+
let current = this.firstChild;
|
|
2373
|
+
let next;
|
|
2374
|
+
while (current !== end) {
|
|
2375
|
+
next = current.nextSibling;
|
|
2376
|
+
parentNode.insertBefore(current, node);
|
|
2377
|
+
current = next;
|
|
2378
|
+
}
|
|
2379
|
+
parentNode.insertBefore(end, node);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Removes the view's DOM nodes.
|
|
2384
|
+
* The nodes are not disposed and the view can later be re-inserted.
|
|
2385
|
+
*/
|
|
2386
|
+
remove() {
|
|
2387
|
+
const fragment = this.fragment;
|
|
2388
|
+
const end = this.lastChild;
|
|
2389
|
+
let current = this.firstChild;
|
|
2390
|
+
let next;
|
|
2391
|
+
while (current !== end) {
|
|
2392
|
+
next = current.nextSibling;
|
|
2393
|
+
fragment.appendChild(current);
|
|
2394
|
+
current = next;
|
|
2395
|
+
}
|
|
2396
|
+
fragment.appendChild(end);
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
|
|
2400
|
+
* Once a view has been disposed, it cannot be inserted or bound again.
|
|
2401
|
+
*/
|
|
2402
|
+
dispose() {
|
|
2403
|
+
removeNodeSequence(this.firstChild, this.lastChild);
|
|
2404
|
+
this.unbind();
|
|
2405
|
+
}
|
|
2406
|
+
onUnbind(behavior) {
|
|
2407
|
+
this.unbindables.push(behavior);
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Binds a view's behaviors to its binding source.
|
|
2411
|
+
* @param source - The binding source for the view's binding behaviors.
|
|
2412
|
+
* @param context - The execution context to run the behaviors within.
|
|
2413
|
+
*/
|
|
2414
|
+
bind(source, context = this) {
|
|
2415
|
+
if (this.source === source) {
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
let behaviors = this.behaviors;
|
|
2419
|
+
if (behaviors === null) {
|
|
2420
|
+
this.source = source;
|
|
2421
|
+
this.context = context;
|
|
2422
|
+
this.behaviors = behaviors = new Array(this.factories.length);
|
|
2423
|
+
const factories = this.factories;
|
|
2424
|
+
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
2425
|
+
const behavior = factories[i].createBehavior();
|
|
2426
|
+
behavior.bind(this);
|
|
2427
|
+
behaviors[i] = behavior;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
else {
|
|
2431
|
+
if (this.source !== null) {
|
|
2432
|
+
this.evaluateUnbindables();
|
|
2433
|
+
}
|
|
2434
|
+
this.isBound = false;
|
|
2435
|
+
this.source = source;
|
|
2436
|
+
this.context = context;
|
|
2437
|
+
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
2438
|
+
behaviors[i].bind(this);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
this.isBound = true;
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Unbinds a view's behaviors from its binding source.
|
|
2445
|
+
*/
|
|
2446
|
+
unbind() {
|
|
2447
|
+
if (!this.isBound || this.source === null) {
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
this.evaluateUnbindables();
|
|
2451
|
+
this.source = null;
|
|
2452
|
+
this.context = this;
|
|
2453
|
+
this.isBound = false;
|
|
2454
|
+
}
|
|
2455
|
+
evaluateUnbindables() {
|
|
2456
|
+
const unbindables = this.unbindables;
|
|
2457
|
+
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
2458
|
+
unbindables[i].unbind(this);
|
|
2459
|
+
}
|
|
2460
|
+
unbindables.length = 0;
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Efficiently disposes of a contiguous range of synthetic view instances.
|
|
2464
|
+
* @param views - A contiguous range of views to be disposed.
|
|
2465
|
+
*/
|
|
2466
|
+
static disposeContiguousBatch(views) {
|
|
2467
|
+
if (views.length === 0) {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
|
|
2471
|
+
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
2472
|
+
views[i].unbind();
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
Observable.defineProperty(HTMLView.prototype, "index");
|
|
2477
|
+
Observable.defineProperty(HTMLView.prototype, "length");
|
|
2478
|
+
|
|
2479
|
+
const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
|
|
2480
|
+
const descriptorCache = {};
|
|
2481
|
+
// used to prevent creating lots of objects just to track node and index while compiling
|
|
2482
|
+
const next = {
|
|
2483
|
+
index: 0,
|
|
2484
|
+
node: null,
|
|
2485
|
+
};
|
|
2486
|
+
function tryWarn(name) {
|
|
2487
|
+
if (!name.startsWith("fast-")) {
|
|
2488
|
+
FAST.warn(1204 /* Message.hostBindingWithoutHost */, { name });
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
const warningHost = new Proxy(document.createElement("div"), {
|
|
2492
|
+
get(target, property) {
|
|
2493
|
+
tryWarn(property);
|
|
2494
|
+
const value = Reflect.get(target, property);
|
|
2495
|
+
return isFunction(value) ? value.bind(target) : value;
|
|
2496
|
+
},
|
|
2497
|
+
set(target, property, value) {
|
|
2498
|
+
tryWarn(property);
|
|
2499
|
+
return Reflect.set(target, property, value);
|
|
2500
|
+
},
|
|
2501
|
+
});
|
|
2502
|
+
class CompilationContext {
|
|
2503
|
+
constructor(fragment, directives) {
|
|
2504
|
+
this.fragment = fragment;
|
|
2505
|
+
this.directives = directives;
|
|
2506
|
+
this.proto = null;
|
|
2507
|
+
this.nodeIds = new Set();
|
|
2508
|
+
this.descriptors = {};
|
|
2509
|
+
this.factories = [];
|
|
2510
|
+
}
|
|
2511
|
+
addFactory(factory, parentId, nodeId, targetIndex) {
|
|
2512
|
+
if (!this.nodeIds.has(nodeId)) {
|
|
2513
|
+
this.nodeIds.add(nodeId);
|
|
2514
|
+
this.addTargetDescriptor(parentId, nodeId, targetIndex);
|
|
2515
|
+
}
|
|
2516
|
+
factory.nodeId = nodeId;
|
|
2517
|
+
this.factories.push(factory);
|
|
2518
|
+
}
|
|
2519
|
+
freeze() {
|
|
2520
|
+
this.proto = Object.create(null, this.descriptors);
|
|
2521
|
+
return this;
|
|
2522
|
+
}
|
|
2523
|
+
addTargetDescriptor(parentId, targetId, targetIndex) {
|
|
2524
|
+
const descriptors = this.descriptors;
|
|
2525
|
+
if (targetId === "r" || // root
|
|
2526
|
+
targetId === "h" || // host
|
|
2527
|
+
descriptors[targetId]) {
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
if (!descriptors[parentId]) {
|
|
2531
|
+
const index = parentId.lastIndexOf(".");
|
|
2532
|
+
const grandparentId = parentId.substring(0, index);
|
|
2533
|
+
const childIndex = parseInt(parentId.substring(index + 1));
|
|
2534
|
+
this.addTargetDescriptor(grandparentId, parentId, childIndex);
|
|
2535
|
+
}
|
|
2536
|
+
let descriptor = descriptorCache[targetId];
|
|
2537
|
+
if (!descriptor) {
|
|
2538
|
+
const field = `_${targetId}`;
|
|
2539
|
+
descriptorCache[targetId] = descriptor = {
|
|
2540
|
+
get() {
|
|
2541
|
+
var _a;
|
|
2542
|
+
return ((_a = this[field]) !== null && _a !== void 0 ? _a : (this[field] = this[parentId].childNodes[targetIndex]));
|
|
2543
|
+
},
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
descriptors[targetId] = descriptor;
|
|
2547
|
+
}
|
|
2548
|
+
createView(hostBindingTarget) {
|
|
2549
|
+
const fragment = this.fragment.cloneNode(true);
|
|
2550
|
+
const targets = Object.create(this.proto);
|
|
2551
|
+
targets.r = fragment;
|
|
2552
|
+
targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
|
|
2553
|
+
for (const id of this.nodeIds) {
|
|
2554
|
+
targets[id]; // trigger locator
|
|
2555
|
+
}
|
|
2556
|
+
return new HTMLView(fragment, this.factories, targets);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBasicValues = false) {
|
|
2560
|
+
const attributes = node.attributes;
|
|
2561
|
+
const directives = context.directives;
|
|
2562
|
+
for (let i = 0, ii = attributes.length; i < ii; ++i) {
|
|
2563
|
+
const attr = attributes[i];
|
|
2564
|
+
const attrValue = attr.value;
|
|
2565
|
+
const parseResult = Parser.parse(attrValue, directives);
|
|
2566
|
+
let result = null;
|
|
2567
|
+
if (parseResult === null) {
|
|
2568
|
+
if (includeBasicValues) {
|
|
2569
|
+
result = new HTMLBindingDirective(oneTime(() => attrValue));
|
|
2570
|
+
Aspect.assign(result, attr.name);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
else {
|
|
2574
|
+
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2575
|
+
result = Compiler.aggregate(parseResult);
|
|
2576
|
+
}
|
|
2577
|
+
if (result !== null) {
|
|
2578
|
+
node.removeAttributeNode(attr);
|
|
2579
|
+
i--;
|
|
2580
|
+
ii--;
|
|
2581
|
+
context.addFactory(result, parentId, nodeId, nodeIndex);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
function compileContent(context, node, parentId, nodeId, nodeIndex) {
|
|
2586
|
+
const parseResult = Parser.parse(node.textContent, context.directives);
|
|
2587
|
+
if (parseResult === null) {
|
|
2588
|
+
next.node = node.nextSibling;
|
|
2589
|
+
next.index = nodeIndex + 1;
|
|
2590
|
+
return next;
|
|
2591
|
+
}
|
|
2592
|
+
let currentNode;
|
|
2593
|
+
let lastNode = (currentNode = node);
|
|
2594
|
+
for (let i = 0, ii = parseResult.length; i < ii; ++i) {
|
|
2595
|
+
const currentPart = parseResult[i];
|
|
2596
|
+
if (i !== 0) {
|
|
2597
|
+
nodeIndex++;
|
|
2598
|
+
nodeId = targetIdFrom(parentId, nodeIndex);
|
|
2599
|
+
currentNode = lastNode.parentNode.insertBefore(document.createTextNode(""), lastNode.nextSibling);
|
|
2600
|
+
}
|
|
2601
|
+
if (isString(currentPart)) {
|
|
2602
|
+
currentNode.textContent = currentPart;
|
|
2603
|
+
}
|
|
2604
|
+
else {
|
|
2605
|
+
currentNode.textContent = " ";
|
|
2606
|
+
Aspect.assign(currentPart);
|
|
2607
|
+
context.addFactory(currentPart, parentId, nodeId, nodeIndex);
|
|
2608
|
+
}
|
|
2609
|
+
lastNode = currentNode;
|
|
2610
|
+
}
|
|
2611
|
+
next.index = nodeIndex + 1;
|
|
2612
|
+
next.node = lastNode.nextSibling;
|
|
2613
|
+
return next;
|
|
2614
|
+
}
|
|
2615
|
+
function compileChildren(context, parent, parentId) {
|
|
2616
|
+
let nodeIndex = 0;
|
|
2617
|
+
let childNode = parent.firstChild;
|
|
2618
|
+
while (childNode) {
|
|
2619
|
+
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2620
|
+
const result = compileNode(context, parentId, childNode, nodeIndex);
|
|
2621
|
+
childNode = result.node;
|
|
2622
|
+
nodeIndex = result.index;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function compileNode(context, parentId, node, nodeIndex) {
|
|
2626
|
+
const nodeId = targetIdFrom(parentId, nodeIndex);
|
|
2627
|
+
switch (node.nodeType) {
|
|
2628
|
+
case 1: // element node
|
|
2629
|
+
compileAttributes(context, parentId, node, nodeId, nodeIndex);
|
|
2630
|
+
compileChildren(context, node, nodeId);
|
|
2631
|
+
break;
|
|
2632
|
+
case 3: // text node
|
|
2633
|
+
return compileContent(context, node, parentId, nodeId, nodeIndex);
|
|
2634
|
+
case 8: // comment
|
|
2635
|
+
const parts = Parser.parse(node.data, context.directives);
|
|
2636
|
+
if (parts !== null) {
|
|
2637
|
+
context.addFactory(
|
|
2638
|
+
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
2639
|
+
Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
|
|
2640
|
+
}
|
|
2641
|
+
break;
|
|
2642
|
+
}
|
|
2643
|
+
next.index = nodeIndex + 1;
|
|
2644
|
+
next.node = node.nextSibling;
|
|
2645
|
+
return next;
|
|
2646
|
+
}
|
|
2647
|
+
function isMarker(node, directives) {
|
|
2648
|
+
return (node &&
|
|
2649
|
+
node.nodeType == 8 &&
|
|
2650
|
+
Parser.parse(node.data, directives) !== null);
|
|
2651
|
+
}
|
|
2652
|
+
const templateTag = "TEMPLATE";
|
|
2653
|
+
const policyOptions = { createHTML: html => html };
|
|
2654
|
+
let htmlPolicy = globalThis.trustedTypes
|
|
2655
|
+
? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
|
|
2656
|
+
: policyOptions;
|
|
2657
|
+
const fastHTMLPolicy = htmlPolicy;
|
|
2658
|
+
/**
|
|
2659
|
+
* Common APIs related to compilation.
|
|
2660
|
+
* @public
|
|
2661
|
+
*/
|
|
2662
|
+
const Compiler = {
|
|
2663
|
+
/**
|
|
2664
|
+
* Sets the HTML trusted types policy used by the compiler.
|
|
2665
|
+
* @param policy - The policy to set for HTML.
|
|
2666
|
+
* @remarks
|
|
2667
|
+
* This API can only be called once, for security reasons. It should be
|
|
2668
|
+
* called by the application developer at the start of their program.
|
|
2669
|
+
*/
|
|
2670
|
+
setHTMLPolicy(policy) {
|
|
2671
|
+
if (htmlPolicy !== fastHTMLPolicy) {
|
|
2672
|
+
throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
|
|
2673
|
+
}
|
|
2674
|
+
htmlPolicy = policy;
|
|
2675
|
+
},
|
|
2676
|
+
/**
|
|
2677
|
+
* Compiles a template and associated directives into a compilation
|
|
2678
|
+
* result which can be used to create views.
|
|
2679
|
+
* @param html - The html string or template element to compile.
|
|
2680
|
+
* @param directives - The directives referenced by the template.
|
|
2681
|
+
* @remarks
|
|
2682
|
+
* The template that is provided for compilation is altered in-place
|
|
2683
|
+
* and cannot be compiled again. If the original template must be preserved,
|
|
2684
|
+
* it is recommended that you clone the original and pass the clone to this API.
|
|
2685
|
+
* @public
|
|
2686
|
+
*/
|
|
2687
|
+
compile(html, directives) {
|
|
2688
|
+
let template;
|
|
2689
|
+
if (isString(html)) {
|
|
2690
|
+
template = document.createElement(templateTag);
|
|
2691
|
+
template.innerHTML = htmlPolicy.createHTML(html);
|
|
2692
|
+
const fec = template.content.firstElementChild;
|
|
2693
|
+
if (fec !== null && fec.tagName === templateTag) {
|
|
2694
|
+
template = fec;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
else {
|
|
2698
|
+
template = html;
|
|
2699
|
+
}
|
|
2700
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
|
|
2701
|
+
const fragment = document.adoptNode(template.content);
|
|
2702
|
+
const context = new CompilationContext(fragment, directives);
|
|
2703
|
+
compileAttributes(context, "", template, /* host */ "h", 0, true);
|
|
2704
|
+
if (
|
|
2705
|
+
// If the first node in a fragment is a marker, that means it's an unstable first node,
|
|
2706
|
+
// because something like a when, repeat, etc. could add nodes before the marker.
|
|
2707
|
+
// To mitigate this, we insert a stable first node. However, if we insert a node,
|
|
2708
|
+
// that will alter the result of the TreeWalker. So, we also need to offset the target index.
|
|
2709
|
+
isMarker(fragment.firstChild, directives) ||
|
|
2710
|
+
// Or if there is only one node and a directive, it means the template's content
|
|
2711
|
+
// is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
|
|
2712
|
+
// the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
|
|
2713
|
+
(fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
|
|
2714
|
+
fragment.insertBefore(document.createComment(""), fragment.firstChild);
|
|
2715
|
+
}
|
|
2716
|
+
compileChildren(context, fragment, /* root */ "r");
|
|
2717
|
+
next.node = null; // prevent leaks
|
|
2718
|
+
return context.freeze();
|
|
2719
|
+
},
|
|
2720
|
+
/**
|
|
2721
|
+
* Sets the default compilation strategy that will be used by the ViewTemplate whenever
|
|
2722
|
+
* it needs to compile a view preprocessed with the html template function.
|
|
2723
|
+
* @param strategy - The compilation strategy to use when compiling templates.
|
|
2724
|
+
*/
|
|
2725
|
+
setDefaultStrategy(strategy) {
|
|
2726
|
+
this.compile = strategy;
|
|
2727
|
+
},
|
|
2728
|
+
/**
|
|
2729
|
+
* Aggregates an array of strings and directives into a single directive.
|
|
2730
|
+
* @param parts - A heterogeneous array of static strings interspersed with
|
|
2731
|
+
* directives.
|
|
2732
|
+
* @returns A single inline directive that aggregates the behavior of all the parts.
|
|
2733
|
+
*/
|
|
2734
|
+
aggregate(parts) {
|
|
2735
|
+
if (parts.length === 1) {
|
|
2736
|
+
return parts[0];
|
|
2737
|
+
}
|
|
2738
|
+
let sourceAspect;
|
|
2739
|
+
let binding;
|
|
2740
|
+
let isVolatile = false;
|
|
2741
|
+
const partCount = parts.length;
|
|
2742
|
+
const finalParts = parts.map((x) => {
|
|
2743
|
+
if (isString(x)) {
|
|
2744
|
+
return () => x;
|
|
2745
|
+
}
|
|
2746
|
+
sourceAspect = x.sourceAspect || sourceAspect;
|
|
2747
|
+
binding = x.dataBinding || binding;
|
|
2748
|
+
isVolatile = isVolatile || x.dataBinding.isVolatile;
|
|
2749
|
+
return x.dataBinding.evaluate;
|
|
2750
|
+
});
|
|
2751
|
+
const expression = (scope, context) => {
|
|
2752
|
+
let output = "";
|
|
2753
|
+
for (let i = 0; i < partCount; ++i) {
|
|
2754
|
+
output += finalParts[i](scope, context);
|
|
2755
|
+
}
|
|
2756
|
+
return output;
|
|
2757
|
+
};
|
|
2758
|
+
binding.evaluate = expression;
|
|
2759
|
+
binding.isVolatile = isVolatile;
|
|
2760
|
+
const directive = new HTMLBindingDirective(binding);
|
|
2761
|
+
Aspect.assign(directive, sourceAspect);
|
|
2762
|
+
return directive;
|
|
2763
|
+
},
|
|
2764
|
+
};
|
|
2765
|
+
|
|
2766
|
+
/**
|
|
2767
|
+
* A template capable of creating HTMLView instances or rendering directly to DOM.
|
|
2768
|
+
* @public
|
|
2769
|
+
*/
|
|
2770
|
+
class ViewTemplate {
|
|
2771
|
+
/**
|
|
2772
|
+
* Creates an instance of ViewTemplate.
|
|
2773
|
+
* @param html - The html representing what this template will instantiate, including placeholders for directives.
|
|
2774
|
+
* @param factories - The directives that will be connected to placeholders in the html.
|
|
2775
|
+
*/
|
|
2776
|
+
constructor(html, factories) {
|
|
2777
|
+
this.result = null;
|
|
2778
|
+
this.html = html;
|
|
2779
|
+
this.factories = factories;
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Creates an HTMLView instance based on this template definition.
|
|
2783
|
+
* @param hostBindingTarget - The element that host behaviors will be bound to.
|
|
2784
|
+
*/
|
|
2785
|
+
create(hostBindingTarget) {
|
|
2786
|
+
if (this.result === null) {
|
|
2787
|
+
this.result = Compiler.compile(this.html, this.factories);
|
|
2788
|
+
}
|
|
2789
|
+
return this.result.createView(hostBindingTarget);
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
|
|
2793
|
+
* @param source - The data source to bind the template to.
|
|
2794
|
+
* @param host - The Element where the template will be rendered.
|
|
2795
|
+
* @param hostBindingTarget - An HTML element to target the host bindings at if different from the
|
|
2796
|
+
* host that the template is being attached to.
|
|
2797
|
+
*/
|
|
2798
|
+
render(source, host, hostBindingTarget) {
|
|
2799
|
+
const view = this.create(hostBindingTarget);
|
|
2800
|
+
view.bind(source);
|
|
2801
|
+
view.appendTo(host);
|
|
2802
|
+
return view;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
// Much thanks to LitHTML for working this out!
|
|
2806
|
+
const lastAttributeNameRegex =
|
|
2807
|
+
/* eslint-disable-next-line no-control-regex */
|
|
2808
|
+
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
2809
|
+
function createAspectedHTML(value, prevString, add) {
|
|
2810
|
+
const match = lastAttributeNameRegex.exec(prevString);
|
|
2811
|
+
if (match !== null) {
|
|
2812
|
+
Aspect.assign(value, match[2]);
|
|
2813
|
+
}
|
|
2814
|
+
return value.createHTML(add);
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Transforms a template literal string into a ViewTemplate.
|
|
2818
|
+
* @param strings - The string fragments that are interpolated with the values.
|
|
2819
|
+
* @param values - The values that are interpolated with the string fragments.
|
|
2820
|
+
* @remarks
|
|
2821
|
+
* The html helper supports interpolation of strings, numbers, binding expressions,
|
|
2822
|
+
* other template instances, and Directive instances.
|
|
2823
|
+
* @public
|
|
2824
|
+
*/
|
|
2825
|
+
function html(strings, ...values) {
|
|
2826
|
+
let html = "";
|
|
2827
|
+
const factories = Object.create(null);
|
|
2828
|
+
const add = (factory) => {
|
|
2829
|
+
var _a;
|
|
2830
|
+
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
2831
|
+
factories[id] = factory;
|
|
2832
|
+
return id;
|
|
2833
|
+
};
|
|
2834
|
+
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
2835
|
+
const currentString = strings[i];
|
|
2836
|
+
const currentValue = values[i];
|
|
2837
|
+
let definition;
|
|
2838
|
+
html += currentString;
|
|
2839
|
+
if (isFunction(currentValue)) {
|
|
2840
|
+
html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
|
|
2841
|
+
}
|
|
2842
|
+
else if (isString(currentValue)) {
|
|
2843
|
+
const match = lastAttributeNameRegex.exec(currentString);
|
|
2844
|
+
if (match !== null) {
|
|
2845
|
+
const directive = new HTMLBindingDirective(oneTime(() => currentValue));
|
|
2846
|
+
Aspect.assign(directive, match[2]);
|
|
2847
|
+
html += directive.createHTML(add);
|
|
2848
|
+
}
|
|
2849
|
+
else {
|
|
2850
|
+
html += currentValue;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
else if (currentValue instanceof Binding) {
|
|
2854
|
+
html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
|
|
2855
|
+
}
|
|
2856
|
+
else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
|
|
2857
|
+
html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
|
|
2858
|
+
}
|
|
2859
|
+
else {
|
|
2860
|
+
if (definition.aspected) {
|
|
2861
|
+
html += createAspectedHTML(currentValue, currentString, add);
|
|
2862
|
+
}
|
|
2863
|
+
else {
|
|
2864
|
+
html += currentValue.createHTML(add);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
return new ViewTemplate(html + strings[strings.length - 1], factories);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
/**
|
|
2872
|
+
* The runtime behavior for template references.
|
|
2873
|
+
* @public
|
|
2874
|
+
*/
|
|
2875
|
+
class RefDirective extends StatelessAttachedAttributeDirective {
|
|
2876
|
+
/**
|
|
2877
|
+
* Bind this behavior.
|
|
2878
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2879
|
+
*/
|
|
2880
|
+
bind(controller) {
|
|
2881
|
+
controller.source[this.options] = controller.targets[this.nodeId];
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
HTMLDirective.define(RefDirective);
|
|
2885
|
+
/**
|
|
2886
|
+
* A directive that observes the updates a property with a reference to the element.
|
|
2887
|
+
* @param propertyName - The name of the property to assign the reference to.
|
|
2888
|
+
* @public
|
|
2889
|
+
*/
|
|
2890
|
+
const ref = (propertyName) => new RefDirective(propertyName);
|
|
2891
|
+
|
|
2892
|
+
/**
|
|
2893
|
+
* A directive that enables basic conditional rendering in a template.
|
|
2894
|
+
* @param condition - The condition to test for rendering.
|
|
2895
|
+
* @param templateOrTemplateBinding - The template or a binding that gets
|
|
2896
|
+
* the template to render when the condition is true.
|
|
2897
|
+
* @public
|
|
2898
|
+
*/
|
|
2899
|
+
function when(condition, templateOrTemplateBinding) {
|
|
2900
|
+
const dataBinding = isFunction(condition) ? condition : () => condition;
|
|
2901
|
+
const templateBinding = isFunction(templateOrTemplateBinding)
|
|
2902
|
+
? templateOrTemplateBinding
|
|
2903
|
+
: () => templateOrTemplateBinding;
|
|
2904
|
+
return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
const defaultRepeatOptions = Object.freeze({
|
|
2908
|
+
positioning: false,
|
|
2909
|
+
recycle: true,
|
|
2910
|
+
});
|
|
2911
|
+
function bindWithoutPositioning(view, items, index, controller) {
|
|
2912
|
+
view.context.parent = controller.source;
|
|
2913
|
+
view.context.parentContext = controller.context;
|
|
2914
|
+
view.bind(items[index]);
|
|
2915
|
+
}
|
|
2916
|
+
function bindWithPositioning(view, items, index, controller) {
|
|
2917
|
+
view.context.parent = controller.source;
|
|
2918
|
+
view.context.parentContext = controller.context;
|
|
2919
|
+
view.context.length = items.length;
|
|
2920
|
+
view.context.index = index;
|
|
2921
|
+
view.bind(items[index]);
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* A behavior that renders a template for each item in an array.
|
|
2925
|
+
* @public
|
|
2926
|
+
*/
|
|
2927
|
+
class RepeatBehavior {
|
|
2928
|
+
/**
|
|
2929
|
+
* Creates an instance of RepeatBehavior.
|
|
2930
|
+
* @param location - The location in the DOM to render the repeat.
|
|
2931
|
+
* @param dataBinding - The array to render.
|
|
2932
|
+
* @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
|
|
2933
|
+
* @param templateBinding - The template to render for each item.
|
|
2934
|
+
* @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
|
|
2935
|
+
* @param options - Options used to turn on special repeat features.
|
|
2936
|
+
*/
|
|
2937
|
+
constructor(directive) {
|
|
2938
|
+
this.directive = directive;
|
|
2939
|
+
this.views = [];
|
|
2940
|
+
this.items = null;
|
|
2941
|
+
this.itemsObserver = null;
|
|
2942
|
+
this.bindView = bindWithoutPositioning;
|
|
2943
|
+
this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
|
|
2944
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
|
|
2945
|
+
if (directive.options.positioning) {
|
|
2946
|
+
this.bindView = bindWithPositioning;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Bind this behavior.
|
|
2951
|
+
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
2952
|
+
*/
|
|
2953
|
+
bind(controller) {
|
|
2954
|
+
this.location = controller.targets[this.directive.nodeId];
|
|
2955
|
+
this.controller = controller;
|
|
2956
|
+
this.items = this.itemsBindingObserver.bind(controller);
|
|
2957
|
+
this.template = this.templateBindingObserver.bind(controller);
|
|
2958
|
+
this.observeItems(true);
|
|
2959
|
+
this.refreshAllViews();
|
|
2960
|
+
controller.onUnbind(this);
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Unbinds this behavior.
|
|
2964
|
+
*/
|
|
2965
|
+
unbind() {
|
|
2966
|
+
if (this.itemsObserver !== null) {
|
|
2967
|
+
this.itemsObserver.unsubscribe(this);
|
|
2968
|
+
}
|
|
2969
|
+
this.unbindAllViews();
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Handles changes in the array, its items, and the repeat template.
|
|
2973
|
+
* @param source - The source of the change.
|
|
2974
|
+
* @param args - The details about what was changed.
|
|
2975
|
+
*/
|
|
2976
|
+
handleChange(source, args) {
|
|
2977
|
+
if (args === this.itemsBindingObserver) {
|
|
2978
|
+
this.items = this.itemsBindingObserver.bind(this.controller);
|
|
2979
|
+
this.observeItems();
|
|
2980
|
+
this.refreshAllViews();
|
|
2981
|
+
}
|
|
2982
|
+
else if (args === this.templateBindingObserver) {
|
|
2983
|
+
this.template = this.templateBindingObserver.bind(this.controller);
|
|
2984
|
+
this.refreshAllViews(true);
|
|
2985
|
+
}
|
|
2986
|
+
else if (!args[0]) {
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
else if (args[0].reset) {
|
|
2990
|
+
this.refreshAllViews();
|
|
2991
|
+
}
|
|
2992
|
+
else {
|
|
2993
|
+
this.updateViews(args);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
observeItems(force = false) {
|
|
2997
|
+
if (!this.items) {
|
|
2998
|
+
this.items = emptyArray;
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
const oldObserver = this.itemsObserver;
|
|
3002
|
+
const newObserver = (this.itemsObserver = Observable.getNotifier(this.items));
|
|
3003
|
+
const hasNewObserver = oldObserver !== newObserver;
|
|
3004
|
+
if (hasNewObserver && oldObserver !== null) {
|
|
3005
|
+
oldObserver.unsubscribe(this);
|
|
3006
|
+
}
|
|
3007
|
+
if (hasNewObserver || force) {
|
|
3008
|
+
newObserver.subscribe(this);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
updateViews(splices) {
|
|
3012
|
+
const views = this.views;
|
|
3013
|
+
const bindView = this.bindView;
|
|
3014
|
+
const items = this.items;
|
|
3015
|
+
const template = this.template;
|
|
3016
|
+
const controller = this.controller;
|
|
3017
|
+
const recycle = this.directive.options.recycle;
|
|
3018
|
+
const leftoverViews = [];
|
|
3019
|
+
let leftoverIndex = 0;
|
|
3020
|
+
let availableViews = 0;
|
|
3021
|
+
for (let i = 0, ii = splices.length; i < ii; ++i) {
|
|
3022
|
+
const splice = splices[i];
|
|
3023
|
+
const removed = splice.removed;
|
|
3024
|
+
let removeIndex = 0;
|
|
3025
|
+
let addIndex = splice.index;
|
|
3026
|
+
const end = addIndex + splice.addedCount;
|
|
3027
|
+
const removedViews = views.splice(splice.index, removed.length);
|
|
3028
|
+
const totalAvailableViews = (availableViews =
|
|
3029
|
+
leftoverViews.length + removedViews.length);
|
|
3030
|
+
for (; addIndex < end; ++addIndex) {
|
|
3031
|
+
const neighbor = views[addIndex];
|
|
3032
|
+
const location = neighbor ? neighbor.firstChild : this.location;
|
|
3033
|
+
let view;
|
|
3034
|
+
if (recycle && availableViews > 0) {
|
|
3035
|
+
if (removeIndex <= totalAvailableViews && removedViews.length > 0) {
|
|
3036
|
+
view = removedViews[removeIndex];
|
|
3037
|
+
removeIndex++;
|
|
3038
|
+
}
|
|
3039
|
+
else {
|
|
3040
|
+
view = leftoverViews[leftoverIndex];
|
|
3041
|
+
leftoverIndex++;
|
|
3042
|
+
}
|
|
3043
|
+
availableViews--;
|
|
3044
|
+
}
|
|
3045
|
+
else {
|
|
3046
|
+
view = template.create();
|
|
3047
|
+
}
|
|
3048
|
+
views.splice(addIndex, 0, view);
|
|
3049
|
+
bindView(view, items, addIndex, controller);
|
|
3050
|
+
view.insertBefore(location);
|
|
3051
|
+
}
|
|
3052
|
+
if (removedViews[removeIndex]) {
|
|
3053
|
+
leftoverViews.push(...removedViews.slice(removeIndex));
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
|
|
3057
|
+
leftoverViews[i].dispose();
|
|
3058
|
+
}
|
|
3059
|
+
if (this.directive.options.positioning) {
|
|
3060
|
+
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
3061
|
+
const context = views[i].context;
|
|
3062
|
+
context.length = i;
|
|
3063
|
+
context.index = ii;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
refreshAllViews(templateChanged = false) {
|
|
3068
|
+
const items = this.items;
|
|
3069
|
+
const template = this.template;
|
|
3070
|
+
const location = this.location;
|
|
3071
|
+
const bindView = this.bindView;
|
|
3072
|
+
const controller = this.controller;
|
|
3073
|
+
let itemsLength = items.length;
|
|
3074
|
+
let views = this.views;
|
|
3075
|
+
let viewsLength = views.length;
|
|
3076
|
+
if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
|
|
3077
|
+
// all views need to be removed
|
|
3078
|
+
HTMLView.disposeContiguousBatch(views);
|
|
3079
|
+
viewsLength = 0;
|
|
3080
|
+
}
|
|
3081
|
+
if (viewsLength === 0) {
|
|
3082
|
+
// all views need to be created
|
|
3083
|
+
this.views = views = new Array(itemsLength);
|
|
3084
|
+
for (let i = 0; i < itemsLength; ++i) {
|
|
3085
|
+
const view = template.create();
|
|
3086
|
+
bindView(view, items, i, controller);
|
|
3087
|
+
views[i] = view;
|
|
3088
|
+
view.insertBefore(location);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
else {
|
|
3092
|
+
// attempt to reuse existing views with new data
|
|
3093
|
+
let i = 0;
|
|
3094
|
+
for (; i < itemsLength; ++i) {
|
|
3095
|
+
if (i < viewsLength) {
|
|
3096
|
+
const view = views[i];
|
|
3097
|
+
bindView(view, items, i, controller);
|
|
3098
|
+
}
|
|
3099
|
+
else {
|
|
3100
|
+
const view = template.create();
|
|
3101
|
+
bindView(view, items, i, controller);
|
|
3102
|
+
views.push(view);
|
|
3103
|
+
view.insertBefore(location);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
const removed = views.splice(i, viewsLength - i);
|
|
3107
|
+
for (i = 0, itemsLength = removed.length; i < itemsLength; ++i) {
|
|
3108
|
+
removed[i].dispose();
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
unbindAllViews() {
|
|
3113
|
+
const views = this.views;
|
|
3114
|
+
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
3115
|
+
views[i].unbind();
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* A directive that configures list rendering.
|
|
3121
|
+
* @public
|
|
3122
|
+
*/
|
|
3123
|
+
class RepeatDirective {
|
|
3124
|
+
/**
|
|
3125
|
+
* Creates an instance of RepeatDirective.
|
|
3126
|
+
* @param dataBinding - The binding that provides the array to render.
|
|
3127
|
+
* @param templateBinding - The template binding used to obtain a template to render for each item in the array.
|
|
3128
|
+
* @param options - Options used to turn on special repeat features.
|
|
3129
|
+
*/
|
|
3130
|
+
constructor(dataBinding, templateBinding, options) {
|
|
3131
|
+
this.dataBinding = dataBinding;
|
|
3132
|
+
this.templateBinding = templateBinding;
|
|
3133
|
+
this.options = options;
|
|
3134
|
+
/**
|
|
3135
|
+
* The unique id of the factory.
|
|
3136
|
+
*/
|
|
3137
|
+
this.id = nextId();
|
|
3138
|
+
ArrayObserver.enable();
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* Creates a placeholder string based on the directive's index within the template.
|
|
3142
|
+
* @param index - The index of the directive within the template.
|
|
3143
|
+
*/
|
|
3144
|
+
createHTML(add) {
|
|
3145
|
+
return Markup.comment(add(this));
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Creates a behavior for the provided target node.
|
|
3149
|
+
* @param target - The node instance to create the behavior for.
|
|
3150
|
+
*/
|
|
3151
|
+
createBehavior() {
|
|
3152
|
+
return new RepeatBehavior(this);
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
HTMLDirective.define(RepeatDirective);
|
|
3156
|
+
/**
|
|
3157
|
+
* A directive that enables list rendering.
|
|
3158
|
+
* @param items - The array to render.
|
|
3159
|
+
* @param template - The template or a template binding used obtain a template
|
|
3160
|
+
* to render for each item in the array.
|
|
3161
|
+
* @param options - Options used to turn on special repeat features.
|
|
3162
|
+
* @public
|
|
3163
|
+
*/
|
|
3164
|
+
function repeat(items, template, options = defaultRepeatOptions) {
|
|
3165
|
+
const dataBinding = normalizeBinding(items);
|
|
3166
|
+
const templateBinding = normalizeBinding(template);
|
|
3167
|
+
return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
const selectElements = (value) => value.nodeType === 1;
|
|
3171
|
+
/**
|
|
3172
|
+
* Creates a function that can be used to filter a Node array, selecting only elements.
|
|
3173
|
+
* @param selector - An optional selector to restrict the filter to.
|
|
3174
|
+
* @public
|
|
3175
|
+
*/
|
|
3176
|
+
const elements = (selector) => selector
|
|
3177
|
+
? value => value.nodeType === 1 && value.matches(selector)
|
|
3178
|
+
: selectElements;
|
|
3179
|
+
/**
|
|
3180
|
+
* A base class for node observation.
|
|
3181
|
+
* @public
|
|
3182
|
+
* @remarks
|
|
3183
|
+
* Internally used by the SlottedDirective and the ChildrenDirective.
|
|
3184
|
+
*/
|
|
3185
|
+
class NodeObservationDirective extends StatelessAttachedAttributeDirective {
|
|
3186
|
+
constructor() {
|
|
3187
|
+
super(...arguments);
|
|
3188
|
+
this.sourceProperty = `${this.id}-s`;
|
|
3189
|
+
}
|
|
3190
|
+
/**
|
|
3191
|
+
* Bind this behavior to the source.
|
|
3192
|
+
* @param source - The source to bind to.
|
|
3193
|
+
* @param context - The execution context that the binding is operating within.
|
|
3194
|
+
* @param targets - The targets that behaviors in a view can attach to.
|
|
3195
|
+
*/
|
|
3196
|
+
bind(controller) {
|
|
3197
|
+
const target = controller.targets[this.nodeId];
|
|
3198
|
+
target[this.sourceProperty] = controller.source;
|
|
3199
|
+
this.updateTarget(controller.source, this.computeNodes(target));
|
|
3200
|
+
this.observe(target);
|
|
3201
|
+
controller.onUnbind(this);
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Unbinds this behavior from the source.
|
|
3205
|
+
* @param source - The source to unbind from.
|
|
3206
|
+
* @param context - The execution context that the binding is operating within.
|
|
3207
|
+
* @param targets - The targets that behaviors in a view can attach to.
|
|
3208
|
+
*/
|
|
3209
|
+
unbind(controller) {
|
|
3210
|
+
const target = controller.targets[this.nodeId];
|
|
3211
|
+
this.updateTarget(controller.source, emptyArray);
|
|
3212
|
+
this.disconnect(target);
|
|
3213
|
+
target[this.sourceProperty] = null;
|
|
3214
|
+
}
|
|
3215
|
+
/**
|
|
3216
|
+
* Gets the data source for the target.
|
|
3217
|
+
* @param target - The target to get the source for.
|
|
3218
|
+
* @returns The source.
|
|
3219
|
+
*/
|
|
3220
|
+
getSource(target) {
|
|
3221
|
+
return target[this.sourceProperty];
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Updates the source property with the computed nodes.
|
|
3225
|
+
* @param source - The source object to assign the nodes property to.
|
|
3226
|
+
* @param value - The nodes to assign to the source object property.
|
|
3227
|
+
*/
|
|
3228
|
+
updateTarget(source, value) {
|
|
3229
|
+
source[this.options.property] = value;
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* Computes the set of nodes that should be assigned to the source property.
|
|
3233
|
+
* @param target - The target to compute the nodes for.
|
|
3234
|
+
* @returns The computed nodes.
|
|
3235
|
+
* @remarks
|
|
3236
|
+
* Applies filters if provided.
|
|
3237
|
+
*/
|
|
3238
|
+
computeNodes(target) {
|
|
3239
|
+
let nodes = this.getNodes(target);
|
|
3240
|
+
if ("filter" in this.options) {
|
|
3241
|
+
nodes = nodes.filter(this.options.filter);
|
|
3242
|
+
}
|
|
3243
|
+
return nodes;
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
const slotEvent = "slotchange";
|
|
3248
|
+
/**
|
|
3249
|
+
* The runtime behavior for slotted node observation.
|
|
3250
|
+
* @public
|
|
3251
|
+
*/
|
|
3252
|
+
class SlottedDirective extends NodeObservationDirective {
|
|
3253
|
+
/**
|
|
3254
|
+
* Begins observation of the nodes.
|
|
3255
|
+
* @param target - The target to observe.
|
|
3256
|
+
*/
|
|
3257
|
+
observe(target) {
|
|
3258
|
+
target.addEventListener(slotEvent, this);
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Disconnects observation of the nodes.
|
|
3262
|
+
* @param target - The target to unobserve.
|
|
3263
|
+
*/
|
|
3264
|
+
disconnect(target) {
|
|
3265
|
+
target.removeEventListener(slotEvent, this);
|
|
3266
|
+
}
|
|
3267
|
+
/**
|
|
3268
|
+
* Retrieves the raw nodes that should be assigned to the source property.
|
|
3269
|
+
* @param target - The target to get the node to.
|
|
3270
|
+
*/
|
|
3271
|
+
getNodes(target) {
|
|
3272
|
+
return target.assignedNodes(this.options);
|
|
3273
|
+
}
|
|
3274
|
+
/** @internal */
|
|
3275
|
+
handleEvent(event) {
|
|
3276
|
+
const target = event.currentTarget;
|
|
3277
|
+
this.updateTarget(this.getSource(target), this.computeNodes(target));
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
HTMLDirective.define(SlottedDirective);
|
|
3281
|
+
/**
|
|
3282
|
+
* A directive that observes the `assignedNodes()` of a slot and updates a property
|
|
3283
|
+
* whenever they change.
|
|
3284
|
+
* @param propertyOrOptions - The options used to configure slotted node observation.
|
|
3285
|
+
* @public
|
|
3286
|
+
*/
|
|
3287
|
+
function slotted(propertyOrOptions) {
|
|
3288
|
+
if (isString(propertyOrOptions)) {
|
|
3289
|
+
propertyOrOptions = { property: propertyOrOptions };
|
|
3290
|
+
}
|
|
3291
|
+
return new SlottedDirective(propertyOrOptions);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
/**
|
|
3295
|
+
* The runtime behavior for child node observation.
|
|
3296
|
+
* @public
|
|
3297
|
+
*/
|
|
3298
|
+
class ChildrenDirective extends NodeObservationDirective {
|
|
3299
|
+
/**
|
|
3300
|
+
* Creates an instance of ChildrenDirective.
|
|
3301
|
+
* @param options - The options to use in configuring the child observation behavior.
|
|
3302
|
+
*/
|
|
3303
|
+
constructor(options) {
|
|
3304
|
+
super(options);
|
|
3305
|
+
this.observerProperty = `${this.id}-o`;
|
|
3306
|
+
this.handleEvent = (mutations, observer) => {
|
|
3307
|
+
const target = observer.target;
|
|
3308
|
+
this.updateTarget(this.getSource(target), this.computeNodes(target));
|
|
3309
|
+
};
|
|
3310
|
+
options.childList = true;
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* Begins observation of the nodes.
|
|
3314
|
+
* @param target - The target to observe.
|
|
3315
|
+
*/
|
|
3316
|
+
observe(target) {
|
|
3317
|
+
var _a;
|
|
3318
|
+
const observer = (_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = new MutationObserver(this.handleEvent));
|
|
3319
|
+
observer.target = target;
|
|
3320
|
+
observer.observe(target, this.options);
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Disconnects observation of the nodes.
|
|
3324
|
+
* @param target - The target to unobserve.
|
|
3325
|
+
*/
|
|
3326
|
+
disconnect(target) {
|
|
3327
|
+
const observer = target[this.observerProperty];
|
|
3328
|
+
observer.target = null;
|
|
3329
|
+
observer.disconnect();
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Retrieves the raw nodes that should be assigned to the source property.
|
|
3333
|
+
* @param target - The target to get the node to.
|
|
3334
|
+
*/
|
|
3335
|
+
getNodes(target) {
|
|
3336
|
+
if ("selector" in this.options) {
|
|
3337
|
+
return Array.from(target.querySelectorAll(this.options.selector));
|
|
3338
|
+
}
|
|
3339
|
+
return Array.from(target.childNodes);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
HTMLDirective.define(ChildrenDirective);
|
|
3343
|
+
/**
|
|
3344
|
+
* A directive that observes the `childNodes` of an element and updates a property
|
|
3345
|
+
* whenever they change.
|
|
3346
|
+
* @param propertyOrOptions - The options used to configure child node observation.
|
|
3347
|
+
* @public
|
|
3348
|
+
*/
|
|
3349
|
+
function children(propertyOrOptions) {
|
|
3350
|
+
if (isString(propertyOrOptions)) {
|
|
3351
|
+
propertyOrOptions = {
|
|
3352
|
+
property: propertyOrOptions,
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
return new ChildrenDirective(propertyOrOptions);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
const booleanMode = "boolean";
|
|
3359
|
+
const reflectMode = "reflect";
|
|
3360
|
+
/**
|
|
3361
|
+
* Metadata used to configure a custom attribute's behavior.
|
|
3362
|
+
* @public
|
|
3363
|
+
*/
|
|
3364
|
+
const AttributeConfiguration = Object.freeze({
|
|
3365
|
+
/**
|
|
3366
|
+
* Locates all attribute configurations associated with a type.
|
|
3367
|
+
*/
|
|
3368
|
+
locate: createMetadataLocator(),
|
|
3369
|
+
});
|
|
3370
|
+
/**
|
|
3371
|
+
* A {@link ValueConverter} that converts to and from `boolean` values.
|
|
3372
|
+
* @remarks
|
|
3373
|
+
* Used automatically when the `boolean` {@link AttributeMode} is selected.
|
|
3374
|
+
* @public
|
|
3375
|
+
*/
|
|
3376
|
+
const booleanConverter = {
|
|
3377
|
+
toView(value) {
|
|
3378
|
+
return value ? "true" : "false";
|
|
3379
|
+
},
|
|
3380
|
+
fromView(value) {
|
|
3381
|
+
return value === null ||
|
|
3382
|
+
value === void 0 ||
|
|
3383
|
+
value === "false" ||
|
|
3384
|
+
value === false ||
|
|
3385
|
+
value === 0
|
|
3386
|
+
? false
|
|
3387
|
+
: true;
|
|
3388
|
+
},
|
|
3389
|
+
};
|
|
3390
|
+
function toNumber(value) {
|
|
3391
|
+
if (value === null || value === undefined) {
|
|
3392
|
+
return null;
|
|
3393
|
+
}
|
|
3394
|
+
const number = value * 1;
|
|
3395
|
+
return isNaN(number) ? null : number;
|
|
3396
|
+
}
|
|
3397
|
+
/**
|
|
3398
|
+
* A {@link ValueConverter} that converts to and from `number` values.
|
|
3399
|
+
* @remarks
|
|
3400
|
+
* This converter allows for nullable numbers, returning `null` if the
|
|
3401
|
+
* input was `null`, `undefined`, or `NaN`.
|
|
3402
|
+
* @public
|
|
3403
|
+
*/
|
|
3404
|
+
const nullableNumberConverter = {
|
|
3405
|
+
toView(value) {
|
|
3406
|
+
const output = toNumber(value);
|
|
3407
|
+
return output ? output.toString() : output;
|
|
3408
|
+
},
|
|
3409
|
+
fromView: toNumber,
|
|
3410
|
+
};
|
|
3411
|
+
/**
|
|
3412
|
+
* An implementation of {@link Accessor} that supports reactivity,
|
|
3413
|
+
* change callbacks, attribute reflection, and type conversion for
|
|
3414
|
+
* custom elements.
|
|
3415
|
+
* @public
|
|
3416
|
+
*/
|
|
3417
|
+
class AttributeDefinition {
|
|
3418
|
+
/**
|
|
3419
|
+
* Creates an instance of AttributeDefinition.
|
|
3420
|
+
* @param Owner - The class constructor that owns this attribute.
|
|
3421
|
+
* @param name - The name of the property associated with the attribute.
|
|
3422
|
+
* @param attribute - The name of the attribute in HTML.
|
|
3423
|
+
* @param mode - The {@link AttributeMode} that describes the behavior of this attribute.
|
|
3424
|
+
* @param converter - A {@link ValueConverter} that integrates with the property getter/setter
|
|
3425
|
+
* to convert values to and from a DOM string.
|
|
3426
|
+
*/
|
|
3427
|
+
constructor(Owner, name, attribute = name.toLowerCase(), mode = reflectMode, converter) {
|
|
3428
|
+
this.guards = new Set();
|
|
3429
|
+
this.Owner = Owner;
|
|
3430
|
+
this.name = name;
|
|
3431
|
+
this.attribute = attribute;
|
|
3432
|
+
this.mode = mode;
|
|
3433
|
+
this.converter = converter;
|
|
3434
|
+
this.fieldName = `_${name}`;
|
|
3435
|
+
this.callbackName = `${name}Changed`;
|
|
3436
|
+
this.hasCallback = this.callbackName in Owner.prototype;
|
|
3437
|
+
if (mode === booleanMode && converter === void 0) {
|
|
3438
|
+
this.converter = booleanConverter;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
/**
|
|
3442
|
+
* Sets the value of the attribute/property on the source element.
|
|
3443
|
+
* @param source - The source element to access.
|
|
3444
|
+
* @param value - The value to set the attribute/property to.
|
|
3445
|
+
*/
|
|
3446
|
+
setValue(source, newValue) {
|
|
3447
|
+
const oldValue = source[this.fieldName];
|
|
3448
|
+
const converter = this.converter;
|
|
3449
|
+
if (converter !== void 0) {
|
|
3450
|
+
newValue = converter.fromView(newValue);
|
|
3451
|
+
}
|
|
3452
|
+
if (oldValue !== newValue) {
|
|
3453
|
+
source[this.fieldName] = newValue;
|
|
3454
|
+
this.tryReflectToAttribute(source);
|
|
3455
|
+
if (this.hasCallback) {
|
|
3456
|
+
source[this.callbackName](oldValue, newValue);
|
|
3457
|
+
}
|
|
3458
|
+
source.$fastController.notify(this.name);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
/**
|
|
3462
|
+
* Gets the value of the attribute/property on the source element.
|
|
3463
|
+
* @param source - The source element to access.
|
|
3464
|
+
*/
|
|
3465
|
+
getValue(source) {
|
|
3466
|
+
Observable.track(source, this.name);
|
|
3467
|
+
return source[this.fieldName];
|
|
3468
|
+
}
|
|
3469
|
+
/** @internal */
|
|
3470
|
+
onAttributeChangedCallback(element, value) {
|
|
3471
|
+
if (this.guards.has(element)) {
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
this.guards.add(element);
|
|
3475
|
+
this.setValue(element, value);
|
|
3476
|
+
this.guards.delete(element);
|
|
3477
|
+
}
|
|
3478
|
+
tryReflectToAttribute(element) {
|
|
3479
|
+
const mode = this.mode;
|
|
3480
|
+
const guards = this.guards;
|
|
3481
|
+
if (guards.has(element) || mode === "fromView") {
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
Updates.enqueue(() => {
|
|
3485
|
+
guards.add(element);
|
|
3486
|
+
const latestValue = element[this.fieldName];
|
|
3487
|
+
switch (mode) {
|
|
3488
|
+
case reflectMode:
|
|
3489
|
+
const converter = this.converter;
|
|
3490
|
+
DOM.setAttribute(element, this.attribute, converter !== void 0 ? converter.toView(latestValue) : latestValue);
|
|
3491
|
+
break;
|
|
3492
|
+
case booleanMode:
|
|
3493
|
+
DOM.setBooleanAttribute(element, this.attribute, latestValue);
|
|
3494
|
+
break;
|
|
3495
|
+
}
|
|
3496
|
+
guards.delete(element);
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Collects all attribute definitions associated with the owner.
|
|
3501
|
+
* @param Owner - The class constructor to collect attribute for.
|
|
3502
|
+
* @param attributeLists - Any existing attributes to collect and merge with those associated with the owner.
|
|
3503
|
+
* @internal
|
|
3504
|
+
*/
|
|
3505
|
+
static collect(Owner, ...attributeLists) {
|
|
3506
|
+
const attributes = [];
|
|
3507
|
+
attributeLists.push(AttributeConfiguration.locate(Owner));
|
|
3508
|
+
for (let i = 0, ii = attributeLists.length; i < ii; ++i) {
|
|
3509
|
+
const list = attributeLists[i];
|
|
3510
|
+
if (list === void 0) {
|
|
3511
|
+
continue;
|
|
3512
|
+
}
|
|
3513
|
+
for (let j = 0, jj = list.length; j < jj; ++j) {
|
|
3514
|
+
const config = list[j];
|
|
3515
|
+
if (isString(config)) {
|
|
3516
|
+
attributes.push(new AttributeDefinition(Owner, config));
|
|
3517
|
+
}
|
|
3518
|
+
else {
|
|
3519
|
+
attributes.push(new AttributeDefinition(Owner, config.property, config.attribute, config.mode, config.converter));
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
return attributes;
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
function attr(configOrTarget, prop) {
|
|
3527
|
+
let config;
|
|
3528
|
+
function decorator($target, $prop) {
|
|
3529
|
+
if (arguments.length > 1) {
|
|
3530
|
+
// Non invocation:
|
|
3531
|
+
// - @attr
|
|
3532
|
+
// Invocation with or w/o opts:
|
|
3533
|
+
// - @attr()
|
|
3534
|
+
// - @attr({...opts})
|
|
3535
|
+
config.property = $prop;
|
|
3536
|
+
}
|
|
3537
|
+
AttributeConfiguration.locate($target.constructor).push(config);
|
|
3538
|
+
}
|
|
3539
|
+
if (arguments.length > 1) {
|
|
3540
|
+
// Non invocation:
|
|
3541
|
+
// - @attr
|
|
3542
|
+
config = {};
|
|
3543
|
+
decorator(configOrTarget, prop);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
// Invocation with or w/o opts:
|
|
3547
|
+
// - @attr()
|
|
3548
|
+
// - @attr({...opts})
|
|
3549
|
+
config = configOrTarget === void 0 ? {} : configOrTarget;
|
|
3550
|
+
return decorator;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
const defaultShadowOptions = { mode: "open" };
|
|
3554
|
+
const defaultElementOptions = {};
|
|
3555
|
+
const fastElementBaseTypes = new Set();
|
|
3556
|
+
const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
|
|
3557
|
+
/**
|
|
3558
|
+
* Defines metadata for a FASTElement.
|
|
3559
|
+
* @public
|
|
3560
|
+
*/
|
|
3561
|
+
class FASTElementDefinition {
|
|
3562
|
+
constructor(type, nameOrConfig = type.definition) {
|
|
3563
|
+
var _a;
|
|
3564
|
+
this.platformDefined = false;
|
|
3565
|
+
if (isString(nameOrConfig)) {
|
|
3566
|
+
nameOrConfig = { name: nameOrConfig };
|
|
3567
|
+
}
|
|
3568
|
+
this.type = type;
|
|
3569
|
+
this.name = nameOrConfig.name;
|
|
3570
|
+
this.template = nameOrConfig.template;
|
|
3571
|
+
this.registry = (_a = nameOrConfig.registry) !== null && _a !== void 0 ? _a : customElements;
|
|
3572
|
+
const proto = type.prototype;
|
|
3573
|
+
const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
|
|
3574
|
+
const observedAttributes = new Array(attributes.length);
|
|
3575
|
+
const propertyLookup = {};
|
|
3576
|
+
const attributeLookup = {};
|
|
3577
|
+
for (let i = 0, ii = attributes.length; i < ii; ++i) {
|
|
3578
|
+
const current = attributes[i];
|
|
3579
|
+
observedAttributes[i] = current.attribute;
|
|
3580
|
+
propertyLookup[current.name] = current;
|
|
3581
|
+
attributeLookup[current.attribute] = current;
|
|
3582
|
+
Observable.defineProperty(proto, current);
|
|
3583
|
+
}
|
|
3584
|
+
Reflect.defineProperty(type, "observedAttributes", {
|
|
3585
|
+
value: observedAttributes,
|
|
3586
|
+
enumerable: true,
|
|
3587
|
+
});
|
|
3588
|
+
this.attributes = attributes;
|
|
3589
|
+
this.propertyLookup = propertyLookup;
|
|
3590
|
+
this.attributeLookup = attributeLookup;
|
|
3591
|
+
this.shadowOptions =
|
|
3592
|
+
nameOrConfig.shadowOptions === void 0
|
|
3593
|
+
? defaultShadowOptions
|
|
3594
|
+
: nameOrConfig.shadowOptions === null
|
|
3595
|
+
? void 0
|
|
3596
|
+
: Object.assign(Object.assign({}, defaultShadowOptions), nameOrConfig.shadowOptions);
|
|
3597
|
+
this.elementOptions =
|
|
3598
|
+
nameOrConfig.elementOptions === void 0
|
|
3599
|
+
? defaultElementOptions
|
|
3600
|
+
: Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
|
|
3601
|
+
this.styles = ElementStyles.normalize(nameOrConfig.styles);
|
|
3602
|
+
fastElementRegistry.register(this);
|
|
3603
|
+
}
|
|
3604
|
+
/**
|
|
3605
|
+
* Indicates if this element has been defined in at least one registry.
|
|
3606
|
+
*/
|
|
3607
|
+
get isDefined() {
|
|
3608
|
+
return this.platformDefined;
|
|
3609
|
+
}
|
|
3610
|
+
/**
|
|
3611
|
+
* Defines a custom element based on this definition.
|
|
3612
|
+
* @param registry - The element registry to define the element in.
|
|
3613
|
+
* @remarks
|
|
3614
|
+
* This operation is idempotent per registry.
|
|
3615
|
+
*/
|
|
3616
|
+
define(registry = this.registry) {
|
|
3617
|
+
const type = this.type;
|
|
3618
|
+
if (!registry.get(this.name)) {
|
|
3619
|
+
this.platformDefined = true;
|
|
3620
|
+
registry.define(this.name, type, this.elementOptions);
|
|
3621
|
+
}
|
|
3622
|
+
return this;
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Creates an instance of FASTElementDefinition.
|
|
3626
|
+
* @param type - The type this definition is being created for.
|
|
3627
|
+
* @param nameOrDef - The name of the element to define or a config object
|
|
3628
|
+
* that describes the element to define.
|
|
3629
|
+
*/
|
|
3630
|
+
static compose(type, nameOrDef) {
|
|
3631
|
+
if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
|
|
3632
|
+
return new FASTElementDefinition(class extends type {
|
|
3633
|
+
}, nameOrDef);
|
|
3634
|
+
}
|
|
3635
|
+
return new FASTElementDefinition(type, nameOrDef);
|
|
3636
|
+
}
|
|
3637
|
+
/**
|
|
3638
|
+
* Registers a FASTElement base type.
|
|
3639
|
+
* @param type - The type to register as a base type.
|
|
3640
|
+
* @internal
|
|
3641
|
+
*/
|
|
3642
|
+
static registerBaseType(type) {
|
|
3643
|
+
fastElementBaseTypes.add(type);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
/**
|
|
3647
|
+
* Gets the element definition associated with the specified type.
|
|
3648
|
+
* @param type - The custom element type to retrieve the definition for.
|
|
3649
|
+
*/
|
|
3650
|
+
FASTElementDefinition.getByType = fastElementRegistry.getByType;
|
|
3651
|
+
/**
|
|
3652
|
+
* Gets the element definition associated with the instance.
|
|
3653
|
+
* @param instance - The custom element instance to retrieve the definition for.
|
|
3654
|
+
*/
|
|
3655
|
+
FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
|
|
3656
|
+
|
|
3657
|
+
const defaultEventOptions = {
|
|
3658
|
+
bubbles: true,
|
|
3659
|
+
composed: true,
|
|
3660
|
+
cancelable: true,
|
|
3661
|
+
};
|
|
3662
|
+
const isConnectedPropertyName = "isConnected";
|
|
3663
|
+
const shadowRoots = new WeakMap();
|
|
3664
|
+
function getShadowRoot(element) {
|
|
3665
|
+
var _a, _b;
|
|
3666
|
+
return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
|
|
3667
|
+
}
|
|
3668
|
+
/**
|
|
3669
|
+
* Controls the lifecycle and rendering of a `FASTElement`.
|
|
3670
|
+
* @public
|
|
3671
|
+
*/
|
|
3672
|
+
class ElementController extends PropertyChangeNotifier {
|
|
3673
|
+
/**
|
|
3674
|
+
* Creates a Controller to control the specified element.
|
|
3675
|
+
* @param element - The element to be controlled by this controller.
|
|
3676
|
+
* @param definition - The element definition metadata that instructs this
|
|
3677
|
+
* controller in how to handle rendering and other platform integrations.
|
|
3678
|
+
* @internal
|
|
3679
|
+
*/
|
|
3680
|
+
constructor(element, definition) {
|
|
3681
|
+
super(element);
|
|
3682
|
+
this.boundObservables = null;
|
|
3683
|
+
this.needsInitialization = true;
|
|
3684
|
+
this.hasExistingShadowRoot = false;
|
|
3685
|
+
this._template = null;
|
|
3686
|
+
this._isConnected = false;
|
|
3687
|
+
this.behaviors = null;
|
|
3688
|
+
this._mainStyles = null;
|
|
3689
|
+
/**
|
|
3690
|
+
* This allows Observable.getNotifier(...) to return the Controller
|
|
3691
|
+
* when the notifier for the Controller itself is being requested. The
|
|
3692
|
+
* result is that the Observable system does not need to create a separate
|
|
3693
|
+
* instance of Notifier for observables on the Controller. The component and
|
|
3694
|
+
* the controller will now share the same notifier, removing one-object construct
|
|
3695
|
+
* per web component instance.
|
|
3696
|
+
*/
|
|
3697
|
+
this.$fastController = this;
|
|
3698
|
+
/**
|
|
3699
|
+
* The view associated with the custom element.
|
|
3700
|
+
* @remarks
|
|
3701
|
+
* If `null` then the element is managing its own rendering.
|
|
3702
|
+
*/
|
|
3703
|
+
this.view = null;
|
|
3704
|
+
this.source = element;
|
|
3705
|
+
this.definition = definition;
|
|
3706
|
+
const shadowOptions = definition.shadowOptions;
|
|
3707
|
+
if (shadowOptions !== void 0) {
|
|
3708
|
+
let shadowRoot = element.shadowRoot;
|
|
3709
|
+
if (shadowRoot) {
|
|
3710
|
+
this.hasExistingShadowRoot = true;
|
|
3711
|
+
}
|
|
3712
|
+
else {
|
|
3713
|
+
shadowRoot = element.attachShadow(shadowOptions);
|
|
3714
|
+
if (shadowOptions.mode === "closed") {
|
|
3715
|
+
shadowRoots.set(element, shadowRoot);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
// Capture any observable values that were set by the binding engine before
|
|
3720
|
+
// the browser upgraded the element. Then delete the property since it will
|
|
3721
|
+
// shadow the getter/setter that is required to make the observable operate.
|
|
3722
|
+
// Later, in the connect callback, we'll re-apply the values.
|
|
3723
|
+
const accessors = Observable.getAccessors(element);
|
|
3724
|
+
if (accessors.length > 0) {
|
|
3725
|
+
const boundObservables = (this.boundObservables = Object.create(null));
|
|
3726
|
+
for (let i = 0, ii = accessors.length; i < ii; ++i) {
|
|
3727
|
+
const propertyName = accessors[i].name;
|
|
3728
|
+
const value = element[propertyName];
|
|
3729
|
+
if (value !== void 0) {
|
|
3730
|
+
delete element[propertyName];
|
|
3731
|
+
boundObservables[propertyName] = value;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
/**
|
|
3737
|
+
* Indicates whether or not the custom element has been
|
|
3738
|
+
* connected to the document.
|
|
3739
|
+
*/
|
|
3740
|
+
get isConnected() {
|
|
3741
|
+
Observable.track(this, isConnectedPropertyName);
|
|
3742
|
+
return this._isConnected;
|
|
3743
|
+
}
|
|
3744
|
+
setIsConnected(value) {
|
|
3745
|
+
this._isConnected = value;
|
|
3746
|
+
Observable.notify(this, isConnectedPropertyName);
|
|
3747
|
+
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Gets/sets the template used to render the component.
|
|
3750
|
+
* @remarks
|
|
3751
|
+
* This value can only be accurately read after connect but can be set at any time.
|
|
3752
|
+
*/
|
|
3753
|
+
get template() {
|
|
3754
|
+
var _a;
|
|
3755
|
+
// 1. Template overrides take top precedence.
|
|
3756
|
+
if (this._template === null) {
|
|
3757
|
+
const definition = this.definition;
|
|
3758
|
+
if (this.source.resolveTemplate) {
|
|
3759
|
+
// 2. Allow for element instance overrides next.
|
|
3760
|
+
this._template = this.source.resolveTemplate();
|
|
3761
|
+
}
|
|
3762
|
+
else if (definition.template) {
|
|
3763
|
+
// 3. Default to the static definition.
|
|
3764
|
+
this._template = (_a = definition.template) !== null && _a !== void 0 ? _a : null;
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
return this._template;
|
|
3768
|
+
}
|
|
3769
|
+
set template(value) {
|
|
3770
|
+
if (this._template === value) {
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3773
|
+
this._template = value;
|
|
3774
|
+
if (!this.needsInitialization) {
|
|
3775
|
+
this.renderTemplate(value);
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
/**
|
|
3779
|
+
* The main set of styles used for the component, independent
|
|
3780
|
+
* of any dynamically added styles.
|
|
3781
|
+
*/
|
|
3782
|
+
get mainStyles() {
|
|
3783
|
+
var _a;
|
|
3784
|
+
// 1. Styles overrides take top precedence.
|
|
3785
|
+
if (this._mainStyles === null) {
|
|
3786
|
+
const definition = this.definition;
|
|
3787
|
+
if (this.source.resolveStyles) {
|
|
3788
|
+
// 2. Allow for element instance overrides next.
|
|
3789
|
+
this._mainStyles = this.source.resolveStyles();
|
|
3790
|
+
}
|
|
3791
|
+
else if (definition.styles) {
|
|
3792
|
+
// 3. Default to the static definition.
|
|
3793
|
+
this._mainStyles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
return this._mainStyles;
|
|
3797
|
+
}
|
|
3798
|
+
set mainStyles(value) {
|
|
3799
|
+
if (this._mainStyles === value) {
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
if (this._mainStyles !== null) {
|
|
3803
|
+
this.removeStyles(this._mainStyles);
|
|
3804
|
+
}
|
|
3805
|
+
this._mainStyles = value;
|
|
3806
|
+
if (!this.needsInitialization) {
|
|
3807
|
+
this.addStyles(value);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* Adds the behavior to the component.
|
|
3812
|
+
* @param behavior - The behavior to add.
|
|
3813
|
+
*/
|
|
3814
|
+
addBehavior(behavior) {
|
|
3815
|
+
var _a, _b;
|
|
3816
|
+
const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
|
|
3817
|
+
const count = (_b = targetBehaviors.get(behavior)) !== null && _b !== void 0 ? _b : 0;
|
|
3818
|
+
if (count === 0) {
|
|
3819
|
+
targetBehaviors.set(behavior, 1);
|
|
3820
|
+
behavior.addedCallback && behavior.addedCallback(this);
|
|
3821
|
+
if (behavior.connectedCallback && this.isConnected) {
|
|
3822
|
+
behavior.connectedCallback(this);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
else {
|
|
3826
|
+
targetBehaviors.set(behavior, count + 1);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Removes the behavior from the component.
|
|
3831
|
+
* @param behavior - The behavior to remove.
|
|
3832
|
+
* @param force - Forces removal even if this behavior was added more than once.
|
|
3833
|
+
*/
|
|
3834
|
+
removeBehavior(behavior, force = false) {
|
|
3835
|
+
const targetBehaviors = this.behaviors;
|
|
3836
|
+
if (targetBehaviors === null) {
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
const count = targetBehaviors.get(behavior);
|
|
3840
|
+
if (count === void 0) {
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
if (count === 1 || force) {
|
|
3844
|
+
targetBehaviors.delete(behavior);
|
|
3845
|
+
if (behavior.disconnectedCallback && this.isConnected) {
|
|
3846
|
+
behavior.disconnectedCallback(this);
|
|
3847
|
+
}
|
|
3848
|
+
behavior.removedCallback && behavior.removedCallback(this);
|
|
3849
|
+
}
|
|
3850
|
+
else {
|
|
3851
|
+
targetBehaviors.set(behavior, count - 1);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
/**
|
|
3855
|
+
* Adds styles to this element. Providing an HTMLStyleElement will attach the element instance to the shadowRoot.
|
|
3856
|
+
* @param styles - The styles to add.
|
|
3857
|
+
*/
|
|
3858
|
+
addStyles(styles) {
|
|
3859
|
+
var _a;
|
|
3860
|
+
if (!styles) {
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
const source = this.source;
|
|
3864
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3865
|
+
if (styles instanceof HTMLElement) {
|
|
3866
|
+
target.append(styles);
|
|
3867
|
+
}
|
|
3868
|
+
else if (!styles.isAttachedTo(target)) {
|
|
3869
|
+
const sourceBehaviors = styles.behaviors;
|
|
3870
|
+
styles.addStylesTo(target);
|
|
3871
|
+
if (sourceBehaviors !== null) {
|
|
3872
|
+
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3873
|
+
this.addBehavior(sourceBehaviors[i]);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Removes styles from this element. Providing an HTMLStyleElement will detach the element instance from the shadowRoot.
|
|
3880
|
+
* @param styles - the styles to remove.
|
|
3881
|
+
*/
|
|
3882
|
+
removeStyles(styles) {
|
|
3883
|
+
var _a;
|
|
3884
|
+
if (!styles) {
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
const source = this.source;
|
|
3888
|
+
const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
|
|
3889
|
+
if (styles instanceof HTMLElement) {
|
|
3890
|
+
target.removeChild(styles);
|
|
3891
|
+
}
|
|
3892
|
+
else if (styles.isAttachedTo(target)) {
|
|
3893
|
+
const sourceBehaviors = styles.behaviors;
|
|
3894
|
+
styles.removeStylesFrom(target);
|
|
3895
|
+
if (sourceBehaviors !== null) {
|
|
3896
|
+
for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
|
|
3897
|
+
this.addBehavior(sourceBehaviors[i]);
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* Runs connected lifecycle behavior on the associated element.
|
|
3904
|
+
*/
|
|
3905
|
+
connect() {
|
|
3906
|
+
if (this._isConnected) {
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
if (this.needsInitialization) {
|
|
3910
|
+
this.finishInitialization();
|
|
3911
|
+
}
|
|
3912
|
+
else if (this.view !== null) {
|
|
3913
|
+
this.view.bind(this.source);
|
|
3914
|
+
}
|
|
3915
|
+
const behaviors = this.behaviors;
|
|
3916
|
+
if (behaviors !== null) {
|
|
3917
|
+
for (const key of behaviors.keys()) {
|
|
3918
|
+
key.connectedCallback && key.connectedCallback(this);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
this.setIsConnected(true);
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Runs disconnected lifecycle behavior on the associated element.
|
|
3925
|
+
*/
|
|
3926
|
+
disconnect() {
|
|
3927
|
+
if (!this._isConnected) {
|
|
3928
|
+
return;
|
|
3929
|
+
}
|
|
3930
|
+
this.setIsConnected(false);
|
|
3931
|
+
if (this.view !== null) {
|
|
3932
|
+
this.view.unbind();
|
|
3933
|
+
}
|
|
3934
|
+
const behaviors = this.behaviors;
|
|
3935
|
+
if (behaviors !== null) {
|
|
3936
|
+
for (const key of behaviors.keys()) {
|
|
3937
|
+
key.disconnectedCallback && key.disconnectedCallback(this);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
/**
|
|
3942
|
+
* Runs the attribute changed callback for the associated element.
|
|
3943
|
+
* @param name - The name of the attribute that changed.
|
|
3944
|
+
* @param oldValue - The previous value of the attribute.
|
|
3945
|
+
* @param newValue - The new value of the attribute.
|
|
3946
|
+
*/
|
|
3947
|
+
onAttributeChangedCallback(name, oldValue, newValue) {
|
|
3948
|
+
const attrDef = this.definition.attributeLookup[name];
|
|
3949
|
+
if (attrDef !== void 0) {
|
|
3950
|
+
attrDef.onAttributeChangedCallback(this.source, newValue);
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
/**
|
|
3954
|
+
* Emits a custom HTML event.
|
|
3955
|
+
* @param type - The type name of the event.
|
|
3956
|
+
* @param detail - The event detail object to send with the event.
|
|
3957
|
+
* @param options - The event options. By default bubbles and composed.
|
|
3958
|
+
* @remarks
|
|
3959
|
+
* Only emits events if connected.
|
|
3960
|
+
*/
|
|
3961
|
+
emit(type, detail, options) {
|
|
3962
|
+
if (this._isConnected) {
|
|
3963
|
+
return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
|
|
3964
|
+
}
|
|
3965
|
+
return false;
|
|
3966
|
+
}
|
|
3967
|
+
finishInitialization() {
|
|
3968
|
+
const element = this.source;
|
|
3969
|
+
const boundObservables = this.boundObservables;
|
|
3970
|
+
// If we have any observables that were bound, re-apply their values.
|
|
3971
|
+
if (boundObservables !== null) {
|
|
3972
|
+
const propertyNames = Object.keys(boundObservables);
|
|
3973
|
+
for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
|
|
3974
|
+
const propertyName = propertyNames[i];
|
|
3975
|
+
element[propertyName] = boundObservables[propertyName];
|
|
3976
|
+
}
|
|
3977
|
+
this.boundObservables = null;
|
|
3978
|
+
}
|
|
3979
|
+
this.renderTemplate(this.template);
|
|
3980
|
+
this.addStyles(this.mainStyles);
|
|
3981
|
+
this.needsInitialization = false;
|
|
3982
|
+
}
|
|
3983
|
+
renderTemplate(template) {
|
|
3984
|
+
var _a;
|
|
3985
|
+
// When getting the host to render to, we start by looking
|
|
3986
|
+
// up the shadow root. If there isn't one, then that means
|
|
3987
|
+
// we're doing a Light DOM render to the element's direct children.
|
|
3988
|
+
const element = this.source;
|
|
3989
|
+
const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
|
|
3990
|
+
if (this.view !== null) {
|
|
3991
|
+
// If there's already a view, we need to unbind and remove through dispose.
|
|
3992
|
+
this.view.dispose();
|
|
3993
|
+
this.view = null;
|
|
3994
|
+
}
|
|
3995
|
+
else if (!this.needsInitialization || this.hasExistingShadowRoot) {
|
|
3996
|
+
this.hasExistingShadowRoot = false;
|
|
3997
|
+
// If there was previous custom rendering, we need to clear out the host.
|
|
3998
|
+
for (let child = host.firstChild; child !== null; child = host.firstChild) {
|
|
3999
|
+
host.removeChild(child);
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
if (template) {
|
|
4003
|
+
// If a new template was provided, render it.
|
|
4004
|
+
this.view = template.render(element, host, element);
|
|
4005
|
+
this.view.sourceLifetime =
|
|
4006
|
+
SourceLifetime.coupled;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* Locates or creates a controller for the specified element.
|
|
4011
|
+
* @param element - The element to return the controller for.
|
|
4012
|
+
* @remarks
|
|
4013
|
+
* The specified element must have a {@link FASTElementDefinition}
|
|
4014
|
+
* registered either through the use of the {@link customElement}
|
|
4015
|
+
* decorator or a call to `FASTElement.define`.
|
|
4016
|
+
*/
|
|
4017
|
+
static forCustomElement(element) {
|
|
4018
|
+
const controller = element.$fastController;
|
|
4019
|
+
if (controller !== void 0) {
|
|
4020
|
+
return controller;
|
|
4021
|
+
}
|
|
4022
|
+
const definition = FASTElementDefinition.getForInstance(element);
|
|
4023
|
+
if (definition === void 0) {
|
|
4024
|
+
throw FAST.error(1401 /* Message.missingElementDefinition */);
|
|
4025
|
+
}
|
|
4026
|
+
return (element.$fastController = new ElementController(element, definition));
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
4031
|
+
function createFASTElement(BaseType) {
|
|
4032
|
+
const type = class extends BaseType {
|
|
4033
|
+
constructor() {
|
|
4034
|
+
/* eslint-disable-next-line */
|
|
4035
|
+
super();
|
|
4036
|
+
ElementController.forCustomElement(this);
|
|
4037
|
+
}
|
|
4038
|
+
$emit(type, detail, options) {
|
|
4039
|
+
return this.$fastController.emit(type, detail, options);
|
|
4040
|
+
}
|
|
4041
|
+
connectedCallback() {
|
|
4042
|
+
this.$fastController.connect();
|
|
4043
|
+
}
|
|
4044
|
+
disconnectedCallback() {
|
|
4045
|
+
this.$fastController.disconnect();
|
|
4046
|
+
}
|
|
4047
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
4048
|
+
this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
|
|
4049
|
+
}
|
|
4050
|
+
};
|
|
4051
|
+
FASTElementDefinition.registerBaseType(type);
|
|
4052
|
+
return type;
|
|
4053
|
+
}
|
|
4054
|
+
function compose(type, nameOrDef) {
|
|
4055
|
+
if (isFunction(type)) {
|
|
4056
|
+
return FASTElementDefinition.compose(type, nameOrDef);
|
|
4057
|
+
}
|
|
4058
|
+
return FASTElementDefinition.compose(this, type);
|
|
4059
|
+
}
|
|
4060
|
+
function define(type, nameOrDef) {
|
|
4061
|
+
if (isFunction(type)) {
|
|
4062
|
+
return FASTElementDefinition.compose(type, nameOrDef).define().type;
|
|
4063
|
+
}
|
|
4064
|
+
return FASTElementDefinition.compose(this, type).define().type;
|
|
4065
|
+
}
|
|
4066
|
+
function from(BaseType) {
|
|
4067
|
+
return createFASTElement(BaseType);
|
|
4068
|
+
}
|
|
4069
|
+
/**
|
|
4070
|
+
* A minimal base class for FASTElements that also provides
|
|
4071
|
+
* static helpers for working with FASTElements.
|
|
4072
|
+
* @public
|
|
4073
|
+
*/
|
|
4074
|
+
const FASTElement = Object.assign(createFASTElement(HTMLElement), {
|
|
4075
|
+
/**
|
|
4076
|
+
* Creates a new FASTElement base class inherited from the
|
|
4077
|
+
* provided base type.
|
|
4078
|
+
* @param BaseType - The base element type to inherit from.
|
|
4079
|
+
*/
|
|
4080
|
+
from,
|
|
4081
|
+
/**
|
|
4082
|
+
* Defines a platform custom element based on the provided type and definition.
|
|
4083
|
+
* @param type - The custom element type to define.
|
|
4084
|
+
* @param nameOrDef - The name of the element to define or a definition object
|
|
4085
|
+
* that describes the element to define.
|
|
4086
|
+
*/
|
|
4087
|
+
define,
|
|
4088
|
+
/**
|
|
4089
|
+
* Defines metadata for a FASTElement which can be used to later define the element.
|
|
4090
|
+
* @public
|
|
4091
|
+
*/
|
|
4092
|
+
compose,
|
|
4093
|
+
});
|
|
4094
|
+
/**
|
|
4095
|
+
* Decorator: Defines a platform custom element based on `FASTElement`.
|
|
4096
|
+
* @param nameOrDef - The name of the element to define or a definition object
|
|
4097
|
+
* that describes the element to define.
|
|
4098
|
+
* @public
|
|
4099
|
+
*/
|
|
4100
|
+
function customElement(nameOrDef) {
|
|
4101
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
4102
|
+
return function (type) {
|
|
4103
|
+
define(type, nameOrDef);
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
export { AdoptedStyleSheetsStrategy, ArrayObserver, Aspect, AttributeConfiguration, AttributeDefinition, Binding, CSSDirective, ChildrenDirective, Compiler, DOM, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewBehaviorOrchestrator, ViewTemplate, attr, bind, booleanConverter, children, createMetadataLocator, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding, nullableNumberConverter, observable, oneTime, ref, repeat, slotted, volatile, when };
|