@microsoft/fast-element 2.0.0-beta.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +1 -1
- package/CHANGELOG.json +497 -0
- package/CHANGELOG.md +172 -1
- package/README.md +1 -9
- package/api-extractor.context.json +14 -0
- package/api-extractor.di.json +14 -0
- package/dist/context/context.api.json +1068 -0
- package/dist/di/di.api.json +4929 -0
- package/dist/dts/binding/binding.d.ts +49 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/{templating/binding-signal.d.ts → binding/signal.d.ts} +19 -4
- package/dist/dts/{templating/binding-two-way.d.ts → binding/two-way.d.ts} +9 -5
- package/dist/dts/components/attributes.d.ts +6 -0
- package/dist/dts/components/element-controller.d.ts +104 -8
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/fast-definitions.d.ts +6 -0
- package/dist/dts/components/hydration.d.ts +56 -0
- package/dist/dts/components/install-hydration.d.ts +1 -0
- package/dist/dts/context.d.ts +29 -15
- package/dist/dts/di/di.d.ts +0 -5
- package/dist/dts/dom-policy.d.ts +83 -0
- package/dist/dts/dom.d.ts +100 -0
- package/dist/dts/hydration/target-builder.d.ts +63 -0
- package/dist/dts/index.d.ts +33 -26
- package/dist/dts/index.rollup.d.ts +0 -1
- package/dist/dts/index.rollup.debug.d.ts +0 -1
- package/dist/dts/interfaces.d.ts +32 -82
- package/dist/dts/metadata.d.ts +6 -5
- package/dist/dts/observation/arrays.d.ts +1 -1
- package/dist/dts/observation/observable.bench.d.ts +18 -0
- package/dist/dts/observation/observable.d.ts +5 -5
- package/dist/dts/pending-task.d.ts +19 -7
- package/dist/dts/platform.d.ts +11 -2
- package/dist/dts/polyfills.d.ts +0 -8
- package/dist/dts/styles/css-binding-directive.d.ts +60 -0
- package/dist/dts/styles/css.d.ts +9 -7
- package/dist/dts/styles/element-styles.d.ts +1 -14
- package/dist/dts/styles/host.d.ts +2 -5
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/compiler.d.ts +11 -13
- package/dist/dts/templating/{binding.d.ts → html-binding-directive.d.ts} +21 -41
- package/dist/dts/templating/html-directive.d.ts +44 -140
- package/dist/dts/templating/install-hydratable-view-templates.d.ts +1 -0
- package/dist/dts/templating/node-observation.d.ts +11 -1
- package/dist/dts/templating/ref.d.ts +4 -0
- package/dist/dts/templating/render.bench.d.ts +3 -0
- package/dist/dts/templating/render.d.ts +49 -9
- package/dist/dts/templating/repeat-basic-reverse.bench.d.ts +3 -0
- package/dist/dts/templating/repeat-basic-shift.bench.d.ts +3 -0
- package/dist/dts/templating/repeat.d.ts +31 -9
- package/dist/dts/templating/template.d.ts +97 -12
- package/dist/dts/templating/view.d.ts +146 -29
- package/dist/dts/templating/when-basic.bench.d.ts +3 -0
- package/dist/dts/templating/when-conditional.bench.d.ts +3 -0
- package/dist/dts/templating/when-switch.bench.d.ts +3 -0
- package/dist/dts/templating/when.d.ts +3 -1
- package/dist/dts/testing/fakes.d.ts +12 -1
- package/dist/dts/tsdoc-metadata.json +1 -1
- package/dist/dts/utilities.d.ts +55 -1
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/{templating/binding-signal.js → binding/signal.js} +22 -6
- package/dist/esm/{templating/binding-two-way.js → binding/two-way.js} +18 -12
- package/dist/esm/components/attributes.js +16 -1
- package/dist/esm/components/element-controller.js +319 -49
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/fast-definitions.js +12 -4
- package/dist/esm/components/fast-element.js +3 -1
- package/dist/esm/components/hydration.js +104 -0
- package/dist/esm/components/install-hydration.js +3 -0
- package/dist/esm/context.js +26 -4
- package/dist/esm/debug.js +8 -2
- package/dist/esm/di/di.js +9 -12
- package/dist/esm/dom-policy.js +345 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/target-builder.js +175 -0
- package/dist/esm/index.js +34 -25
- package/dist/esm/index.rollup.debug.js +3 -1
- package/dist/esm/index.rollup.js +3 -1
- package/dist/esm/interfaces.js +51 -3
- package/dist/esm/metadata.js +11 -8
- package/dist/esm/observation/arrays.js +1 -1
- package/dist/esm/observation/observable.bench.js +79 -0
- package/dist/esm/observation/observable.js +20 -15
- package/dist/esm/observation/update-queue.js +2 -2
- package/dist/esm/pending-task.js +13 -1
- package/dist/esm/platform.js +12 -2
- package/dist/esm/polyfills.js +3 -61
- package/dist/esm/styles/css-binding-directive.js +76 -0
- package/dist/esm/styles/css.js +14 -7
- package/dist/esm/styles/element-styles.js +0 -33
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +8 -4
- package/dist/esm/templating/compiler.js +37 -44
- package/dist/esm/templating/html-binding-directive.js +218 -0
- package/dist/esm/templating/html-directive.js +25 -152
- package/dist/esm/templating/install-hydratable-view-templates.js +17 -0
- package/dist/esm/templating/node-observation.js +14 -8
- package/dist/esm/templating/ref.js +1 -1
- package/dist/esm/templating/render.bench.js +56 -0
- package/dist/esm/templating/render.js +74 -30
- package/dist/esm/templating/repeat-basic-reverse.bench.js +43 -0
- package/dist/esm/templating/repeat-basic-shift.bench.js +43 -0
- package/dist/esm/templating/repeat.js +116 -17
- package/dist/esm/templating/template.js +135 -60
- package/dist/esm/templating/view.js +254 -34
- package/dist/esm/templating/when-basic.bench.js +36 -0
- package/dist/esm/templating/when-conditional.bench.js +39 -0
- package/dist/esm/templating/when-switch.bench.js +68 -0
- package/dist/esm/templating/when.js +12 -5
- package/dist/esm/testing/fakes.js +32 -1
- package/dist/esm/testing/fixture.js +1 -1
- package/dist/esm/utilities.js +97 -1
- package/dist/fast-element.api.json +9788 -5666
- package/dist/fast-element.d.ts +813 -2392
- package/dist/fast-element.debug.js +2785 -969
- package/dist/fast-element.debug.min.js +3 -1
- package/dist/fast-element.js +2638 -828
- package/dist/fast-element.min.js +3 -1
- package/dist/fast-element.untrimmed.d.ts +661 -313
- package/docs/{api-report.md → api-report.api.md} +238 -151
- package/docs/context/api-report.api.md +69 -0
- package/docs/di/api-report.api.md +315 -0
- package/karma.conf.cjs +2 -1
- package/package.json +59 -47
- package/scripts/run-api-extractor.js +51 -0
- package/scripts/run-benchmarks.js +46 -0
- package/tensile.config.js +12 -0
- package/dist/dts/templating/dom.d.ts +0 -41
- package/dist/esm/templating/binding.js +0 -282
- package/dist/esm/templating/dom.js +0 -49
- package/docs/guide/declaring-templates.md +0 -230
- package/docs/guide/defining-elements.md +0 -214
- package/docs/guide/leveraging-css.md +0 -253
- package/docs/guide/next-steps.md +0 -13
- package/docs/guide/observables-and-state.md +0 -213
- package/docs/guide/using-directives.md +0 -576
- package/docs/guide/working-with-shadow-dom.md +0 -296
|
@@ -1,8 +1,56 @@
|
|
|
1
1
|
import { isFunction, isString } from "../interfaces.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Binding } from "../binding/binding.js";
|
|
3
|
+
import { FAST, makeSerializationNoop } from "../platform.js";
|
|
4
|
+
import { oneWay } from "../binding/one-way.js";
|
|
5
|
+
import { oneTime } from "../binding/one-time.js";
|
|
6
|
+
import { HTMLBindingDirective } from "./html-binding-directive.js";
|
|
3
7
|
import { Compiler } from "./compiler.js";
|
|
4
|
-
import {
|
|
8
|
+
import { HTMLDirective, } from "./html-directive.js";
|
|
5
9
|
import { nextId } from "./markup.js";
|
|
10
|
+
// Much thanks to LitHTML for working this out!
|
|
11
|
+
const lastAttributeNameRegex =
|
|
12
|
+
/* eslint-disable-next-line no-control-regex, max-len */
|
|
13
|
+
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
14
|
+
const noFactories = Object.create(null);
|
|
15
|
+
/**
|
|
16
|
+
* Inlines a template into another template.
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export class InlineTemplateDirective {
|
|
20
|
+
/**
|
|
21
|
+
* Creates an instance of InlineTemplateDirective.
|
|
22
|
+
* @param template - The template to inline.
|
|
23
|
+
*/
|
|
24
|
+
constructor(html, factories = noFactories) {
|
|
25
|
+
this.html = html;
|
|
26
|
+
this.factories = factories;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates HTML to be used within a template.
|
|
30
|
+
* @param add - Can be used to add behavior factories to a template.
|
|
31
|
+
*/
|
|
32
|
+
createHTML(add) {
|
|
33
|
+
const factories = this.factories;
|
|
34
|
+
for (const key in factories) {
|
|
35
|
+
add(factories[key]);
|
|
36
|
+
}
|
|
37
|
+
return this.html;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* An empty template partial.
|
|
42
|
+
*/
|
|
43
|
+
InlineTemplateDirective.empty = new InlineTemplateDirective("");
|
|
44
|
+
HTMLDirective.define(InlineTemplateDirective);
|
|
45
|
+
function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
|
|
46
|
+
if (definition.aspected) {
|
|
47
|
+
const match = lastAttributeNameRegex.exec(prevString);
|
|
48
|
+
if (match !== null) {
|
|
49
|
+
HTMLDirective.assignAspect(value, match[2]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return value.createHTML(add);
|
|
53
|
+
}
|
|
6
54
|
/**
|
|
7
55
|
* A template capable of creating HTMLView instances or rendering directly to DOM.
|
|
8
56
|
* @public
|
|
@@ -12,21 +60,53 @@ export class ViewTemplate {
|
|
|
12
60
|
* Creates an instance of ViewTemplate.
|
|
13
61
|
* @param html - The html representing what this template will instantiate, including placeholders for directives.
|
|
14
62
|
* @param factories - The directives that will be connected to placeholders in the html.
|
|
63
|
+
* @param policy - The security policy to use when compiling this template.
|
|
15
64
|
*/
|
|
16
|
-
constructor(html, factories) {
|
|
65
|
+
constructor(html, factories = {}, policy) {
|
|
66
|
+
this.policy = policy;
|
|
17
67
|
this.result = null;
|
|
18
68
|
this.html = html;
|
|
19
69
|
this.factories = factories;
|
|
20
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
compile() {
|
|
75
|
+
if (this.result === null) {
|
|
76
|
+
this.result = Compiler.compile(this.html, this.factories, this.policy);
|
|
77
|
+
}
|
|
78
|
+
return this.result;
|
|
79
|
+
}
|
|
21
80
|
/**
|
|
22
81
|
* Creates an HTMLView instance based on this template definition.
|
|
23
82
|
* @param hostBindingTarget - The element that host behaviors will be bound to.
|
|
24
83
|
*/
|
|
25
84
|
create(hostBindingTarget) {
|
|
26
|
-
|
|
27
|
-
|
|
85
|
+
return this.compile().createView(hostBindingTarget);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns a directive that can inline the template.
|
|
89
|
+
*/
|
|
90
|
+
inline() {
|
|
91
|
+
return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Sets the DOMPolicy for this template.
|
|
95
|
+
* @param policy - The policy to associated with this template.
|
|
96
|
+
* @returns The modified template instance.
|
|
97
|
+
* @remarks
|
|
98
|
+
* The DOMPolicy can only be set once for a template and cannot be
|
|
99
|
+
* set after the template is compiled.
|
|
100
|
+
*/
|
|
101
|
+
withPolicy(policy) {
|
|
102
|
+
if (this.result) {
|
|
103
|
+
throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
|
|
104
|
+
}
|
|
105
|
+
if (this.policy) {
|
|
106
|
+
throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
|
|
28
107
|
}
|
|
29
|
-
|
|
108
|
+
this.policy = policy;
|
|
109
|
+
return this;
|
|
30
110
|
}
|
|
31
111
|
/**
|
|
32
112
|
* Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
|
|
@@ -41,18 +121,49 @@ export class ViewTemplate {
|
|
|
41
121
|
view.appendTo(host);
|
|
42
122
|
return view;
|
|
43
123
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Creates a template based on a set of static strings and dynamic values.
|
|
126
|
+
* @param strings - The static strings to create the template with.
|
|
127
|
+
* @param values - The dynamic values to create the template with.
|
|
128
|
+
* @param policy - The DOMPolicy to associated with the template.
|
|
129
|
+
* @returns A ViewTemplate.
|
|
130
|
+
* @remarks
|
|
131
|
+
* This API should not be used directly under normal circumstances because constructing
|
|
132
|
+
* a template in this way, if not done properly, can open up the application to XSS
|
|
133
|
+
* attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
|
|
134
|
+
* and also be sure to manually sanitize all static strings particularly if they can
|
|
135
|
+
* come from user input.
|
|
136
|
+
*/
|
|
137
|
+
static create(strings, values, policy) {
|
|
138
|
+
let html = "";
|
|
139
|
+
const factories = Object.create(null);
|
|
140
|
+
const add = (factory) => {
|
|
141
|
+
var _a;
|
|
142
|
+
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
143
|
+
factories[id] = factory;
|
|
144
|
+
return id;
|
|
145
|
+
};
|
|
146
|
+
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
147
|
+
const currentString = strings[i];
|
|
148
|
+
let currentValue = values[i];
|
|
149
|
+
let definition;
|
|
150
|
+
html += currentString;
|
|
151
|
+
if (isFunction(currentValue)) {
|
|
152
|
+
currentValue = new HTMLBindingDirective(oneWay(currentValue));
|
|
153
|
+
}
|
|
154
|
+
else if (currentValue instanceof Binding) {
|
|
155
|
+
currentValue = new HTMLBindingDirective(currentValue);
|
|
156
|
+
}
|
|
157
|
+
else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
|
|
158
|
+
const staticValue = currentValue;
|
|
159
|
+
currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
|
|
160
|
+
}
|
|
161
|
+
html += createHTML(currentValue, currentString, add, definition);
|
|
162
|
+
}
|
|
163
|
+
return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
|
|
53
164
|
}
|
|
54
|
-
return value.createHTML(add);
|
|
55
165
|
}
|
|
166
|
+
makeSerializationNoop(ViewTemplate);
|
|
56
167
|
/**
|
|
57
168
|
* Transforms a template literal string into a ViewTemplate.
|
|
58
169
|
* @param strings - The string fragments that are interpolated with the values.
|
|
@@ -62,48 +173,12 @@ function createAspectedHTML(value, prevString, add) {
|
|
|
62
173
|
* other template instances, and Directive instances.
|
|
63
174
|
* @public
|
|
64
175
|
*/
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const add = (factory) => {
|
|
69
|
-
var _a;
|
|
70
|
-
const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
|
|
71
|
-
factories[id] = factory;
|
|
72
|
-
return id;
|
|
73
|
-
};
|
|
74
|
-
for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
|
|
75
|
-
const currentString = strings[i];
|
|
76
|
-
const currentValue = values[i];
|
|
77
|
-
let definition;
|
|
78
|
-
html += currentString;
|
|
79
|
-
if (isFunction(currentValue)) {
|
|
80
|
-
html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
|
|
81
|
-
}
|
|
82
|
-
else if (isString(currentValue)) {
|
|
83
|
-
const match = lastAttributeNameRegex.exec(currentString);
|
|
84
|
-
if (match !== null) {
|
|
85
|
-
const directive = new HTMLBindingDirective(oneTime(() => currentValue));
|
|
86
|
-
Aspect.assign(directive, match[2]);
|
|
87
|
-
html += directive.createHTML(add);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
html += currentValue;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else if (currentValue instanceof Binding) {
|
|
94
|
-
html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
|
|
95
|
-
}
|
|
96
|
-
else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
|
|
97
|
-
html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
if (definition.aspected) {
|
|
101
|
-
html += createAspectedHTML(currentValue, currentString, add);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
html += currentValue.createHTML(add);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
176
|
+
export const html = ((strings, ...values) => {
|
|
177
|
+
if (Array.isArray(strings) && Array.isArray(strings.raw)) {
|
|
178
|
+
return ViewTemplate.create(strings, values);
|
|
107
179
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
180
|
+
throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
|
|
181
|
+
});
|
|
182
|
+
html.partial = (html) => {
|
|
183
|
+
return new InlineTemplateDirective(html);
|
|
184
|
+
};
|
|
@@ -1,47 +1,24 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { Hydratable } from "../components/hydration.js";
|
|
3
|
+
import { buildViewBindingTargets, createRangeForNodes, HydrationTargetElementError, targetFactory, } from "../hydration/target-builder.js";
|
|
1
4
|
import { ExecutionContext, Observable, SourceLifetime, } from "../observation/observable.js";
|
|
5
|
+
import { makeSerializationNoop } from "../platform.js";
|
|
2
6
|
function removeNodeSequence(firstNode, lastNode) {
|
|
3
7
|
const parent = firstNode.parentNode;
|
|
4
8
|
let current = firstNode;
|
|
5
9
|
let next;
|
|
6
10
|
while (current !== lastNode) {
|
|
7
11
|
next = current.nextSibling;
|
|
12
|
+
if (!next) {
|
|
13
|
+
throw new Error(`Unmatched first/last child inside "${lastNode.getRootNode().host.nodeName}".`);
|
|
14
|
+
}
|
|
8
15
|
parent.removeChild(current);
|
|
9
16
|
current = next;
|
|
10
17
|
}
|
|
11
18
|
parent.removeChild(lastNode);
|
|
12
19
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* @public
|
|
16
|
-
*/
|
|
17
|
-
export class HTMLView {
|
|
18
|
-
/**
|
|
19
|
-
* Constructs an instance of HTMLView.
|
|
20
|
-
* @param fragment - The html fragment that contains the nodes for this view.
|
|
21
|
-
* @param behaviors - The behaviors to be applied to this view.
|
|
22
|
-
*/
|
|
23
|
-
constructor(fragment, factories, targets) {
|
|
24
|
-
this.fragment = fragment;
|
|
25
|
-
this.factories = factories;
|
|
26
|
-
this.targets = targets;
|
|
27
|
-
this.behaviors = null;
|
|
28
|
-
this.unbindables = [];
|
|
29
|
-
/**
|
|
30
|
-
* The data that the view is bound to.
|
|
31
|
-
*/
|
|
32
|
-
this.source = null;
|
|
33
|
-
/**
|
|
34
|
-
* Indicates whether the controller is bound.
|
|
35
|
-
*/
|
|
36
|
-
this.isBound = false;
|
|
37
|
-
/**
|
|
38
|
-
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
39
|
-
*/
|
|
40
|
-
this.sourceLifetime = SourceLifetime.unknown;
|
|
41
|
-
/**
|
|
42
|
-
* The execution context the view is running within.
|
|
43
|
-
*/
|
|
44
|
-
this.context = this;
|
|
20
|
+
class DefaultExecutionContext {
|
|
21
|
+
constructor() {
|
|
45
22
|
/**
|
|
46
23
|
* The index of the current item within a repeat context.
|
|
47
24
|
*/
|
|
@@ -50,8 +27,6 @@ export class HTMLView {
|
|
|
50
27
|
* The length of the current collection within a repeat context.
|
|
51
28
|
*/
|
|
52
29
|
this.length = 0;
|
|
53
|
-
this.firstChild = fragment.firstChild;
|
|
54
|
-
this.lastChild = fragment.lastChild;
|
|
55
30
|
}
|
|
56
31
|
/**
|
|
57
32
|
* The current event within an event handler.
|
|
@@ -106,6 +81,43 @@ export class HTMLView {
|
|
|
106
81
|
eventTarget() {
|
|
107
82
|
return this.event.target;
|
|
108
83
|
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The standard View implementation, which also implements ElementView and SyntheticView.
|
|
87
|
+
* @public
|
|
88
|
+
*/
|
|
89
|
+
export class HTMLView extends DefaultExecutionContext {
|
|
90
|
+
/**
|
|
91
|
+
* Constructs an instance of HTMLView.
|
|
92
|
+
* @param fragment - The html fragment that contains the nodes for this view.
|
|
93
|
+
* @param behaviors - The behaviors to be applied to this view.
|
|
94
|
+
*/
|
|
95
|
+
constructor(fragment, factories, targets) {
|
|
96
|
+
super();
|
|
97
|
+
this.fragment = fragment;
|
|
98
|
+
this.factories = factories;
|
|
99
|
+
this.targets = targets;
|
|
100
|
+
this.behaviors = null;
|
|
101
|
+
this.unbindables = [];
|
|
102
|
+
/**
|
|
103
|
+
* The data that the view is bound to.
|
|
104
|
+
*/
|
|
105
|
+
this.source = null;
|
|
106
|
+
/**
|
|
107
|
+
* Indicates whether the controller is bound.
|
|
108
|
+
*/
|
|
109
|
+
this.isBound = false;
|
|
110
|
+
/**
|
|
111
|
+
* Indicates how the source's lifetime relates to the controller's lifetime.
|
|
112
|
+
*/
|
|
113
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
114
|
+
/**
|
|
115
|
+
* The execution context the view is running within.
|
|
116
|
+
*/
|
|
117
|
+
this.context = this;
|
|
118
|
+
this.firstChild = fragment.firstChild;
|
|
119
|
+
this.lastChild = fragment.lastChild;
|
|
120
|
+
}
|
|
109
121
|
/**
|
|
110
122
|
* Appends the view's DOM nodes to the referenced node.
|
|
111
123
|
* @param node - The parent node to append the view's DOM nodes to.
|
|
@@ -230,5 +242,213 @@ export class HTMLView {
|
|
|
230
242
|
}
|
|
231
243
|
}
|
|
232
244
|
}
|
|
245
|
+
makeSerializationNoop(HTMLView);
|
|
233
246
|
Observable.defineProperty(HTMLView.prototype, "index");
|
|
234
247
|
Observable.defineProperty(HTMLView.prototype, "length");
|
|
248
|
+
export const HydrationStage = {
|
|
249
|
+
unhydrated: "unhydrated",
|
|
250
|
+
hydrating: "hydrating",
|
|
251
|
+
hydrated: "hydrated",
|
|
252
|
+
};
|
|
253
|
+
/** @public */
|
|
254
|
+
export class HydrationBindingError extends Error {
|
|
255
|
+
constructor(
|
|
256
|
+
/**
|
|
257
|
+
* The error message
|
|
258
|
+
*/
|
|
259
|
+
message,
|
|
260
|
+
/**
|
|
261
|
+
* The factory that was unable to be bound
|
|
262
|
+
*/
|
|
263
|
+
factory,
|
|
264
|
+
/**
|
|
265
|
+
* A DocumentFragment containing a clone of the
|
|
266
|
+
* view's Nodes.
|
|
267
|
+
*/
|
|
268
|
+
fragment,
|
|
269
|
+
/**
|
|
270
|
+
* String representation of the HTML in the template that
|
|
271
|
+
* threw the binding error.
|
|
272
|
+
*/
|
|
273
|
+
templateString) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.factory = factory;
|
|
276
|
+
this.fragment = fragment;
|
|
277
|
+
this.templateString = templateString;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export class HydrationView extends DefaultExecutionContext {
|
|
281
|
+
constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
|
|
282
|
+
super();
|
|
283
|
+
this.firstChild = firstChild;
|
|
284
|
+
this.lastChild = lastChild;
|
|
285
|
+
this.sourceTemplate = sourceTemplate;
|
|
286
|
+
this.hostBindingTarget = hostBindingTarget;
|
|
287
|
+
this[_a] = Hydratable;
|
|
288
|
+
this.context = this;
|
|
289
|
+
this.source = null;
|
|
290
|
+
this.isBound = false;
|
|
291
|
+
this.sourceLifetime = SourceLifetime.unknown;
|
|
292
|
+
this.unbindables = [];
|
|
293
|
+
this.fragment = null;
|
|
294
|
+
this.behaviors = null;
|
|
295
|
+
this._hydrationStage = HydrationStage.unhydrated;
|
|
296
|
+
this._bindingViewBoundaries = {};
|
|
297
|
+
this._targets = {};
|
|
298
|
+
this.factories = sourceTemplate.compile().factories;
|
|
299
|
+
}
|
|
300
|
+
get hydrationStage() {
|
|
301
|
+
return this._hydrationStage;
|
|
302
|
+
}
|
|
303
|
+
get targets() {
|
|
304
|
+
return this._targets;
|
|
305
|
+
}
|
|
306
|
+
get bindingViewBoundaries() {
|
|
307
|
+
return this._bindingViewBoundaries;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* no-op. Hydrated views are don't need to be moved from a documentFragment
|
|
311
|
+
* to the target node.
|
|
312
|
+
*/
|
|
313
|
+
insertBefore(node) {
|
|
314
|
+
// No-op in cases where this is called before the view is removed,
|
|
315
|
+
// because the nodes will already be in the document and just need hydrating.
|
|
316
|
+
if (this.fragment === null) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (this.fragment.hasChildNodes()) {
|
|
320
|
+
node.parentNode.insertBefore(this.fragment, node);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const end = this.lastChild;
|
|
324
|
+
if (node.previousSibling === end)
|
|
325
|
+
return;
|
|
326
|
+
const parentNode = node.parentNode;
|
|
327
|
+
let current = this.firstChild;
|
|
328
|
+
let next;
|
|
329
|
+
while (current !== end) {
|
|
330
|
+
next = current.nextSibling;
|
|
331
|
+
parentNode.insertBefore(current, node);
|
|
332
|
+
current = next;
|
|
333
|
+
}
|
|
334
|
+
parentNode.insertBefore(end, node);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Appends the view to a node. In cases where this is called before the
|
|
339
|
+
* view has been removed, the method will no-op.
|
|
340
|
+
* @param node - the node to append the view to.
|
|
341
|
+
*/
|
|
342
|
+
appendTo(node) {
|
|
343
|
+
if (this.fragment !== null) {
|
|
344
|
+
node.appendChild(this.fragment);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
remove() {
|
|
348
|
+
const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
|
|
349
|
+
const end = this.lastChild;
|
|
350
|
+
let current = this.firstChild;
|
|
351
|
+
let next;
|
|
352
|
+
while (current !== end) {
|
|
353
|
+
next = current.nextSibling;
|
|
354
|
+
if (!next) {
|
|
355
|
+
throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
|
|
356
|
+
}
|
|
357
|
+
fragment.appendChild(current);
|
|
358
|
+
current = next;
|
|
359
|
+
}
|
|
360
|
+
fragment.appendChild(end);
|
|
361
|
+
}
|
|
362
|
+
bind(source, context = this) {
|
|
363
|
+
var _b, _c;
|
|
364
|
+
if (this.hydrationStage !== HydrationStage.hydrated) {
|
|
365
|
+
this._hydrationStage = HydrationStage.hydrating;
|
|
366
|
+
}
|
|
367
|
+
if (this.source === source) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
let behaviors = this.behaviors;
|
|
371
|
+
if (behaviors === null) {
|
|
372
|
+
this.source = source;
|
|
373
|
+
this.context = context;
|
|
374
|
+
try {
|
|
375
|
+
const { targets, boundaries } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
|
|
376
|
+
this._targets = targets;
|
|
377
|
+
this._bindingViewBoundaries = boundaries;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (error instanceof HydrationTargetElementError) {
|
|
381
|
+
let templateString = this.sourceTemplate.html;
|
|
382
|
+
if (typeof templateString !== "string") {
|
|
383
|
+
templateString = templateString.innerHTML;
|
|
384
|
+
}
|
|
385
|
+
error.templateString = templateString;
|
|
386
|
+
}
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
this.behaviors = behaviors = new Array(this.factories.length);
|
|
390
|
+
const factories = this.factories;
|
|
391
|
+
for (let i = 0, ii = factories.length; i < ii; ++i) {
|
|
392
|
+
const factory = factories[i];
|
|
393
|
+
if (factory.targetNodeId === "h" && this.hostBindingTarget) {
|
|
394
|
+
targetFactory(factory, this.hostBindingTarget, this._targets);
|
|
395
|
+
}
|
|
396
|
+
// If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
|
|
397
|
+
if (factory.targetNodeId in this.targets) {
|
|
398
|
+
const behavior = factory.createBehavior();
|
|
399
|
+
behavior.bind(this);
|
|
400
|
+
behaviors[i] = behavior;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
let templateString = this.sourceTemplate.html;
|
|
404
|
+
if (typeof templateString !== "string") {
|
|
405
|
+
templateString = templateString.innerHTML;
|
|
406
|
+
}
|
|
407
|
+
throw new HydrationBindingError(`HydrationView was unable to successfully target bindings inside "${(_c = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode()).host) === null || _c === void 0 ? void 0 : _c.nodeName}".`, factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
if (this.source !== null) {
|
|
413
|
+
this.evaluateUnbindables();
|
|
414
|
+
}
|
|
415
|
+
this.isBound = false;
|
|
416
|
+
this.source = source;
|
|
417
|
+
this.context = context;
|
|
418
|
+
for (let i = 0, ii = behaviors.length; i < ii; ++i) {
|
|
419
|
+
behaviors[i].bind(this);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
this.isBound = true;
|
|
423
|
+
this._hydrationStage = HydrationStage.hydrated;
|
|
424
|
+
}
|
|
425
|
+
unbind() {
|
|
426
|
+
if (!this.isBound || this.source === null) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this.evaluateUnbindables();
|
|
430
|
+
this.source = null;
|
|
431
|
+
this.context = this;
|
|
432
|
+
this.isBound = false;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
|
|
436
|
+
* Once a view has been disposed, it cannot be inserted or bound again.
|
|
437
|
+
*/
|
|
438
|
+
dispose() {
|
|
439
|
+
removeNodeSequence(this.firstChild, this.lastChild);
|
|
440
|
+
this.unbind();
|
|
441
|
+
}
|
|
442
|
+
onUnbind(behavior) {
|
|
443
|
+
this.unbindables.push(behavior);
|
|
444
|
+
}
|
|
445
|
+
evaluateUnbindables() {
|
|
446
|
+
const unbindables = this.unbindables;
|
|
447
|
+
for (let i = 0, ii = unbindables.length; i < ii; ++i) {
|
|
448
|
+
unbindables[i].unbind(this);
|
|
449
|
+
}
|
|
450
|
+
unbindables.length = 0;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
_a = Hydratable;
|
|
454
|
+
makeSerializationNoop(HydrationView);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { attr, FASTElement, html, when } from "../index.js";
|
|
11
|
+
class TestWhen extends FASTElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.try = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
__decorate([
|
|
18
|
+
attr({
|
|
19
|
+
mode: "boolean",
|
|
20
|
+
}),
|
|
21
|
+
__metadata("design:type", Boolean)
|
|
22
|
+
], TestWhen.prototype, "try", void 0);
|
|
23
|
+
TestWhen.define({
|
|
24
|
+
name: "test-when",
|
|
25
|
+
template: html `
|
|
26
|
+
${when(x => x.try, html `
|
|
27
|
+
<span>Yes</span>
|
|
28
|
+
`)}
|
|
29
|
+
`,
|
|
30
|
+
});
|
|
31
|
+
const itemRenderer = () => {
|
|
32
|
+
const testWhen = document.createElement("test-when");
|
|
33
|
+
return testWhen;
|
|
34
|
+
};
|
|
35
|
+
export default itemRenderer;
|
|
36
|
+
export { tests } from "@tensile-perf/web-components";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { attr, FASTElement, html, when } from "../index.js";
|
|
11
|
+
class TestWhen extends FASTElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.try = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
__decorate([
|
|
18
|
+
attr({
|
|
19
|
+
mode: "boolean",
|
|
20
|
+
}),
|
|
21
|
+
__metadata("design:type", Boolean)
|
|
22
|
+
], TestWhen.prototype, "try", void 0);
|
|
23
|
+
TestWhen.define({
|
|
24
|
+
name: "test-when",
|
|
25
|
+
template: html `
|
|
26
|
+
${when(x => x.try, html `
|
|
27
|
+
<span>Yes</span>
|
|
28
|
+
`)}
|
|
29
|
+
${when(x => !x.try, html `
|
|
30
|
+
<span>No</span>
|
|
31
|
+
`)}
|
|
32
|
+
`,
|
|
33
|
+
});
|
|
34
|
+
const itemRenderer = () => {
|
|
35
|
+
const testWhen = document.createElement("test-when");
|
|
36
|
+
return testWhen;
|
|
37
|
+
};
|
|
38
|
+
export default itemRenderer;
|
|
39
|
+
export { tests } from "@tensile-perf/web-components";
|