@microsoft/fast-element 2.0.0-beta.3 → 2.0.0-beta.4

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 (50) hide show
  1. package/CHANGELOG.json +81 -0
  2. package/CHANGELOG.md +20 -1
  3. package/dist/dts/components/fast-definitions.d.ts +9 -8
  4. package/dist/dts/components/fast-element.d.ts +8 -4
  5. package/dist/dts/context.d.ts +1 -1
  6. package/dist/dts/di/di.d.ts +854 -0
  7. package/dist/dts/hooks.d.ts +2 -2
  8. package/dist/dts/interfaces.d.ts +38 -7
  9. package/dist/dts/observation/observable.d.ts +19 -13
  10. package/dist/dts/styles/element-styles.d.ts +6 -0
  11. package/dist/dts/templating/binding-signal.d.ts +10 -27
  12. package/dist/dts/templating/binding-two-way.d.ts +16 -41
  13. package/dist/dts/templating/binding.d.ts +79 -118
  14. package/dist/dts/templating/html-directive.d.ts +28 -2
  15. package/dist/dts/templating/render.d.ts +277 -0
  16. package/dist/dts/templating/repeat.d.ts +12 -16
  17. package/dist/dts/templating/template.d.ts +3 -3
  18. package/dist/dts/templating/when.d.ts +3 -3
  19. package/dist/dts/testing/exports.d.ts +2 -0
  20. package/dist/dts/testing/fixture.d.ts +90 -0
  21. package/dist/dts/testing/timeout.d.ts +7 -0
  22. package/dist/esm/components/fast-definitions.js +25 -27
  23. package/dist/esm/components/fast-element.js +16 -8
  24. package/dist/esm/context.js +5 -1
  25. package/dist/esm/debug.js +34 -4
  26. package/dist/esm/di/di.js +1349 -0
  27. package/dist/esm/observation/observable.js +4 -4
  28. package/dist/esm/platform.js +1 -1
  29. package/dist/esm/styles/element-styles.js +14 -0
  30. package/dist/esm/templating/binding-signal.js +56 -61
  31. package/dist/esm/templating/binding-two-way.js +51 -35
  32. package/dist/esm/templating/binding.js +137 -156
  33. package/dist/esm/templating/compiler.js +29 -7
  34. package/dist/esm/templating/html-directive.js +12 -1
  35. package/dist/esm/templating/render.js +392 -0
  36. package/dist/esm/templating/repeat.js +54 -40
  37. package/dist/esm/templating/template.js +8 -5
  38. package/dist/esm/templating/when.js +5 -4
  39. package/dist/esm/testing/exports.js +2 -0
  40. package/dist/esm/testing/fixture.js +88 -0
  41. package/dist/esm/testing/timeout.js +24 -0
  42. package/dist/fast-element.api.json +3257 -3151
  43. package/dist/fast-element.d.ts +210 -209
  44. package/dist/fast-element.debug.js +329 -248
  45. package/dist/fast-element.debug.min.js +1 -1
  46. package/dist/fast-element.js +295 -244
  47. package/dist/fast-element.min.js +1 -1
  48. package/dist/fast-element.untrimmed.d.ts +218 -214
  49. package/docs/api-report.md +83 -85
  50. package/package.json +13 -1
