@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,9 +1,14 @@
|
|
|
1
1
|
import { FASTElementDefinition } from "../components/fast-definitions.js";
|
|
2
|
+
import { isHydratable } from "../components/hydration.js";
|
|
2
3
|
import { isFunction, isString } from "../interfaces.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { Binding } from "../binding/binding.js";
|
|
5
|
+
import { oneTime } from "../binding/one-time.js";
|
|
6
|
+
import { oneWay } from "../binding/one-way.js";
|
|
7
|
+
import { normalizeBinding } from "../binding/normalize.js";
|
|
8
|
+
import { HTMLDirective, } from "./html-directive.js";
|
|
5
9
|
import { Markup } from "./markup.js";
|
|
6
|
-
import { html, } from "./template.js";
|
|
10
|
+
import { html, ViewTemplate, } from "./template.js";
|
|
11
|
+
import { HydrationStage } from "./view.js";
|
|
7
12
|
/**
|
|
8
13
|
* A Behavior that enables advanced rendering.
|
|
9
14
|
* @public
|
|
@@ -19,20 +24,32 @@ export class RenderBehavior {
|
|
|
19
24
|
this.controller = null;
|
|
20
25
|
this.view = null;
|
|
21
26
|
this.data = null;
|
|
22
|
-
this.dataBindingObserver = directive.dataBinding.createObserver(
|
|
23
|
-
this.templateBindingObserver = directive.templateBinding.createObserver(
|
|
27
|
+
this.dataBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
28
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
31
|
* Bind this behavior.
|
|
27
32
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
28
33
|
*/
|
|
29
34
|
bind(controller) {
|
|
30
|
-
this.location = controller.targets[this.directive.
|
|
35
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
31
36
|
this.controller = controller;
|
|
32
37
|
this.data = this.dataBindingObserver.bind(controller);
|
|
33
38
|
this.template = this.templateBindingObserver.bind(controller);
|
|
34
39
|
controller.onUnbind(this);
|
|
35
|
-
this.
|
|
40
|
+
if (isHydratable(this.template) &&
|
|
41
|
+
isHydratable(controller) &&
|
|
42
|
+
controller.hydrationStage !== HydrationStage.hydrated &&
|
|
43
|
+
!this.view) {
|
|
44
|
+
const viewNodes = controller.bindingViewBoundaries[this.directive.targetNodeId];
|
|
45
|
+
if (viewNodes) {
|
|
46
|
+
this.view = this.template.hydrate(viewNodes.first, viewNodes.last);
|
|
47
|
+
this.bindView(this.view);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.refreshView();
|
|
52
|
+
}
|
|
36
53
|
}
|
|
37
54
|
/**
|
|
38
55
|
* Unbinds this behavior.
|
|
@@ -56,6 +73,20 @@ export class RenderBehavior {
|
|
|
56
73
|
}
|
|
57
74
|
this.refreshView();
|
|
58
75
|
}
|
|
76
|
+
bindView(view) {
|
|
77
|
+
// It's possible that the value is the same as the previous template
|
|
78
|
+
// and that there's actually no need to compose it.
|
|
79
|
+
if (!view.isComposed) {
|
|
80
|
+
view.isComposed = true;
|
|
81
|
+
view.bind(this.data);
|
|
82
|
+
view.insertBefore(this.location);
|
|
83
|
+
view.$fastTemplate = this.template;
|
|
84
|
+
}
|
|
85
|
+
else if (view.needsBindOnly) {
|
|
86
|
+
view.needsBindOnly = false;
|
|
87
|
+
view.bind(this.data);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
59
90
|
refreshView() {
|
|
60
91
|
let view = this.view;
|
|
61
92
|
const template = this.template;
|
|
@@ -79,18 +110,7 @@ export class RenderBehavior {
|
|
|
79
110
|
this.view.context.parentContext = this.controller.context;
|
|
80
111
|
}
|
|
81
112
|
}
|
|
82
|
-
|
|
83
|
-
// and that there's actually no need to compose it.
|
|
84
|
-
if (!view.isComposed) {
|
|
85
|
-
view.isComposed = true;
|
|
86
|
-
view.bind(this.data);
|
|
87
|
-
view.insertBefore(this.location);
|
|
88
|
-
view.$fastTemplate = template;
|
|
89
|
-
}
|
|
90
|
-
else if (view.needsBindOnly) {
|
|
91
|
-
view.needsBindOnly = false;
|
|
92
|
-
view.bind(this.data);
|
|
93
|
-
}
|
|
113
|
+
this.bindView(view);
|
|
94
114
|
}
|
|
95
115
|
}
|
|
96
116
|
/**
|
|
@@ -129,7 +149,7 @@ function isElementRenderOptions(object) {
|
|
|
129
149
|
}
|
|
130
150
|
const typeToInstructionLookup = new Map();
|
|
131
151
|
/* eslint @typescript-eslint/naming-convention: "off"*/
|
|
132
|
-
const defaultAttributes = { ":model": x => x };
|
|
152
|
+
const defaultAttributes = { ":model": (x) => x };
|
|
133
153
|
const brand = Symbol("RenderInstruction");
|
|
134
154
|
const defaultViewName = "default-view";
|
|
135
155
|
const nullTemplate = html `
|
|
@@ -141,12 +161,13 @@ function instructionToTemplate(def) {
|
|
|
141
161
|
}
|
|
142
162
|
return def.template;
|
|
143
163
|
}
|
|
144
|
-
function createElementTemplate(tagName,
|
|
164
|
+
function createElementTemplate(tagName, options) {
|
|
145
165
|
const markup = [];
|
|
146
166
|
const values = [];
|
|
167
|
+
const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
|
|
168
|
+
markup.push(`<${tagName}`);
|
|
147
169
|
if (attributes) {
|
|
148
170
|
const attrNames = Object.getOwnPropertyNames(attributes);
|
|
149
|
-
markup.push(`<${tagName}`);
|
|
150
171
|
for (let i = 0, ii = attrNames.length; i < ii; ++i) {
|
|
151
172
|
const name = attrNames[i];
|
|
152
173
|
if (i === 0) {
|
|
@@ -157,11 +178,17 @@ function createElementTemplate(tagName, attributes, content) {
|
|
|
157
178
|
}
|
|
158
179
|
values.push(attributes[name]);
|
|
159
180
|
}
|
|
160
|
-
markup.push(`"
|
|
181
|
+
markup.push(`"`);
|
|
161
182
|
}
|
|
162
|
-
|
|
163
|
-
markup.
|
|
183
|
+
if (directives) {
|
|
184
|
+
markup[markup.length - 1] += " ";
|
|
185
|
+
for (let i = 0, ii = directives.length; i < ii; ++i) {
|
|
186
|
+
const directive = directives[i];
|
|
187
|
+
markup.push(i > 0 ? "" : " ");
|
|
188
|
+
values.push(directive);
|
|
189
|
+
}
|
|
164
190
|
}
|
|
191
|
+
markup[markup.length - 1] += ">";
|
|
165
192
|
if (content && isFunction(content.create)) {
|
|
166
193
|
values.push(content);
|
|
167
194
|
markup.push(`</${tagName}>`);
|
|
@@ -170,10 +197,10 @@ function createElementTemplate(tagName, attributes, content) {
|
|
|
170
197
|
const lastIndex = markup.length - 1;
|
|
171
198
|
markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
|
|
172
199
|
}
|
|
173
|
-
return
|
|
200
|
+
return ViewTemplate.create(markup, values, policy);
|
|
174
201
|
}
|
|
175
202
|
function create(options) {
|
|
176
|
-
var _a
|
|
203
|
+
var _a;
|
|
177
204
|
const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
|
|
178
205
|
let template;
|
|
179
206
|
if (isElementRenderOptions(options)) {
|
|
@@ -187,7 +214,10 @@ function create(options) {
|
|
|
187
214
|
throw new Error("Invalid element for model rendering.");
|
|
188
215
|
}
|
|
189
216
|
}
|
|
190
|
-
|
|
217
|
+
if (!options.attributes) {
|
|
218
|
+
options.attributes = defaultAttributes;
|
|
219
|
+
}
|
|
220
|
+
template = createElementTemplate(tagName, options);
|
|
191
221
|
}
|
|
192
222
|
else {
|
|
193
223
|
template = options.template;
|
|
@@ -239,6 +269,11 @@ export const RenderInstruction = Object.freeze({
|
|
|
239
269
|
/**
|
|
240
270
|
* Creates a RenderInstruction for a set of options.
|
|
241
271
|
* @param options - The options to use when creating the RenderInstruction.
|
|
272
|
+
* @remarks
|
|
273
|
+
* This API should be used with caution. When providing attributes or content,
|
|
274
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
275
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
276
|
+
* content and attribute values particularly if they can come from user input.
|
|
242
277
|
*/
|
|
243
278
|
create,
|
|
244
279
|
/**
|
|
@@ -246,7 +281,13 @@ export const RenderInstruction = Object.freeze({
|
|
|
246
281
|
* @param tagName - The tag name to use when creating the template.
|
|
247
282
|
* @param attributes - The attributes to apply to the element.
|
|
248
283
|
* @param content - The content to insert into the element.
|
|
284
|
+
* @param policy - The DOMPolicy to create the template with.
|
|
249
285
|
* @returns A template based on the provided specifications.
|
|
286
|
+
* @remarks
|
|
287
|
+
* This API should be used with caution. When providing attributes or content,
|
|
288
|
+
* if not done properly, you can open up the application to XSS attacks. When using this API,
|
|
289
|
+
* provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
|
|
290
|
+
* content and attribute values particularly if they can come from user input.
|
|
250
291
|
*/
|
|
251
292
|
createElementTemplate,
|
|
252
293
|
/**
|
|
@@ -307,6 +348,9 @@ export class NodeTemplate {
|
|
|
307
348
|
create() {
|
|
308
349
|
return this;
|
|
309
350
|
}
|
|
351
|
+
hydrate(first, last) {
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
310
354
|
}
|
|
311
355
|
/**
|
|
312
356
|
* Creates a RenderDirective for use in advanced rendering scenarios.
|
|
@@ -346,7 +390,7 @@ export function render(value, template) {
|
|
|
346
390
|
});
|
|
347
391
|
}
|
|
348
392
|
else if (isFunction(template)) {
|
|
349
|
-
templateBinding =
|
|
393
|
+
templateBinding = oneWay((s, c) => {
|
|
350
394
|
var _a;
|
|
351
395
|
let result = template(s, c);
|
|
352
396
|
if (isString(result)) {
|
|
@@ -356,7 +400,7 @@ export function render(value, template) {
|
|
|
356
400
|
result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
|
|
357
401
|
}
|
|
358
402
|
return result;
|
|
359
|
-
}, true);
|
|
403
|
+
}, void 0, true);
|
|
360
404
|
}
|
|
361
405
|
else if (isString(template)) {
|
|
362
406
|
templateBindingDependsOnData = true;
|
|
@@ -0,0 +1,43 @@
|
|
|
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, nullableNumberConverter, repeat } from "../index.js";
|
|
11
|
+
class TestRepeat extends FASTElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.count = 0;
|
|
15
|
+
this.items = new Array(this.count).fill("foo");
|
|
16
|
+
}
|
|
17
|
+
connectedCallback() {
|
|
18
|
+
super.connectedCallback();
|
|
19
|
+
this.items.reverse();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
__decorate([
|
|
23
|
+
attr({
|
|
24
|
+
mode: "fromView",
|
|
25
|
+
converter: nullableNumberConverter,
|
|
26
|
+
}),
|
|
27
|
+
__metadata("design:type", Number)
|
|
28
|
+
], TestRepeat.prototype, "count", void 0);
|
|
29
|
+
TestRepeat.define({
|
|
30
|
+
name: "test-repeat",
|
|
31
|
+
template: html `
|
|
32
|
+
${repeat(x => x.items, html `
|
|
33
|
+
<span>${x => x}</span>
|
|
34
|
+
`)}
|
|
35
|
+
`,
|
|
36
|
+
});
|
|
37
|
+
const itemRenderer = () => {
|
|
38
|
+
const testRepeat = document.createElement("test-repeat");
|
|
39
|
+
testRepeat.setAttribute("count", "100");
|
|
40
|
+
return testRepeat;
|
|
41
|
+
};
|
|
42
|
+
export default itemRenderer;
|
|
43
|
+
export { tests } from "@tensile-perf/web-components";
|
|
@@ -0,0 +1,43 @@
|
|
|
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, nullableNumberConverter, repeat } from "../index.js";
|
|
11
|
+
class TestRepeat extends FASTElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.count = 0;
|
|
15
|
+
this.items = new Array(this.count).fill("foo");
|
|
16
|
+
}
|
|
17
|
+
connectedCallback() {
|
|
18
|
+
super.connectedCallback();
|
|
19
|
+
this.items.shift();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
__decorate([
|
|
23
|
+
attr({
|
|
24
|
+
mode: "fromView",
|
|
25
|
+
converter: nullableNumberConverter,
|
|
26
|
+
}),
|
|
27
|
+
__metadata("design:type", Number)
|
|
28
|
+
], TestRepeat.prototype, "count", void 0);
|
|
29
|
+
TestRepeat.define({
|
|
30
|
+
name: "test-repeat",
|
|
31
|
+
template: html `
|
|
32
|
+
${repeat(x => x.items, html `
|
|
33
|
+
<span>${x => x}</span>
|
|
34
|
+
`)}
|
|
35
|
+
`,
|
|
36
|
+
});
|
|
37
|
+
const itemRenderer = () => {
|
|
38
|
+
const testRepeat = document.createElement("test-repeat");
|
|
39
|
+
testRepeat.setAttribute("count", "100");
|
|
40
|
+
return testRepeat;
|
|
41
|
+
};
|
|
42
|
+
export default itemRenderer;
|
|
43
|
+
export { tests } from "@tensile-perf/web-components";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { HydrationMarkup, isHydratable } from "../components/hydration.js";
|
|
2
|
+
import { ArrayObserver } from "../observation/arrays.js";
|
|
1
3
|
import { Observable } from "../observation/observable.js";
|
|
2
4
|
import { emptyArray } from "../platform.js";
|
|
3
|
-
import {
|
|
4
|
-
import { Markup, nextId } from "./markup.js";
|
|
5
|
+
import { normalizeBinding } from "../binding/normalize.js";
|
|
5
6
|
import { HTMLDirective, } from "./html-directive.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { Markup } from "./markup.js";
|
|
8
|
+
import { HTMLView, HydrationStage } from "./view.js";
|
|
8
9
|
const defaultRepeatOptions = Object.freeze({
|
|
9
10
|
positioning: false,
|
|
10
11
|
recycle: true,
|
|
@@ -21,6 +22,19 @@ function bindWithPositioning(view, items, index, controller) {
|
|
|
21
22
|
view.context.index = index;
|
|
22
23
|
view.bind(items[index]);
|
|
23
24
|
}
|
|
25
|
+
function isCommentNode(node) {
|
|
26
|
+
return node.nodeType === Node.COMMENT_NODE;
|
|
27
|
+
}
|
|
28
|
+
export class HydrationRepeatError extends Error {
|
|
29
|
+
constructor(
|
|
30
|
+
/**
|
|
31
|
+
* The error message
|
|
32
|
+
*/
|
|
33
|
+
message, propertyBag) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.propertyBag = propertyBag;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
24
38
|
/**
|
|
25
39
|
* A behavior that renders a template for each item in an array.
|
|
26
40
|
* @public
|
|
@@ -37,12 +51,13 @@ export class RepeatBehavior {
|
|
|
37
51
|
*/
|
|
38
52
|
constructor(directive) {
|
|
39
53
|
this.directive = directive;
|
|
40
|
-
this.views = [];
|
|
41
54
|
this.items = null;
|
|
42
55
|
this.itemsObserver = null;
|
|
43
56
|
this.bindView = bindWithoutPositioning;
|
|
44
|
-
|
|
45
|
-
this.
|
|
57
|
+
/** @internal */
|
|
58
|
+
this.views = [];
|
|
59
|
+
this.itemsBindingObserver = directive.dataBinding.createObserver(this, directive);
|
|
60
|
+
this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
|
|
46
61
|
if (directive.options.positioning) {
|
|
47
62
|
this.bindView = bindWithPositioning;
|
|
48
63
|
}
|
|
@@ -52,12 +67,19 @@ export class RepeatBehavior {
|
|
|
52
67
|
* @param controller - The view controller that manages the lifecycle of this behavior.
|
|
53
68
|
*/
|
|
54
69
|
bind(controller) {
|
|
55
|
-
this.location = controller.targets[this.directive.
|
|
70
|
+
this.location = controller.targets[this.directive.targetNodeId];
|
|
56
71
|
this.controller = controller;
|
|
57
72
|
this.items = this.itemsBindingObserver.bind(controller);
|
|
58
73
|
this.template = this.templateBindingObserver.bind(controller);
|
|
59
74
|
this.observeItems(true);
|
|
60
|
-
this.
|
|
75
|
+
if (isHydratable(this.template) &&
|
|
76
|
+
isHydratable(controller) &&
|
|
77
|
+
controller.hydrationStage !== HydrationStage.hydrated) {
|
|
78
|
+
this.hydrateViews(this.template);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.refreshAllViews();
|
|
82
|
+
}
|
|
61
83
|
controller.onUnbind(this);
|
|
62
84
|
}
|
|
63
85
|
/**
|
|
@@ -158,10 +180,10 @@ export class RepeatBehavior {
|
|
|
158
180
|
leftoverViews[i].dispose();
|
|
159
181
|
}
|
|
160
182
|
if (this.directive.options.positioning) {
|
|
161
|
-
for (let i = 0,
|
|
183
|
+
for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
|
|
162
184
|
const context = views[i].context;
|
|
163
|
-
context.length =
|
|
164
|
-
context.index =
|
|
185
|
+
context.length = viewsLength;
|
|
186
|
+
context.index = i;
|
|
165
187
|
}
|
|
166
188
|
}
|
|
167
189
|
}
|
|
@@ -195,6 +217,18 @@ export class RepeatBehavior {
|
|
|
195
217
|
for (; i < itemsLength; ++i) {
|
|
196
218
|
if (i < viewsLength) {
|
|
197
219
|
const view = views[i];
|
|
220
|
+
if (!view) {
|
|
221
|
+
const serializer = new XMLSerializer();
|
|
222
|
+
throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
|
|
223
|
+
index: i,
|
|
224
|
+
hydrationStage: this.controller
|
|
225
|
+
.hydrationStage,
|
|
226
|
+
itemsLength,
|
|
227
|
+
viewsState: views.map(v => (v ? "hydrated" : "empty")),
|
|
228
|
+
viewTemplateString: serializer.serializeToString(template.create().fragment),
|
|
229
|
+
rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
198
232
|
bindView(view, items, i, controller);
|
|
199
233
|
}
|
|
200
234
|
else {
|
|
@@ -213,7 +247,76 @@ export class RepeatBehavior {
|
|
|
213
247
|
unbindAllViews() {
|
|
214
248
|
const views = this.views;
|
|
215
249
|
for (let i = 0, ii = views.length; i < ii; ++i) {
|
|
216
|
-
views[i]
|
|
250
|
+
const view = views[i];
|
|
251
|
+
if (!view) {
|
|
252
|
+
const serializer = new XMLSerializer();
|
|
253
|
+
throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
|
|
254
|
+
index: i,
|
|
255
|
+
hydrationStage: this.controller.hydrationStage,
|
|
256
|
+
viewsState: views.map(v => (v ? "hydrated" : "empty")),
|
|
257
|
+
rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
view.unbind();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
hydrateViews(template) {
|
|
264
|
+
if (!this.items) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.views = new Array(this.items.length);
|
|
268
|
+
let current = this.location.previousSibling;
|
|
269
|
+
while (current !== null) {
|
|
270
|
+
if (!isCommentNode(current)) {
|
|
271
|
+
current = current.previousSibling;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const index = HydrationMarkup.parseRepeatEndMarker(current.data);
|
|
275
|
+
if (index === null) {
|
|
276
|
+
current = current.previousSibling;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
current.data = "";
|
|
280
|
+
// end of repeat is the previousSibling of end comment
|
|
281
|
+
const end = current.previousSibling;
|
|
282
|
+
if (!end) {
|
|
283
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": end should never be null.`);
|
|
284
|
+
}
|
|
285
|
+
// find start marker
|
|
286
|
+
let start = end;
|
|
287
|
+
// How many unmatched end markers we've encountered
|
|
288
|
+
let unmatchedEndMarkers = 0;
|
|
289
|
+
while (start !== null) {
|
|
290
|
+
if (isCommentNode(start)) {
|
|
291
|
+
if (HydrationMarkup.isRepeatViewEndMarker(start.data)) {
|
|
292
|
+
unmatchedEndMarkers++;
|
|
293
|
+
}
|
|
294
|
+
else if (HydrationMarkup.isRepeatViewStartMarker(start.data)) {
|
|
295
|
+
if (unmatchedEndMarkers) {
|
|
296
|
+
unmatchedEndMarkers--;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
if (HydrationMarkup.parseRepeatStartMarker(start.data) !==
|
|
300
|
+
index) {
|
|
301
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host
|
|
302
|
+
.nodeName}": Mismatched start and end markers.`);
|
|
303
|
+
}
|
|
304
|
+
start.data = "";
|
|
305
|
+
current = start.previousSibling;
|
|
306
|
+
// start of repeat content is the nextSibling of start comment
|
|
307
|
+
start = start.nextSibling;
|
|
308
|
+
const view = template.hydrate(start, end);
|
|
309
|
+
this.views[index] = view;
|
|
310
|
+
this.bindView(view, this.items, index, this.controller);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
start = start.previousSibling;
|
|
316
|
+
}
|
|
317
|
+
if (!start) {
|
|
318
|
+
throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": start should never be null.`);
|
|
319
|
+
}
|
|
217
320
|
}
|
|
218
321
|
}
|
|
219
322
|
}
|
|
@@ -232,10 +335,6 @@ export class RepeatDirective {
|
|
|
232
335
|
this.dataBinding = dataBinding;
|
|
233
336
|
this.templateBinding = templateBinding;
|
|
234
337
|
this.options = options;
|
|
235
|
-
/**
|
|
236
|
-
* The unique id of the factory.
|
|
237
|
-
*/
|
|
238
|
-
this.id = nextId();
|
|
239
338
|
ArrayObserver.enable();
|
|
240
339
|
}
|
|
241
340
|
/**
|