@microsoft/fast-element 2.0.0-beta.9 → 2.0.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.
Files changed (142) hide show
  1. package/.eslintrc.json +1 -1
  2. package/CHANGELOG.json +518 -0
  3. package/CHANGELOG.md +181 -1
  4. package/README.md +1 -9
  5. package/api-extractor.context.json +14 -0
  6. package/api-extractor.di.json +14 -0
  7. package/dist/context/context.api.json +1068 -0
  8. package/dist/di/di.api.json +4929 -0
  9. package/dist/dts/binding/binding.d.ts +49 -0
  10. package/dist/dts/binding/normalize.d.ts +9 -0
  11. package/dist/dts/binding/one-time.d.ts +11 -0
  12. package/dist/dts/binding/one-way.d.ts +20 -0
  13. package/dist/dts/{templating/binding-signal.d.ts → binding/signal.d.ts} +19 -4
  14. package/dist/dts/{templating/binding-two-way.d.ts → binding/two-way.d.ts} +9 -5
  15. package/dist/dts/components/attributes.d.ts +7 -1
  16. package/dist/dts/components/element-controller.d.ts +104 -8
  17. package/dist/dts/components/element-hydration.d.ts +2 -0
  18. package/dist/dts/components/fast-definitions.d.ts +6 -0
  19. package/dist/dts/components/hydration.d.ts +56 -0
  20. package/dist/dts/components/install-hydration.d.ts +1 -0
  21. package/dist/dts/context.d.ts +29 -15
  22. package/dist/dts/di/di.d.ts +0 -5
  23. package/dist/dts/dom-policy.d.ts +83 -0
  24. package/dist/dts/dom.d.ts +100 -0
  25. package/dist/dts/hydration/target-builder.d.ts +63 -0
  26. package/dist/dts/index.d.ts +33 -26
  27. package/dist/dts/index.rollup.d.ts +0 -1
  28. package/dist/dts/index.rollup.debug.d.ts +0 -1
  29. package/dist/dts/interfaces.d.ts +32 -82
  30. package/dist/dts/metadata.d.ts +6 -5
  31. package/dist/dts/observation/arrays.d.ts +1 -1
  32. package/dist/dts/observation/observable.bench.d.ts +18 -0
  33. package/dist/dts/observation/observable.d.ts +5 -5
  34. package/dist/dts/pending-task.d.ts +19 -7
  35. package/dist/dts/platform.d.ts +11 -2
  36. package/dist/dts/polyfills.d.ts +0 -8
  37. package/dist/dts/styles/css-binding-directive.d.ts +60 -0
  38. package/dist/dts/styles/css.d.ts +9 -7
  39. package/dist/dts/styles/element-styles.d.ts +1 -14
  40. package/dist/dts/styles/host.d.ts +2 -5
  41. package/dist/dts/styles/style-strategy.d.ts +42 -0
  42. package/dist/dts/templating/compiler.d.ts +11 -13
  43. package/dist/dts/templating/{binding.d.ts → html-binding-directive.d.ts} +21 -41
  44. package/dist/dts/templating/html-directive.d.ts +44 -140
  45. package/dist/dts/templating/install-hydratable-view-templates.d.ts +1 -0
  46. package/dist/dts/templating/node-observation.d.ts +11 -1
  47. package/dist/dts/templating/ref.d.ts +4 -0
  48. package/dist/dts/templating/render.bench.d.ts +3 -0
  49. package/dist/dts/templating/render.d.ts +49 -9
  50. package/dist/dts/templating/repeat-basic-reverse.bench.d.ts +3 -0
  51. package/dist/dts/templating/repeat-basic-shift.bench.d.ts +3 -0
  52. package/dist/dts/templating/repeat.d.ts +31 -9
  53. package/dist/dts/templating/template.d.ts +97 -12
  54. package/dist/dts/templating/view.d.ts +146 -29
  55. package/dist/dts/templating/when-basic.bench.d.ts +3 -0
  56. package/dist/dts/templating/when-conditional.bench.d.ts +3 -0
  57. package/dist/dts/templating/when-switch.bench.d.ts +3 -0
  58. package/dist/dts/templating/when.d.ts +3 -1
  59. package/dist/dts/testing/fakes.d.ts +12 -1
  60. package/dist/dts/tsdoc-metadata.json +1 -1
  61. package/dist/dts/utilities.d.ts +55 -1
  62. package/dist/esm/binding/binding.js +18 -0
  63. package/dist/esm/binding/normalize.js +17 -0
  64. package/dist/esm/binding/one-time.js +21 -0
  65. package/dist/esm/binding/one-way.js +30 -0
  66. package/dist/esm/{templating/binding-signal.js → binding/signal.js} +22 -6
  67. package/dist/esm/{templating/binding-two-way.js → binding/two-way.js} +18 -12
  68. package/dist/esm/components/attributes.js +19 -6
  69. package/dist/esm/components/element-controller.js +319 -49
  70. package/dist/esm/components/element-hydration.js +2 -0
  71. package/dist/esm/components/fast-definitions.js +12 -4
  72. package/dist/esm/components/fast-element.js +3 -1
  73. package/dist/esm/components/hydration.js +104 -0
  74. package/dist/esm/components/install-hydration.js +3 -0
  75. package/dist/esm/context.js +26 -4
  76. package/dist/esm/debug.js +8 -2
  77. package/dist/esm/di/di.js +9 -12
  78. package/dist/esm/dom-policy.js +345 -0
  79. package/dist/esm/dom.js +101 -0
  80. package/dist/esm/hydration/target-builder.js +175 -0
  81. package/dist/esm/index.js +34 -25
  82. package/dist/esm/index.rollup.debug.js +3 -1
  83. package/dist/esm/index.rollup.js +3 -1
  84. package/dist/esm/interfaces.js +51 -3
  85. package/dist/esm/metadata.js +11 -8
  86. package/dist/esm/observation/arrays.js +1 -1
  87. package/dist/esm/observation/observable.bench.js +79 -0
  88. package/dist/esm/observation/observable.js +20 -15
  89. package/dist/esm/observation/update-queue.js +2 -2
  90. package/dist/esm/pending-task.js +13 -1
  91. package/dist/esm/platform.js +12 -2
  92. package/dist/esm/polyfills.js +3 -61
  93. package/dist/esm/styles/css-binding-directive.js +76 -0
  94. package/dist/esm/styles/css.js +14 -7
  95. package/dist/esm/styles/element-styles.js +0 -33
  96. package/dist/esm/styles/style-strategy.js +1 -0
  97. package/dist/esm/templating/children.js +8 -4
  98. package/dist/esm/templating/compiler.js +37 -44
  99. package/dist/esm/templating/html-binding-directive.js +218 -0
  100. package/dist/esm/templating/html-directive.js +25 -152
  101. package/dist/esm/templating/install-hydratable-view-templates.js +17 -0
  102. package/dist/esm/templating/node-observation.js +14 -8
  103. package/dist/esm/templating/ref.js +1 -1
  104. package/dist/esm/templating/render.bench.js +56 -0
  105. package/dist/esm/templating/render.js +74 -30
  106. package/dist/esm/templating/repeat-basic-reverse.bench.js +43 -0
  107. package/dist/esm/templating/repeat-basic-shift.bench.js +43 -0
  108. package/dist/esm/templating/repeat.js +116 -17
  109. package/dist/esm/templating/template.js +135 -60
  110. package/dist/esm/templating/view.js +254 -34
  111. package/dist/esm/templating/when-basic.bench.js +36 -0
  112. package/dist/esm/templating/when-conditional.bench.js +39 -0
  113. package/dist/esm/templating/when-switch.bench.js +68 -0
  114. package/dist/esm/templating/when.js +12 -5
  115. package/dist/esm/testing/fakes.js +32 -1
  116. package/dist/esm/testing/fixture.js +1 -1
  117. package/dist/esm/utilities.js +97 -1
  118. package/dist/fast-element.api.json +9789 -5667
  119. package/dist/fast-element.d.ts +813 -2392
  120. package/dist/fast-element.debug.js +2788 -974
  121. package/dist/fast-element.debug.min.js +3 -1
  122. package/dist/fast-element.js +2641 -833
  123. package/dist/fast-element.min.js +3 -1
  124. package/dist/fast-element.untrimmed.d.ts +662 -314
  125. package/docs/{api-report.md → api-report.api.md} +238 -151
  126. package/docs/context/api-report.api.md +69 -0
  127. package/docs/di/api-report.api.md +315 -0
  128. package/karma.conf.cjs +2 -1
  129. package/package.json +59 -47
  130. package/scripts/run-api-extractor.js +51 -0
  131. package/scripts/run-benchmarks.js +46 -0
  132. package/tensile.config.js +12 -0
  133. package/dist/dts/templating/dom.d.ts +0 -41
  134. package/dist/esm/templating/binding.js +0 -282
  135. package/dist/esm/templating/dom.js +0 -49
  136. package/docs/guide/declaring-templates.md +0 -230
  137. package/docs/guide/defining-elements.md +0 -214
  138. package/docs/guide/leveraging-css.md +0 -253
  139. package/docs/guide/next-steps.md +0 -13
  140. package/docs/guide/observables-and-state.md +0 -213
  141. package/docs/guide/using-directives.md +0 -576
  142. 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 { bind, normalizeBinding, oneTime, } from "./binding.js";