@@ -0,0 +1,392 @@
1
+ import { FASTElementDefinition } from "../components/fast-definitions.js";
2
+ import { isFunction, isString } from "../interfaces.js";
3
+ import { bind, normalizeBinding, oneTime, } from "./binding.js";
4
+ import { Binding, HTMLDirective, } from "./html-directive.js";
5
+ import { Markup } from "./markup.js";
6
+ import { html, } from "./template.js";
7
+ /**
8
+ * A Behavior that enables advanced rendering.
9
+ * @public
10
+ */
11
+ export class RenderBehavior {
12
+ /**
13
+ * Creates an instance of RenderBehavior.
14
+ * @param location - A Node representing the location where this behavior will render.
15
+ * @param dataBinding - A binding expression that returns the data to render.
16
+ * @param templateBinding - A binding expression that returns the template to use with the data.
17
+ */
18
+ constructor(directive, location) {
19
+ this.directive = directive;
20
+ this.location = location;
21
+ this.source = null;
22
+ this.view = null;
23
+ this.data = null;
24
+ this.originalContext = void 0;
25
+ this.childContext = void 0;
26
+ this.dataBindingObserver = directive.dataBinding.createObserver(directive, this);
27
+ this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
28
+ }
29
+ /**
30
+ * Bind this behavior to the source.
31
+ * @param source - The source to bind to.
32
+ * @param context - The execution context that the binding is operating within.
33
+ */
34
+ bind(source, context) {
35
+ this.source = source;
36
+ this.originalContext = context;
37
+ this.childContext = context.createChildContext(source);
38
+ this.data = this.dataBindingObserver.observe(source, this.originalContext);
39
+ this.template = this.templateBindingObserver.observe(source, this.originalContext);
40
+ this.refreshView();
41
+ }
42
+ /**
43
+ * Unbinds this behavior from the source.
44
+ * @param source - The source to unbind from.
45
+ */
46
+ unbind(source, context) {
47
+ this.source = null;
48
+ this.data = null;
49
+ const view = this.view;
50
+ if (view !== null && view.isComposed) {
51
+ view.unbind();
52
+ view.needsBindOnly = true;
53
+ }
54
+ this.dataBindingObserver.dispose();
55
+ this.templateBindingObserver.dispose();
56
+ }
57
+ /** @internal */
58
+ handleChange(source, observer) {
59
+ if (observer === this.dataBindingObserver) {
60
+ this.data = this.dataBindingObserver.observe(this.source, this.originalContext);
61
+ }
62
+ if (this.directive.templateBindingDependsOnData ||
63
+ observer === this.templateBindingObserver) {
64
+ this.template = this.templateBindingObserver.observe(this.source, this.originalContext);
65
+ }
66
+ this.refreshView();
67
+ }
68
+ refreshView() {
69
+ let view = this.view;
70
+ const template = this.template;
71
+ if (view === null) {
72
+ this.view = view = template.create();
73
+ }
74
+ else {
75
+ // If there is a previous view, but it wasn't created
76
+ // from the same template as the new value, then we
77
+ // need to remove the old view if it's still in the DOM
78
+ // and create a new view from the template.
79
+ if (view.$fastTemplate !== template) {
80
+ if (view.isComposed) {
81
+ view.remove();
82
+ view.unbind();
83
+ }
84
+ this.view = view = template.create();
85
+ }
86
+ }
87
+ // It's possible that the value is the same as the previous template
88
+ // and that there's actually no need to compose it.
89
+ if (!view.isComposed) {
90
+ view.isComposed = true;
91
+ view.bind(this.data, this.childContext);
92
+ view.insertBefore(this.location);
93
+ view.$fastTemplate = template;
94
+ }
95
+ else if (view.needsBindOnly) {
96
+ view.needsBindOnly = false;
97
+ view.bind(this.data, this.childContext);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * A Directive that enables use of the RenderBehavior.
103
+ * @public
104
+ */
105
+ export class RenderDirective {
106
+ /**
107
+ * Creates an instance of RenderDirective.
108
+ * @param dataBinding - A binding expression that returns the data to render.
109
+ * @param templateBinding - A binding expression that returns the template to use to render the data.
110
+ */
111
+ constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
112
+ this.dataBinding = dataBinding;
113
+ this.templateBinding = templateBinding;
114
+ this.templateBindingDependsOnData = templateBindingDependsOnData;
115
+ }
116
+ /**
117
+ * Creates HTML to be used within a template.
118
+ * @param add - Can be used to add behavior factories to a template.
119
+ */
120
+ createHTML(add) {
121
+ return Markup.comment(add(this));
122
+ }
123
+ /**
124
+ * Creates a behavior.
125
+ * @param targets - The targets available for behaviors to be attached to.
126
+ */
127
+ createBehavior(targets) {
128
+ return new RenderBehavior(this, targets[this.nodeId]);
129
+ }
130
+ }
131
+ HTMLDirective.define(RenderDirective);
132
+ function isElementRenderOptions(object) {
133
+ return !!object.element || !!object.tagName;
134
+ }
135
+ const typeToInstructionLookup = new Map();
136
+ /* eslint @typescript-eslint/naming-convention: "off"*/
137
+ const defaultAttributes = { ":model": x => x };
138
+ const brand = Symbol("RenderInstruction");
139
+ const defaultViewName = "default-view";
140
+ const nullTemplate = html `
141
+  
142
+ `;
143
+ function instructionToTemplate(def) {
144
+ if (def === void 0) {
145
+ return nullTemplate;
146
+ }
147
+ return def.template;
148
+ }
149
+ function createElementTemplate(tagName, attributes, content) {
150
+ const markup = [];
151
+ const values = [];
152
+ if (attributes) {
153
+ const attrNames = Object.getOwnPropertyNames(attributes);
154
+ markup.push(`<${tagName}`);
155
+ for (let i = 0, ii = attrNames.length; i < ii; ++i) {
156
+ const name = attrNames[i];
157
+ if (i === 0) {
158
+ markup[0] = `${markup[0]} ${name}="`;
159
+ }
160
+ else {
161
+ markup.push(`" ${name}="`);
162
+ }
163
+ values.push(attributes[name]);
164
+ }
165
+ markup.push(`">`);
166
+ }
167
+ else {
168
+ markup.push(`<${tagName}>`);
169
+ }
170
+ if (content && isFunction(content.create)) {
171
+ values.push(content);
172
+ markup.push(`</${tagName}>`);
173
+ }
174
+ else {
175
+ const lastIndex = markup.length - 1;
176
+ markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
177
+ }
178
+ return html(markup, ...values);
179
+ }
180
+ function create(options) {
181
+ var _a, _b;
182
+ const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
183
+ let template;
184
+ if (isElementRenderOptions(options)) {
185
+ let tagName = options.tagName;
186
+ if (!tagName) {
187
+ const def = FASTElementDefinition.getByType(options.element);
188
+ if (def) {
189
+ tagName = def.name;
190
+ }
191
+ else {
192
+ throw new Error("Invalid element for model rendering.");
193
+ }
194
+ }
195
+ template = createElementTemplate(tagName, (_b = options.attributes) !== null && _b !== void 0 ? _b : defaultAttributes, options.content);
196
+ }
197
+ else {
198
+ template = options.template;
199
+ }
200
+ return {
201
+ brand,
202
+ type: options.type,
203
+ name,
204
+ template,
205
+ };
206
+ }
207
+ function instanceOf(object) {
208
+ return object && object.brand === brand;
209
+ }
210
+ function register(optionsOrInstruction) {
211
+ let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
212
+ if (lookup === void 0) {
213
+ typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
214
+ }
215
+ const instruction = instanceOf(optionsOrInstruction)
216
+ ? optionsOrInstruction
217
+ : create(optionsOrInstruction);
218
+ return (lookup[instruction.name] = instruction);
219
+ }
220
+ function getByType(type, name) {
221
+ const entries = typeToInstructionLookup.get(type);
222
+ if (entries === void 0) {
223
+ return void 0;
224
+ }
225
+ return entries[name !== null && name !== void 0 ? name : defaultViewName];
226
+ }
227
+ function getForInstance(object, name) {
228
+ if (object) {
229
+ return getByType(object.constructor, name);
230
+ }
231
+ return void 0;
232
+ }
233
+ /**
234
+ * Provides APIs for creating and interacting with render instructions.
235
+ * @public
236
+ */
237
+ export const RenderInstruction = Object.freeze({
238
+ /**
239
+ * Checks whether the provided object is a RenderInstruction.
240
+ * @param object - The object to check.
241
+ * @returns true if the object is a RenderInstruction; false otherwise
242
+ */
243
+ instanceOf,
244
+ /**
245
+ * Creates a RenderInstruction for a set of options.
246
+ * @param options - The options to use when creating the RenderInstruction.
247
+ */
248
+ create,
249
+ /**
250
+ * Creates a template based on a tag name.
251
+ * @param tagName - The tag name to use when creating the template.
252
+ * @param attributes - The attributes to apply to the element.
253
+ * @param content - The content to insert into the element.
254
+ * @returns A template based on the provided specifications.
255
+ */
256
+ createElementTemplate,
257
+ /**
258
+ * Creates and registers an instruction.
259
+ * @param options The options to use when creating the RenderInstruction.
260
+ * @remarks
261
+ * A previously created RenderInstruction can also be registered.
262
+ */
263
+ register,
264
+ /**
265
+ * Finds a previously registered RenderInstruction by type and optionally by name.
266
+ * @param type - The type to retrieve the RenderInstruction for.
267
+ * @param name - An optional name used in differentiating between multiple registered instructions.
268
+ * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
269
+ */
270
+ getByType,
271
+ /**
272
+ * Finds a previously registered RenderInstruction for the instance's type and optionally by name.
273
+ * @param object - The instance to retrieve the RenderInstruction for.
274
+ * @param name - An optional name used in differentiating between multiple registered instructions.
275
+ * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
276
+ */
277
+ getForInstance,
278
+ });
279
+ export function renderWith(value, name) {
280
+ return function (type) {
281
+ if (isFunction(value)) {
282
+ register({ type, element: value, name });
283
+ }
284
+ else if (isFunction(value.create)) {
285
+ register({ type, template: value, name });
286
+ }
287
+ else {
288
+ register(Object.assign({ type }, value));
289
+ }
290
+ };
291
+ }
292
+ /**
293
+ * @internal
294
+ */
295
+ export class NodeTemplate {
296
+ constructor(node) {
297
+ this.node = node;
298
+ node.$fastTemplate = this;
299
+ }
300
+ bind(source, context) { }
301
+ unbind() { }
302
+ insertBefore(refNode) {
303
+ refNode.parentNode.insertBefore(this.node, refNode);
304
+ }
305
+ remove() {
306
+ this.node.parentNode.removeChild(this.node);
307
+ }
308
+ create() {
309
+ return this;
310
+ }
311
+ }
312
+ /**
313
+ * Creates a RenderDirective for use in advanced rendering scenarios.
314
+ * @param value - The binding expression that returns the data to be rendered. The expression
315
+ * can also return a Node to render directly.
316
+ * @param template - A template to render the data with
317
+ * or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
318
+ * Expressions can also be provided to dynamically determine either the template or the name.
319
+ * @returns A RenderDirective suitable for use in a template.
320
+ * @remarks
321
+ * If no binding is provided, then a default binding that returns the source is created.
322
+ * If no template is provided, then a binding is created that will use registered
323
+ * RenderInstructions to determine the view.
324
+ * If the template binding returns a string, then it will be used to look up a
325
+ * RenderInstruction to determine the view.
326
+ * @public
327
+ */
328
+ export function render(value, template) {
329
+ let dataBinding;
330
+ if (value === void 0) {
331
+ dataBinding = oneTime((source) => source);
332
+ }
333
+ else {
334
+ dataBinding = normalizeBinding(value);
335
+ }
336
+ let templateBinding;
337
+ let templateBindingDependsOnData = false;
338
+ if (template === void 0) {
339
+ templateBindingDependsOnData = true;
340
+ templateBinding = oneTime((s, c) => {
341
+ var _a;
342
+ const data = dataBinding.evaluate(s, c);
343
+ if (data instanceof Node) {
344
+ return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
345
+ }
346
+ return instructionToTemplate(getForInstance(data));
347
+ });
348
+ }
349
+ else if (isFunction(template)) {
350
+ templateBinding = bind((s, c) => {
351
+ var _a;
352
+ let result = template(s, c);
353
+ if (isString(result)) {
354
+ result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
355
+ }
356
+ else if (result instanceof Node) {
357
+ result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
358
+ }
359
+ return result;
360
+ }, true);
361
+ }
362
+ else if (isString(template)) {
363
+ templateBindingDependsOnData = true;
364
+ templateBinding = oneTime((s, c) => {
365
+ var _a;
366
+ const data = dataBinding.evaluate(s, c);
367
+ if (data instanceof Node) {
368
+ return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
369
+ }
370
+ return instructionToTemplate(getForInstance(data, template));
371
+ });
372
+ }
373
+ else if (template instanceof Binding) {
374
+ const evaluateTemplate = template.evaluate;
375
+ template.evaluate = (s, c) => {
376
+ var _a;
377
+ let result = evaluateTemplate(s, c);
378
+ if (isString(result)) {
379
+ result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
380
+ }
381
+ else if (result instanceof Node) {
382
+ result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
383
+ }
384
+ return result;
385
+ };
386
+ templateBinding = template;
387
+ }
388
+ else {
389
+ templateBinding = oneTime((s, c) => template);
390
+ }
391
+ return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
392
+ }
@@ -1,10 +1,10 @@
1
- import { isFunction } from "../interfaces.js";
2
1
  import { Observable, } from "../observation/observable.js";
3
2
  import { emptyArray } from "../platform.js";
4
3
  import { ArrayObserver } from "../observation/arrays.js";
5
- import { Markup } from "./markup.js";
4
+ import { Markup, nextId } from "./markup.js";
6
5
  import { HTMLDirective, } from "./html-directive.js";
7
6
  import { HTMLView } from "./view.js";
7
+ import { normalizeBinding } from "./binding.js";
8
8
  const defaultRepeatOptions = Object.freeze({
9
9
  positioning: false,
10
10
  recycle: true,
@@ -23,17 +23,15 @@ export class RepeatBehavior {
23
23
  /**
24
24
  * Creates an instance of RepeatBehavior.
25
25
  * @param location - The location in the DOM to render the repeat.
26
- * @param itemsBinding - The array to render.
26
+ * @param dataBinding - The array to render.
27
27
  * @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
28
28
  * @param templateBinding - The template to render for each item.
29
29
  * @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
30
30
  * @param options - Options used to turn on special repeat features.
31
31
  */
32
- constructor(location, itemsBinding, isItemsBindingVolatile, templateBinding, isTemplateBindingVolatile, options) {
32
+ constructor(directive, location) {
33
+ this.directive = directive;
33
34
  this.location = location;
34
- this.itemsBinding = itemsBinding;
35
- this.templateBinding = templateBinding;
36
- this.options = options;
37
35
  this.source = null;
38
36
  this.views = [];
39
37
  this.items = null;
@@ -41,9 +39,9 @@ export class RepeatBehavior {
41
39
  this.context = void 0;
42
40
  this.childContext = void 0;
43
41
  this.bindView = bindWithoutPositioning;
44
- this.itemsBindingObserver = Observable.binding(itemsBinding, this, isItemsBindingVolatile);
45
- this.templateBindingObserver = Observable.binding(templateBinding, this, isTemplateBindingVolatile);
46
- if (options.positioning) {
42
+ this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
43
+ this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
44
+ if (directive.options.positioning) {
47
45
  this.bindView = bindWithPositioning;
48
46
  }
49
47
  }
@@ -81,12 +79,12 @@ export class RepeatBehavior {
81
79
  * @param args - The details about what was changed.
82
80
  */
83
81
  handleChange(source, args) {
84
- if (source === this.itemsBinding) {
82
+ if (args === this.itemsBindingObserver) {
85
83
  this.items = this.itemsBindingObserver.observe(this.source, this.context);
86
84
  this.observeItems();
87
85
  this.refreshAllViews();
88
86
  }
89
- else if (source === this.templateBinding) {
87
+ else if (args === this.templateBindingObserver) {
90
88
  this.template = this.templateBindingObserver.observe(this.source, this.context);
91
89
  this.refreshAllViews(true);
92
90
  }
@@ -115,36 +113,51 @@ export class RepeatBehavior {
115
113
  updateViews(splices) {
116
114
  const views = this.views;
117
115
  const childContext = this.childContext;
118
- const totalRemoved = [];
119
116
  const bindView = this.bindView;
120
- let removeDelta = 0;
121
- for (let i = 0, ii = splices.length; i < ii; ++i) {
122
- const splice = splices[i];
123
- const removed = splice.removed;
124
- totalRemoved.push(...views.splice(splice.index + removeDelta, removed.length));
125
- removeDelta -= splice.addedCount;
126
- }
127
117
  const items = this.items;
128
118
  const template = this.template;
119
+ const recycle = this.directive.options.recycle;
120
+ const leftoverViews = [];
121
+ let leftoverIndex = 0;
122
+ let availableViews = 0;
129
123
  for (let i = 0, ii = splices.length; i < ii; ++i) {
130
124
  const splice = splices[i];
125
+ const removed = splice.removed;
126
+ let removeIndex = 0;
131
127
  let addIndex = splice.index;
132
128
  const end = addIndex + splice.addedCount;
129
+ const removedViews = views.splice(splice.index, removed.length);
130
+ availableViews = leftoverViews.length + removedViews.length;
133
131
  for (; addIndex < end; ++addIndex) {
134
132
  const neighbor = views[addIndex];
135
133
  const location = neighbor ? neighbor.firstChild : this.location;
136
- const view = this.options.recycle && totalRemoved.length > 0
137
- ? totalRemoved.shift()
138
- : template.create();
134
+ let view;
135
+ if (recycle && availableViews > 0) {
136
+ if (removeIndex <= availableViews && removedViews.length > 0) {
137
+ view = removedViews[removeIndex];
138
+ removeIndex++;
139
+ }
140
+ else {
141
+ view = leftoverViews[leftoverIndex];
142
+ leftoverIndex++;
143
+ }
144
+ availableViews--;
145
+ }
146
+ else {
147
+ view = template.create();
148
+ }
139
149
  views.splice(addIndex, 0, view);
140
150
  bindView(view, items, addIndex, childContext);
141
151
  view.insertBefore(location);
142
152
  }
153
+ if (removedViews[removeIndex]) {
154
+ leftoverViews.push(...removedViews.slice(removeIndex));
155
+ }
143
156
  }
144
- for (let i = 0, ii = totalRemoved.length; i < ii; ++i) {
145
- totalRemoved[i].dispose();
157
+ for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
158
+ leftoverViews[i].dispose();
146
159
  }
147
- if (this.options.positioning) {
160
+ if (this.directive.options.positioning) {
148
161
  for (let i = 0, ii = views.length; i < ii; ++i) {
149
162
  views[i].context.updatePosition(i, ii);
150
163
  }
@@ -159,7 +172,7 @@ export class RepeatBehavior {
159
172
  let itemsLength = items.length;
160
173
  let views = this.views;
161
174
  let viewsLength = views.length;
162
- if (itemsLength === 0 || templateChanged || !this.options.recycle) {
175
+ if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
163
176
  // all views need to be removed
164
177
  HTMLView.disposeContiguousBatch(views);
165
178
  viewsLength = 0;
@@ -209,17 +222,19 @@ export class RepeatBehavior {
209
222
  export class RepeatDirective {
210
223
  /**
211
224
  * Creates an instance of RepeatDirective.
212
- * @param itemsBinding - The binding that provides the array to render.
225
+ * @param dataBinding - The binding that provides the array to render.
213
226
  * @param templateBinding - The template binding used to obtain a template to render for each item in the array.
214
227
  * @param options - Options used to turn on special repeat features.
215
228
  */
216
- constructor(itemsBinding, templateBinding, options) {
217
- this.itemsBinding = itemsBinding;
229
+ constructor(dataBinding, templateBinding, options) {
230
+ this.dataBinding = dataBinding;
218
231
  this.templateBinding = templateBinding;
219
232
  this.options = options;
233
+ /**
234
+ * The unique id of the factory.
235
+ */
236
+ this.id = nextId();
220
237
  ArrayObserver.enable();
221
- this.isItemsBindingVolatile = Observable.isVolatileBinding(itemsBinding);
222
- this.isTemplateBindingVolatile = Observable.isVolatileBinding(templateBinding);
223
238
  }
224
239
  /**
225
240
  * Creates a placeholder string based on the directive's index within the template.
@@ -233,21 +248,20 @@ export class RepeatDirective {
233
248
  * @param target - The node instance to create the behavior for.
234
249
  */
235
250
  createBehavior(targets) {
236
- return new RepeatBehavior(targets[this.nodeId], this.itemsBinding, this.isItemsBindingVolatile, this.templateBinding, this.isTemplateBindingVolatile, this.options);
251
+ return new RepeatBehavior(this, targets[this.nodeId]);
237
252
  }
238
253
  }
239
254
  HTMLDirective.define(RepeatDirective);
240
255
  /**
241
256
  * A directive that enables list rendering.
242
- * @param itemsBinding - The array to render.
243
- * @param templateOrTemplateBinding - The template or a template binding used obtain a template
257
+ * @param items - The array to render.
258
+ * @param template - The template or a template binding used obtain a template
244
259
  * to render for each item in the array.
245
260
  * @param options - Options used to turn on special repeat features.
246
261
  * @public
247
262
  */
248
- export function repeat(itemsBinding, templateOrTemplateBinding, options = defaultRepeatOptions) {
249
- const templateBinding = isFunction(templateOrTemplateBinding)
250
- ? templateOrTemplateBinding
251
- : () => templateOrTemplateBinding;
252
- return new RepeatDirective(itemsBinding, templateBinding, options);
263
+ export function repeat(items, template, options = defaultRepeatOptions) {
264
+ const dataBinding = normalizeBinding(items);
265
+ const templateBinding = normalizeBinding(template);
266
+ return new RepeatDirective(dataBinding, templateBinding, options);
253
267
  }
@@ -1,8 +1,8 @@
1
1
  import { isFunction, isString } from "../interfaces.js";
2
2
  import { ExecutionContext } from "../observation/observable.js";
3
- import { bind, oneTime } from "./binding.js";
3
+ import { bind, HTMLBindingDirective, oneTime } from "./binding.js";
4
4
  import { Compiler } from "./compiler.js";
5
- import { Aspect, HTMLDirective, } from "./html-directive.js";
5
+ import { Aspect, Binding, HTMLDirective, } from "./html-directive.js";
6
6
  import { nextId } from "./markup.js";
7
7
  /**
8
8
  * A template capable of creating HTMLView instances or rendering directly to DOM.
@@ -78,12 +78,12 @@ export function html(strings, ...values) {
78
78
  let definition;
79
79
  html += currentString;
80
80
  if (isFunction(currentValue)) {
81
- html += createAspectedHTML(bind(currentValue), currentString, add);
81
+ html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
82
82
  }
83
83
  else if (isString(currentValue)) {
84
84
  const match = lastAttributeNameRegex.exec(currentString);
85
85
  if (match !== null) {
86
- const directive = bind(() => currentValue, oneTime);
86
+ const directive = new HTMLBindingDirective(oneTime(() => currentValue));
87
87
  Aspect.assign(directive, match[2]);
88
88
  html += directive.createHTML(add);
89
89
  }
@@ -91,8 +91,11 @@ export function html(strings, ...values) {
91
91
  html += currentValue;
92
92
  }
93
93
  }
94
+ else if (currentValue instanceof Binding) {
95
+ html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
96
+ }
94
97
  else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
95
- html += createAspectedHTML(bind(() => currentValue, oneTime), currentString, add);
98
+ html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
96
99
  }
97
100
  else {
98
101
  if (definition.aspected) {
@@ -1,14 +1,15 @@
1
1
  import { isFunction } from "../interfaces.js";
2
2
  /**
3
3
  * A directive that enables basic conditional rendering in a template.
4
- * @param binding - The condition to test for rendering.
4
+ * @param condition - The condition to test for rendering.
5
5
  * @param templateOrTemplateBinding - The template or a binding that gets
6
6
  * the template to render when the condition is true.
7
7
  * @public
8
8
  */
9
- export function when(binding, templateOrTemplateBinding) {
10
- const getTemplate = isFunction(templateOrTemplateBinding)
9
+ export function when(condition, templateOrTemplateBinding) {
10
+ const dataBinding = isFunction(condition) ? condition : () => condition;
11
+ const templateBinding = isFunction(templateOrTemplateBinding)
11
12
  ? templateOrTemplateBinding
12
13
  : () => templateOrTemplateBinding;
13
- return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
14
+ return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
14
15
  }
@@ -0,0 +1,2 @@
1
+ export { timeout } from "./timeout.js";
2
+ export * from "./fixture.js";