@lushly-dev/fast-element 3.0.1-preview.0 → 3.0.1-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/command/command.api.json +7857 -0
- package/dist/context/context.api.json +1084 -0
- package/dist/declarative/declarative.api.json +8130 -0
- package/dist/definition-schema/definition-schema.api.json +2689 -0
- package/dist/di/di.api.json +4945 -0
- package/dist/dts/arrays.d.ts +2 -0
- package/dist/dts/binding/binding.d.ts +59 -0
- package/dist/dts/binding/exports.d.ts +9 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/binding/signal.d.ts +36 -0
- package/dist/dts/binding/two-way.d.ts +44 -0
- package/dist/dts/command/behavior.d.ts +94 -0
- package/dist/dts/command/behaviors/confirm-behavior.d.ts +139 -0
- package/dist/dts/command/behaviors/undo-behavior.d.ts +178 -0
- package/dist/dts/command/command.d.ts +79 -0
- package/dist/dts/command/component-registry.d.ts +50 -0
- package/dist/dts/command/components/confirm-dialog.d.ts +117 -0
- package/dist/dts/command/index.d.ts +21 -0
- package/dist/dts/command/registry.d.ts +201 -0
- package/dist/dts/command/schema-validation.d.ts +45 -0
- package/dist/dts/command/types.d.ts +332 -0
- package/dist/dts/command/validation.d.ts +116 -0
- package/dist/dts/command/weak-ref.d.ts +11 -0
- package/dist/dts/components/attributes.d.ts +153 -0
- package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
- package/dist/dts/components/element-controller.d.ts +349 -0
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/enable-hydration.d.ts +66 -0
- package/dist/dts/components/fast-definitions.d.ts +211 -0
- package/dist/dts/components/fast-element-registry.d.ts +23 -0
- package/dist/dts/components/fast-element.d.ts +91 -0
- package/dist/dts/components/hydration-tracker.d.ts +110 -0
- package/dist/dts/components/hydration.d.ts +52 -0
- package/dist/dts/components/schema.d.ts +205 -0
- package/dist/dts/context.d.ts +171 -0
- package/dist/dts/debug.d.ts +5 -0
- package/dist/dts/declarative/attribute-map.d.ts +58 -0
- package/dist/dts/declarative/debug.d.ts +4 -0
- package/dist/dts/declarative/index.d.ts +14 -0
- package/dist/dts/declarative/interfaces.d.ts +8 -0
- package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
- package/dist/dts/declarative/observer-map.d.ts +89 -0
- package/dist/dts/declarative/runtime.d.ts +5 -0
- package/dist/dts/declarative/syntax.d.ts +21 -0
- package/dist/dts/declarative/template-bridge.d.ts +33 -0
- package/dist/dts/declarative/template-parser.d.ts +98 -0
- package/dist/dts/declarative/template.d.ts +9 -0
- package/dist/dts/declarative/utilities.d.ts +358 -0
- package/dist/dts/definition-schema/api.d.ts +42 -0
- package/dist/dts/definition-schema/cem.d.ts +78 -0
- package/dist/dts/definition-schema/extension.d.ts +18 -0
- package/dist/dts/definition-schema/extract.d.ts +38 -0
- package/dist/dts/definition-schema/index.d.ts +9 -0
- package/dist/dts/definition-schema/storage.d.ts +35 -0
- package/dist/dts/definition-schema/types.d.ts +176 -0
- package/dist/dts/dev-mode.d.ts +13 -0
- package/dist/dts/di/di.d.ts +894 -0
- package/dist/dts/dom-policy.d.ts +101 -0
- package/dist/dts/dom.d.ts +88 -0
- package/dist/dts/hydration/diagnostics.d.ts +93 -0
- package/dist/dts/hydration/exports.d.ts +18 -0
- package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
- package/dist/dts/hydration/messages.d.ts +62 -0
- package/dist/dts/hydration/runtime.d.ts +7 -0
- package/dist/dts/hydration/target-builder.d.ts +107 -0
- package/dist/dts/index.d.ts +51 -0
- package/dist/dts/index.debug.d.ts +1 -0
- package/dist/dts/index.rollup.d.ts +1 -0
- package/dist/dts/index.rollup.debug.d.ts +1 -0
- package/dist/dts/interfaces.d.ts +106 -0
- package/dist/dts/metadata.d.ts +26 -0
- package/dist/dts/observation/arrays.d.ts +248 -0
- package/dist/dts/observation/notifier.d.ts +127 -0
- package/dist/dts/observation/observable.d.ts +276 -0
- package/dist/dts/observation/update-queue.d.ts +40 -0
- package/dist/dts/observation/volatile.d.ts +9 -0
- package/dist/dts/platform.d.ts +87 -0
- package/dist/dts/registry.d.ts +2 -0
- package/dist/dts/render.d.ts +7 -0
- package/dist/dts/schema.d.ts +1 -0
- package/dist/dts/scoped/detection.d.ts +55 -0
- package/dist/dts/scoped/index.d.ts +68 -0
- package/dist/dts/scoped/mixin.d.ts +118 -0
- package/dist/dts/scoped/registry.d.ts +76 -0
- package/dist/dts/scoped/types.d.ts +48 -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 +50 -0
- package/dist/dts/styles/css.d.ts +34 -0
- package/dist/dts/styles/element-styles.d.ts +61 -0
- package/dist/dts/styles/host.d.ts +65 -0
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/children.d.ts +62 -0
- package/dist/dts/templating/compiler.d.ts +54 -0
- package/dist/dts/templating/html-binding-directive.d.ts +155 -0
- package/dist/dts/templating/html-directive.d.ts +218 -0
- package/dist/dts/templating/html.d.ts +1 -0
- package/dist/dts/templating/hydration-view.d.ts +130 -0
- package/dist/dts/templating/markup.d.ts +48 -0
- package/dist/dts/templating/node-observation.d.ts +99 -0
- package/dist/dts/templating/ref.d.ts +23 -0
- package/dist/dts/templating/render.d.ts +318 -0
- package/dist/dts/templating/repeat.d.ts +131 -0
- package/dist/dts/templating/slotted.d.ts +38 -0
- package/dist/dts/templating/template.d.ts +221 -0
- package/dist/dts/templating/view.d.ts +268 -0
- package/dist/dts/templating/when.d.ts +12 -0
- package/dist/dts/testing/exports.d.ts +3 -0
- package/dist/dts/testing/fakes.d.ts +15 -0
- package/dist/dts/testing/fixture.d.ts +84 -0
- package/dist/dts/testing/models.d.ts +20 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/dts/trust/index.d.ts +8 -0
- package/dist/dts/trust/types.d.ts +48 -0
- package/dist/dts/trust/utils.d.ts +26 -0
- package/dist/dts/tsdoc-metadata.json +11 -0
- package/dist/dts/utilities.d.ts +76 -0
- package/dist/esm/arrays.js +1 -0
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/exports.js +6 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/binding/signal.js +99 -0
- package/dist/esm/binding/two-way.js +110 -0
- package/dist/esm/command/behavior.js +155 -0
- package/dist/esm/command/behaviors/confirm-behavior.js +225 -0
- package/dist/esm/command/behaviors/undo-behavior.js +312 -0
- package/dist/esm/command/command.js +104 -0
- package/dist/esm/command/component-registry.js +107 -0
- package/dist/esm/command/components/confirm-dialog.js +370 -0
- package/dist/esm/command/index.js +22 -0
- package/dist/esm/command/registry.js +752 -0
- package/dist/esm/command/schema-validation.js +194 -0
- package/dist/esm/command/types.js +86 -0
- package/dist/esm/command/validation.js +511 -0
- package/dist/esm/command/weak-ref.js +5 -0
- package/dist/esm/components/attributes.js +219 -0
- package/dist/esm/components/definition-schema-transforms.js +23 -0
- package/dist/esm/components/element-controller.js +839 -0
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/enable-hydration.js +123 -0
- package/dist/esm/components/fast-definitions.js +294 -0
- package/dist/esm/components/fast-element-registry.js +67 -0
- package/dist/esm/components/fast-element.js +82 -0
- package/dist/esm/components/hydration-tracker.js +210 -0
- package/dist/esm/components/hydration.js +187 -0
- package/dist/esm/components/schema.js +253 -0
- package/dist/esm/context.js +185 -0
- package/dist/esm/debug.js +71 -0
- package/dist/esm/declarative/attribute-map.js +121 -0
- package/dist/esm/declarative/debug.js +4 -0
- package/dist/esm/declarative/index.js +10 -0
- package/dist/esm/declarative/interfaces.js +9 -0
- package/dist/esm/declarative/observer-map-utilities.js +565 -0
- package/dist/esm/declarative/observer-map.js +216 -0
- package/dist/esm/declarative/runtime.js +14 -0
- package/dist/esm/declarative/syntax.js +36 -0
- package/dist/esm/declarative/template-bridge.js +160 -0
- package/dist/esm/declarative/template-parser.js +306 -0
- package/dist/esm/declarative/template.js +116 -0
- package/dist/esm/declarative/utilities.js +1069 -0
- package/dist/esm/definition-schema/api.js +129 -0
- package/dist/esm/definition-schema/cem.js +89 -0
- package/dist/esm/definition-schema/extension.js +53 -0
- package/dist/esm/definition-schema/extract.js +167 -0
- package/dist/esm/definition-schema/index.js +8 -0
- package/dist/esm/definition-schema/storage.js +58 -0
- package/dist/esm/definition-schema/types.js +8 -0
- package/dist/esm/dev-mode.js +22 -0
- package/dist/esm/di/di.js +1440 -0
- package/dist/esm/dom-policy.js +374 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/diagnostics.js +50 -0
- package/dist/esm/hydration/exports.js +6 -0
- package/dist/esm/hydration/hydration-debugger.js +112 -0
- package/dist/esm/hydration/messages.js +84 -0
- package/dist/esm/hydration/runtime.js +33 -0
- package/dist/esm/hydration/target-builder.js +264 -0
- package/dist/esm/index.debug.js +3 -0
- package/dist/esm/index.js +49 -0
- package/dist/esm/index.rollup.debug.js +6 -0
- package/dist/esm/index.rollup.js +4 -0
- package/dist/esm/interfaces.js +55 -0
- package/dist/esm/metadata.js +63 -0
- package/dist/esm/observation/arrays.js +661 -0
- package/dist/esm/observation/notifier.js +164 -0
- package/dist/esm/observation/observable.js +324 -0
- package/dist/esm/observation/update-queue.js +56 -0
- package/dist/esm/observation/volatile.js +16 -0
- package/dist/esm/platform.js +98 -0
- package/dist/esm/registry.js +2 -0
- package/dist/esm/render.js +1 -0
- package/dist/esm/schema.js +1 -0
- package/dist/esm/scoped/detection.js +102 -0
- package/dist/esm/scoped/index.js +69 -0
- package/dist/esm/scoped/mixin.js +127 -0
- package/dist/esm/scoped/registry.js +125 -0
- package/dist/esm/scoped/types.js +4 -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 +36 -0
- package/dist/esm/styles/css.js +56 -0
- package/dist/esm/styles/element-styles.js +144 -0
- package/dist/esm/styles/host.js +1 -0
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +67 -0
- package/dist/esm/templating/compiler.js +359 -0
- package/dist/esm/templating/html-binding-directive.js +288 -0
- package/dist/esm/templating/html-directive.js +113 -0
- package/dist/esm/templating/html.js +1 -0
- package/dist/esm/templating/hydration-view.js +228 -0
- package/dist/esm/templating/markup.js +83 -0
- package/dist/esm/templating/node-observation.js +84 -0
- package/dist/esm/templating/ref.js +21 -0
- package/dist/esm/templating/render.js +468 -0
- package/dist/esm/templating/repeat.js +434 -0
- package/dist/esm/templating/slotted.js +49 -0
- package/dist/esm/templating/template.js +206 -0
- package/dist/esm/templating/view.js +298 -0
- package/dist/esm/templating/when.js +22 -0
- package/dist/esm/testing/exports.js +3 -0
- package/dist/esm/testing/fakes.js +107 -0
- package/dist/esm/testing/fixture.js +86 -0
- package/dist/esm/testing/models.js +58 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/trust/index.js +7 -0
- package/dist/esm/trust/types.js +6 -0
- package/dist/esm/trust/utils.js +126 -0
- package/dist/esm/utilities.js +140 -0
- package/dist/fast-element.api.json +26798 -0
- package/dist/fast-element.d.ts +3985 -0
- package/dist/fast-element.debug.js +7618 -0
- package/dist/fast-element.debug.min.js +3 -0
- package/dist/fast-element.js +7617 -0
- package/dist/fast-element.min.js +3 -0
- package/dist/fast-element.untrimmed.d.ts +4103 -0
- package/dist/observation/arrays/arrays.api.json +2621 -0
- package/dist/scoped/scoped.api.json +891 -0
- package/dist/trust/trust.api.json +629 -0
- package/package.json +12 -12
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { isHydratable } from "../components/hydration.js";
|
|
2
|
+
import { DOM, DOMAspect } from "../dom.js";
|
|
3
|
+
import { Message } from "../interfaces.js";
|
|
4
|
+
import { ExecutionContext, } from "../observation/observable.js";
|
|
5
|
+
import { FAST } from "../platform.js";
|
|
6
|
+
import { HTMLDirective, } from "./html-directive.js";
|
|
7
|
+
import { HydrationStage } from "./hydration-view.js";
|
|
8
|
+
import { Markup } from "./markup.js";
|
|
9
|
+
import { createChildView } from "./view.js";
|
|
10
|
+
function isContentTemplate(value) {
|
|
11
|
+
return value.create !== undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sink function for DOMAspect.content bindings (text content interpolation).
|
|
15
|
+
* Handles two cases:
|
|
16
|
+
* - If the value is a ContentTemplate (has a create() method), it composes a child
|
|
17
|
+
* view into the DOM, managing view lifecycle (create/reuse/remove/bind).
|
|
18
|
+
* - If the value is a primitive, it sets target.textContent directly, first removing
|
|
19
|
+
* any previously composed view.
|
|
20
|
+
*/
|
|
21
|
+
function updateContent(target, aspect, value, controller) {
|
|
22
|
+
// If there's no actual value, then this equates to the
|
|
23
|
+
// empty string for the purposes of content bindings.
|
|
24
|
+
if (value === null || value === undefined) {
|
|
25
|
+
value = "";
|
|
26
|
+
}
|
|
27
|
+
// If the value has a "create" method, then it's a ContentTemplate.
|
|
28
|
+
if (isContentTemplate(value)) {
|
|
29
|
+
target.textContent = "";
|
|
30
|
+
let view = target.$fastView;
|
|
31
|
+
// If there's no previous view that we might be able to
|
|
32
|
+
// reuse then create a new view from the template.
|
|
33
|
+
if (view === void 0) {
|
|
34
|
+
if (isHydratable(controller) &&
|
|
35
|
+
isHydratable(value) &&
|
|
36
|
+
controller.bindingViewBoundaries[this.targetNodeId] !== undefined &&
|
|
37
|
+
controller.hydrationStage !== HydrationStage.hydrated) {
|
|
38
|
+
const viewNodes = controller.bindingViewBoundaries[this.targetNodeId];
|
|
39
|
+
view = value.hydrate(viewNodes.first, viewNodes.last);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
view = createChildView(value, controller);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// If there is a previous view, but it wasn't created
|
|
47
|
+
// from the same template as the new value, then we
|
|
48
|
+
// need to remove the old view if it's still in the DOM
|
|
49
|
+
// and create a new view from the template.
|
|
50
|
+
if (target.$fastTemplate !== value) {
|
|
51
|
+
if (view.isComposed) {
|
|
52
|
+
view.remove();
|
|
53
|
+
view.unbind();
|
|
54
|
+
}
|
|
55
|
+
view = createChildView(value, controller);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// It's possible that the value is the same as the previous template
|
|
59
|
+
// and that there's actually no need to compose it.
|
|
60
|
+
if (!view.isComposed) {
|
|
61
|
+
view.isComposed = true;
|
|
62
|
+
view.bind(controller.source, controller.context);
|
|
63
|
+
view.insertBefore(target);
|
|
64
|
+
target.$fastView = view;
|
|
65
|
+
target.$fastTemplate = value;
|
|
66
|
+
}
|
|
67
|
+
else if (view.needsBindOnly) {
|
|
68
|
+
view.needsBindOnly = false;
|
|
69
|
+
view.bind(controller.source, controller.context);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const view = target.$fastView;
|
|
74
|
+
// If there is a view and it's currently composed into
|
|
75
|
+
// the DOM, then we need to remove it.
|
|
76
|
+
if (view !== void 0 && view.isComposed) {
|
|
77
|
+
view.isComposed = false;
|
|
78
|
+
view.remove();
|
|
79
|
+
if (view.needsBindOnly) {
|
|
80
|
+
view.needsBindOnly = false;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
view.unbind();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
target.textContent = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sink function for DOMAspect.tokenList bindings (e.g., :classList).
|
|
91
|
+
* Uses a versioning scheme to efficiently track which CSS classes were added
|
|
92
|
+
* in the current update vs. the previous one. Classes from the previous version
|
|
93
|
+
* that aren't present in the new value are automatically removed.
|
|
94
|
+
*/
|
|
95
|
+
function updateTokenList(target, aspect, value) {
|
|
96
|
+
var _a;
|
|
97
|
+
const lookup = `${this.id}-t`;
|
|
98
|
+
const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
|
|
99
|
+
const classVersions = state.cv;
|
|
100
|
+
let version = state.v;
|
|
101
|
+
const tokenList = target[aspect];
|
|
102
|
+
// Add the classes, tracking the version at which they were added.
|
|
103
|
+
if (value !== null && value !== undefined && value.length) {
|
|
104
|
+
const names = value.split(/\s+/);
|
|
105
|
+
for (let i = 0, ii = names.length; i < ii; ++i) {
|
|
106
|
+
const currentName = names[i];
|
|
107
|
+
if (currentName === "") {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
classVersions[currentName] = version;
|
|
111
|
+
tokenList.add(currentName);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
state.v = version + 1;
|
|
115
|
+
// If this is the first call to add classes, there's no need to remove old ones.
|
|
116
|
+
if (version === 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Remove classes from the previous version.
|
|
120
|
+
version -= 1;
|
|
121
|
+
for (const name in classVersions) {
|
|
122
|
+
if (classVersions[name] === version) {
|
|
123
|
+
tokenList.remove(name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Maps each DOMAspect type to its corresponding DOM update ("sink") function.
|
|
129
|
+
* When a binding value changes, the sink function for the binding's aspect type
|
|
130
|
+
* is called to push the new value into the DOM. Events are handled separately
|
|
131
|
+
* via addEventListener in bind(), so the event sink is a no-op.
|
|
132
|
+
*/
|
|
133
|
+
const sinkLookup = {
|
|
134
|
+
[DOMAspect.attribute]: DOM.setAttribute,
|
|
135
|
+
[DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
|
|
136
|
+
[DOMAspect.property]: (t, a, v) => (t[a] = v),
|
|
137
|
+
[DOMAspect.content]: updateContent,
|
|
138
|
+
[DOMAspect.tokenList]: updateTokenList,
|
|
139
|
+
[DOMAspect.event]: () => void 0,
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* The central binding directive that bridges data expressions and DOM updates.
|
|
143
|
+
*
|
|
144
|
+
* HTMLBindingDirective fulfills three roles simultaneously:
|
|
145
|
+
* - **HTMLDirective**: Produces placeholder HTML via createHTML() during template authoring.
|
|
146
|
+
* - **ViewBehaviorFactory**: Creates behaviors (returns itself) during view creation.
|
|
147
|
+
* - **ViewBehavior / EventListener**: Attaches to a DOM node during bind, manages
|
|
148
|
+
* expression observers for reactive updates, and handles DOM events directly.
|
|
149
|
+
*
|
|
150
|
+
* The aspectType (set by HTMLDirective.assignAspect during template processing)
|
|
151
|
+
* determines which DOM "sink" function is used to apply values — e.g.,
|
|
152
|
+
* setAttribute for attributes, addEventListener for events, textContent for content.
|
|
153
|
+
*
|
|
154
|
+
* @public
|
|
155
|
+
*/
|
|
156
|
+
export class HTMLBindingDirective {
|
|
157
|
+
/**
|
|
158
|
+
* Creates an instance of HTMLBindingDirective.
|
|
159
|
+
* @param dataBinding - The binding configuration to apply.
|
|
160
|
+
*/
|
|
161
|
+
constructor(dataBinding) {
|
|
162
|
+
this.updateTarget = null;
|
|
163
|
+
/**
|
|
164
|
+
* The type of aspect to target.
|
|
165
|
+
*/
|
|
166
|
+
this.aspectType = DOMAspect.content;
|
|
167
|
+
this.dataBinding = dataBinding;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Creates HTML to be used within a template.
|
|
171
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
172
|
+
*/
|
|
173
|
+
createHTML(add) {
|
|
174
|
+
return Markup.interpolation(add(this));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Creates a behavior.
|
|
178
|
+
*/
|
|
179
|
+
createBehavior() {
|
|
180
|
+
var _a;
|
|
181
|
+
if (this.updateTarget === null) {
|
|
182
|
+
const sink = sinkLookup[this.aspectType];
|
|
183
|
+
const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
|
|
184
|
+
if (!sink) {
|
|
185
|
+
throw FAST.error(Message.unsupportedBindingBehavior);
|
|
186
|
+
}
|
|
187
|
+
this.data = `${this.id}-d`;
|
|
188
|
+
this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
|
|
189
|
+
}
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Attaches this binding to its target DOM node.
|
|
194
|
+
* - For events: stores the controller reference on the target element and registers
|
|
195
|
+
* this directive as the EventListener via addEventListener. The directive's
|
|
196
|
+
* handleEvent() method will be called when the event fires.
|
|
197
|
+
* - For content bindings: registers an unbind handler, then falls through to the
|
|
198
|
+
* default path.
|
|
199
|
+
* - For all non-event bindings: creates (or reuses) an ExpressionObserver, evaluates
|
|
200
|
+
* the binding expression, and applies the result to the DOM via the updateTarget
|
|
201
|
+
* sink function. The observer will call handleChange() on future data changes.
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
bind(controller) {
|
|
205
|
+
var _a;
|
|
206
|
+
const target = controller.targets[this.targetNodeId];
|
|
207
|
+
switch (this.aspectType) {
|
|
208
|
+
case DOMAspect.event:
|
|
209
|
+
target[this.data] = controller;
|
|
210
|
+
target.addEventListener(this.targetAspect, this, this.dataBinding.options);
|
|
211
|
+
break;
|
|
212
|
+
case DOMAspect.content:
|
|
213
|
+
controller.onUnbind(this);
|
|
214
|
+
// intentional fall through
|
|
215
|
+
default: {
|
|
216
|
+
const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
|
|
217
|
+
observer.target = target;
|
|
218
|
+
observer.controller = controller;
|
|
219
|
+
// Evaluate to establish dependency tracking
|
|
220
|
+
const value = observer.bind(controller);
|
|
221
|
+
// Skip DOM update for attribute bindings when prerendered —
|
|
222
|
+
// the server has already rendered the correct attribute values.
|
|
223
|
+
// Content, property, and tokenList bindings must still run
|
|
224
|
+
// so structural directives, host properties, and class lists
|
|
225
|
+
// are properly initialized.
|
|
226
|
+
if (controller._skipAttrUpdates &&
|
|
227
|
+
(this.aspectType === DOMAspect.attribute ||
|
|
228
|
+
this.aspectType === DOMAspect.booleanAttribute)) {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
this.updateTarget(target, this.targetAspect, value, controller);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** @internal */
|
|
237
|
+
unbind(controller) {
|
|
238
|
+
const target = controller.targets[this.targetNodeId];
|
|
239
|
+
const view = target.$fastView;
|
|
240
|
+
if (view !== void 0 && view.isComposed) {
|
|
241
|
+
view.unbind();
|
|
242
|
+
view.needsBindOnly = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Implements the EventListener interface. When a DOM event fires on the target
|
|
247
|
+
* element, this method retrieves the ViewController stored on the element,
|
|
248
|
+
* sets the event on the ExecutionContext so `c.event` is available to the
|
|
249
|
+
* binding expression, and evaluates the expression. If the expression returns
|
|
250
|
+
* anything other than `true`, the event's default action is prevented.
|
|
251
|
+
* @internal
|
|
252
|
+
*/
|
|
253
|
+
handleEvent(event) {
|
|
254
|
+
const controller = event.currentTarget[this.data];
|
|
255
|
+
if (controller.isBound) {
|
|
256
|
+
ExecutionContext.setEvent(event);
|
|
257
|
+
const result = this.dataBinding.evaluate(controller.source, controller.context);
|
|
258
|
+
ExecutionContext.setEvent(null);
|
|
259
|
+
if (result !== true) {
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Called by the ExpressionObserver when a tracked dependency changes.
|
|
266
|
+
* Re-evaluates the binding expression via observer.bind() and pushes
|
|
267
|
+
* the new value to the DOM through the updateTarget sink function.
|
|
268
|
+
* This is the reactive update path that keeps the DOM in sync with data.
|
|
269
|
+
*
|
|
270
|
+
* Guards against stale notifications: when a view is unbound (e.g., after
|
|
271
|
+
* a parent `when` directive tears down a child element), coupled-lifetime
|
|
272
|
+
* observers may still hold active subscriptions. If a property change fires
|
|
273
|
+
* on the source element while the view is inactive, this guard prevents
|
|
274
|
+
* the binding expression from evaluating with a null source.
|
|
275
|
+
* @internal
|
|
276
|
+
*/
|
|
277
|
+
handleChange(binding, observer) {
|
|
278
|
+
const controller = observer.controller;
|
|
279
|
+
// https://github.com/microsoft/fast/issues/7444
|
|
280
|
+
// This guard will be reconsidered in the next major version.
|
|
281
|
+
if (!controller.isBound) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const target = observer.target;
|
|
285
|
+
this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
HTMLDirective.define(HTMLBindingDirective, { aspected: true });
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { DOMAspect } from "../dom.js";
|
|
2
|
+
import { createTypeRegistry, makeSerializationNoop } from "../platform.js";
|
|
3
|
+
import { Markup } from "./markup.js";
|
|
4
|
+
const registry = createTypeRegistry();
|
|
5
|
+
/**
|
|
6
|
+
* Instructs the template engine to apply behavior to a node.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export const HTMLDirective = Object.freeze({
|
|
10
|
+
/**
|
|
11
|
+
* Gets the directive definition associated with the instance.
|
|
12
|
+
* @param instance - The directive instance to retrieve the definition for.
|
|
13
|
+
*/
|
|
14
|
+
getForInstance: registry.getForInstance,
|
|
15
|
+
/**
|
|
16
|
+
* Gets the directive definition associated with the specified type.
|
|
17
|
+
* @param type - The directive type to retrieve the definition for.
|
|
18
|
+
*/
|
|
19
|
+
getByType: registry.getByType,
|
|
20
|
+
/**
|
|
21
|
+
* Defines an HTMLDirective based on the options.
|
|
22
|
+
* @param type - The type to define as a directive.
|
|
23
|
+
* @param options - Options that specify the directive's application.
|
|
24
|
+
*/
|
|
25
|
+
define(type, options) {
|
|
26
|
+
options = options || {};
|
|
27
|
+
options.type = type;
|
|
28
|
+
registry.register(options);
|
|
29
|
+
return type;
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Determines the DOM aspect type for a directive based on attribute name prefix.
|
|
33
|
+
* The prefix convention maps to aspect types as follows:
|
|
34
|
+
* - No prefix (e.g. "class") → DOMAspect.attribute
|
|
35
|
+
* - ":" prefix (e.g. ":value") → DOMAspect.property (":classList" → DOMAspect.tokenList)
|
|
36
|
+
* - "?" prefix (e.g. "?disabled") → DOMAspect.booleanAttribute
|
|
37
|
+
* - `@` prefix (e.g. `@click`) → DOMAspect.event
|
|
38
|
+
* - Falsy or absent value → DOMAspect.content (see remarks)
|
|
39
|
+
* @param directive - The directive to assign the aspect to.
|
|
40
|
+
* @param value - The value to base the aspect determination on.
|
|
41
|
+
* @remarks
|
|
42
|
+
* If a falsy value is provided, then the content aspect will be assigned.
|
|
43
|
+
*/
|
|
44
|
+
assignAspect(directive, value) {
|
|
45
|
+
if (!value) {
|
|
46
|
+
directive.aspectType = DOMAspect.content;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
directive.sourceAspect = value;
|
|
50
|
+
switch (value[0]) {
|
|
51
|
+
case ":":
|
|
52
|
+
directive.targetAspect = value.substring(1);
|
|
53
|
+
directive.aspectType =
|
|
54
|
+
directive.targetAspect === "classList"
|
|
55
|
+
? DOMAspect.tokenList
|
|
56
|
+
: DOMAspect.property;
|
|
57
|
+
break;
|
|
58
|
+
case "?":
|
|
59
|
+
directive.targetAspect = value.substring(1);
|
|
60
|
+
directive.aspectType = DOMAspect.booleanAttribute;
|
|
61
|
+
break;
|
|
62
|
+
case "@":
|
|
63
|
+
directive.targetAspect = value.substring(1);
|
|
64
|
+
directive.aspectType = DOMAspect.event;
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
directive.targetAspect = value;
|
|
68
|
+
directive.aspectType = DOMAspect.attribute;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Decorator: Defines an HTMLDirective.
|
|
75
|
+
* @param options - Provides options that specify the directive's application.
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export function htmlDirective(options) {
|
|
79
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
80
|
+
return function (type) {
|
|
81
|
+
HTMLDirective.define(type, options);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A base class used for attribute directives that don't need internal state.
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
export class StatelessAttachedAttributeDirective {
|
|
89
|
+
/**
|
|
90
|
+
* Creates an instance of RefDirective.
|
|
91
|
+
* @param options - The options to use in configuring the directive.
|
|
92
|
+
*/
|
|
93
|
+
constructor(options) {
|
|
94
|
+
this.options = options;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Creates a placeholder string based on the directive's index within the template.
|
|
98
|
+
* @param index - The index of the directive within the template.
|
|
99
|
+
* @remarks
|
|
100
|
+
* Creates a custom attribute placeholder.
|
|
101
|
+
*/
|
|
102
|
+
createHTML(add) {
|
|
103
|
+
return Markup.attribute(add(this));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Creates a behavior.
|
|
107
|
+
* @param targets - The targets available for behaviors to be attached to.
|
|
108
|
+
*/
|
|
109
|
+
createBehavior() {
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
makeSerializationNoop(StatelessAttachedAttributeDirective);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { html } from "./template.js";
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { Hydratable } from "../components/hydration.js";
|
|
3
|
+
import { getHostName, getHydrationDiagnostic, } from "../hydration/diagnostics.js";
|
|
4
|
+
import { buildViewBindingTargets, createRangeForNodes, HydrationTargetElementError, targetFactory, } from "../hydration/target-builder.js";
|
|
5
|
+
import { SourceLifetime } from "../observation/observable.js";
|
|
6
|
+
import { makeSerializationNoop } from "../platform.js";
|
|
7
|
+
import { DefaultExecutionContext, removeNodeSequence, } from "./view.js";
|
|
8
|
+
/** @public */
|
|
9
|
+
export const HydrationStage = {
|
|
10
|
+
unhydrated: "unhydrated",
|
|
11
|
+
hydrating: "hydrating",
|
|
12
|
+
hydrated: "hydrated",
|
|
13
|
+
};
|
|
14
|
+
/** @public */
|
|
15
|
+
export class HydrationBindingError extends Error {
|
|
16
|
+
constructor(
|
|
17
|
+
/**
|
|
18
|
+
* The error message
|
|
19
|
+
*/
|
|
20
|
+
message,
|
|
21
|
+
/**
|
|
22
|
+
* The factory that was unable to be bound
|
|
23
|
+
*/
|
|
24
|
+
factory,
|
|
25
|
+
/**
|
|
26
|
+
* A DocumentFragment containing a clone of the
|
|
27
|
+
* view's Nodes.
|
|
28
|
+
*/
|
|
29
|
+
fragment,
|
|
30
|
+
/**
|
|
31
|
+
* String representation of the HTML in the template that
|
|
32
|
+
* threw the binding error.
|
|
33
|
+
*/
|
|
34
|
+
templateString,
|
|
35
|
+
/**
|
|
36
|
+
* Structured description of the binding the hydration walk was
|
|
37
|
+
* attempting to apply when the mismatch was detected.
|
|
38
|
+
*/
|
|
39
|
+
expected,
|
|
40
|
+
/**
|
|
41
|
+
* Structured description of the server-rendered DOM that was
|
|
42
|
+
* encountered at the mismatch point.
|
|
43
|
+
*/
|
|
44
|
+
received) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.factory = factory;
|
|
47
|
+
this.fragment = fragment;
|
|
48
|
+
this.templateString = templateString;
|
|
49
|
+
this.expected = expected;
|
|
50
|
+
this.received = received;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class HydrationView extends DefaultExecutionContext {
|
|
54
|
+
get hydrationStage() {
|
|
55
|
+
return this._hydrationStage;
|
|
56
|
+
}
|
|
57
|
+
get targets() {
|
|
58
|
+
return this._targets;
|
|
59
|
+
}
|
|
60
|
+
get bindingViewBoundaries() {
|
|
61
|
+
return this._bindingViewBoundaries;
|
|
62
|
+
}
|
|
63
|
+
constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
|
|
64
|
+
super();
|
|
65
|
+
this.firstChild = firstChild;
|
|
66
|
+
this.lastChild = lastChild;
|
|
67
|
+
this.sourceTemplate = sourceTemplate;
|
|
68
|
+
this.hostBindingTarget = hostBindingTarget;
|
|
69
|
+
this[_a] = Hydratable;
|
|
70
|
+
this.context = this;
|
|
71
|
+
this.source = null;
|
|
72
|
+
this.isBound = false;
|
|
73
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
74
|
+
this.unbindables = [];
|
|
75
|
+
this.fragment = null;
|
|
76
|
+
this.behaviors = null;
|
|
77
|
+
this._hydrationStage = HydrationStage.unhydrated;
|
|
78
|
+
this._bindingViewBoundaries = {};
|
|
79
|
+
this._targets = {};
|
|
80
|
+
this.factories = sourceTemplate.compile().factories;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* no-op. Hydrated views are don't need to be moved from a documentFragment
|
|
84
|
+
* to the target node.
|
|
85
|
+
*/
|
|
86
|
+
insertBefore(node) {
|
|
87
|
+
// No-op in cases where this is called before the view is removed,
|
|
88
|
+
// because the nodes will already be in the document and just need hydrating.
|
|
89
|
+
if (this.fragment === null) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (this.fragment.hasChildNodes()) {
|
|
93
|
+
node.parentNode.insertBefore(this.fragment, node);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const end = this.lastChild;
|
|
97
|
+
if (node.previousSibling === end)
|
|
98
|
+
return;
|
|
99
|
+
const parentNode = node.parentNode;
|
|
100
|
+
let current = this.firstChild;
|
|
101
|
+
let next;
|
|
102
|
+
while (current !== end) {
|
|
103
|
+
next = current.nextSibling;
|
|
104
|
+
parentNode.insertBefore(current, node);
|
|
105
|
+
current = next;
|
|
106
|
+
}
|
|
107
|
+
parentNode.insertBefore(end, node);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Appends the view to a node. In cases where this is called before the
|
|
112
|
+
* view has been removed, the method will no-op.
|
|
113
|
+
* @param node - the node to append the view to.
|
|
114
|
+
*/
|
|
115
|
+
appendTo(node) {
|
|
116
|
+
if (this.fragment !== null) {
|
|
117
|
+
node.appendChild(this.fragment);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
remove() {
|
|
121
|
+
const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
|
|
122
|
+
const end = this.lastChild;
|
|
123
|
+
let current = this.firstChild;
|
|
124
|
+
let next;
|
|
125
|
+
while (current !== end) {
|
|
126
|
+
next = current.nextSibling;
|
|
127
|
+
if (!next) {
|
|
128
|
+
throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
|
|
129
|
+
}
|
|
130
|
+
fragment.appendChild(current);
|
|
131
|
+
current = next;
|
|
132
|
+
}
|
|
133
|
+
fragment.appendChild(end);
|
|
134
|
+
}
|
|
135
|
+
bind(source, context = this) {
|
|
136
|
+
if (this.source === source && this.context === context) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (this.hydrationStage !== HydrationStage.hydrated) {
|
|
140
|
+
this._hydrationStage = HydrationStage.hydrating;
|
|
141
|
+
}
|
|
142
|
+
let behaviors = this.behaviors;
|
|
143
|
+
if (behaviors === null) {
|
|
144
|
+
this.source = source;
|
|
145
|
+
this.context = context;
|
|
146
|
+
try {
|
|
147
|
+
const { targets, boundaries } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
|
|
148
|
+
this._targets = targets;
|
|
149
|
+
this._bindingViewBoundaries = boundaries;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof HydrationTargetElementError) {
|
|
153
|
+
let templateString = this.sourceTemplate.html;
|
|
154
|
+
if (typeof templateString !== "string") {
|
|
155
|
+
templateString = templateString.innerHTML;
|
|
156
|
+
}
|
|
157
|
+
error.templateString = templateString;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
this.behaviors = behaviors = new Array(this.factories.length);
|
|
162
|
+
const factories = this.factories;
|
|
163
|
+
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
164
|
+
const factory = factories[i];
|
|
165
|
+
if (factory.targetNodeId === "h" && this.hostBindingTarget) {
|
|
166
|
+
targetFactory(factory, this.hostBindingTarget, this._targets);
|
|
167
|
+
}
|
|
168
|
+
// If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
|
|
169
|
+
if (factory.targetNodeId in this.targets) {
|
|
170
|
+
const behavior = factory.createBehavior();
|
|
171
|
+
behavior.bind(this);
|
|
172
|
+
behaviors[i] = behavior;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
let templateString = this.sourceTemplate.html;
|
|
176
|
+
if (typeof templateString !== "string") {
|
|
177
|
+
templateString = templateString.innerHTML;
|
|
178
|
+
}
|
|
179
|
+
const fragment = createRangeForNodes(this.firstChild, this.lastChild).cloneContents();
|
|
180
|
+
const result = getHydrationDiagnostic().formatBindingMismatch(factory, this.firstChild, this.lastChild, getHostName(this.firstChild));
|
|
181
|
+
throw new HydrationBindingError(result.message, factory, fragment, templateString, result.expected, result.received);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
if (this.source !== null) {
|
|
187
|
+
this.evaluateUnbindables();
|
|
188
|
+
}
|
|
189
|
+
this.isBound = false;
|
|
190
|
+
this.source = source;
|
|
191
|
+
this.context = context;
|
|
192
|
+
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
193
|
+
behaviors[i].bind(this);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
this.isBound = true;
|
|
197
|
+
this._hydrationStage = HydrationStage.hydrated;
|
|
198
|
+
}
|
|
199
|
+
unbind() {
|
|
200
|
+
if (!this.isBound || this.source === null) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.evaluateUnbindables();
|
|
204
|
+
this.source = null;
|
|
205
|
+
this.context = this;
|
|
206
|
+
this.isBound = false;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
|
|
210
|
+
* Once a view has been disposed, it cannot be inserted or bound again.
|
|
211
|
+
*/
|
|
212
|
+
dispose() {
|
|
213
|
+
removeNodeSequence(this.firstChild, this.lastChild);
|
|
214
|
+
this.unbind();
|
|
215
|
+
}
|
|
216
|
+
onUnbind(behavior) {
|
|
217
|
+
this.unbindables.push(behavior);
|
|
218
|
+
}
|
|
219
|
+
evaluateUnbindables() {
|
|
220
|
+
const unbindables = this.unbindables;
|
|
221
|
+
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
222
|
+
unbindables[i].unbind(this);
|
|
223
|
+
}
|
|
224
|
+
unbindables.length = 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
_a = Hydratable;
|
|
228
|
+
makeSerializationNoop(HydrationView);
|