4
- import { Binding, HTMLDirective, } from "./html-directive.js";
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(directive, this);
23
- this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
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.nodeId];
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.refreshView();
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
- // It's possible that the value is the same as the previous template
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, attributes, content) {
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
- else {
163
- markup.push(`<${tagName}>`);
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 html(markup, ...values);
200
+ return ViewTemplate.create(markup, values, policy);
174
201
  }
175
202
  function create(options) {
176
- var _a, _b;
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
- template = createElementTemplate(tagName, (_b = options.attributes) !== null && _b !== void 0 ? _b : defaultAttributes, options.content);
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 = bind((s, c) => {
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 { ArrayObserver } from "../observation/arrays.js";
4
- import { Markup, nextId } from "./markup.js";
5
+ import { normalizeBinding } from "../binding/normalize.js";
5
6
  import { HTMLDirective, } from "./html-directive.js";
6
- import { HTMLView } from "./view.js";
7
- import { normalizeBinding } from "./binding.js";
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
- this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
45
- this.templateBindingObserver = directive.templateBinding.createObserver(directive, 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.nodeId];
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.refreshAllViews();
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, ii = views.length; i < ii; ++i) {
183
+ for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
162
184
  const context = views[i].context;
163
- context.length = i;
164
- context.index = ii;
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].unbind();
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
  /**