@microsoft/fast-element 1.10.5 → 2.0.0-beta.10

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 (122) hide show
  1. package/.eslintrc.json +1 -12
  2. package/CHANGELOG.json +629 -6
  3. package/CHANGELOG.md +152 -5
  4. package/dist/dts/components/attributes.d.ts +14 -1
  5. package/dist/dts/components/{controller.d.ts → element-controller.d.ts} +32 -32
  6. package/dist/dts/components/fast-definitions.d.ts +51 -11
  7. package/dist/dts/components/fast-element.d.ts +18 -23
  8. package/dist/dts/context.d.ts +157 -0
  9. package/dist/{esm/observation/behavior.js → dts/debug.d.ts} +0 -0
  10. package/dist/dts/di/di.d.ts +899 -0
  11. package/dist/dts/index.d.ts +17 -16
  12. package/dist/dts/index.debug.d.ts +2 -0
  13. package/dist/dts/index.rollup.d.ts +2 -0
  14. package/dist/dts/index.rollup.debug.d.ts +3 -0
  15. package/dist/dts/interfaces.d.ts +176 -0
  16. package/dist/dts/metadata.d.ts +25 -0
  17. package/dist/dts/observation/arrays.d.ts +207 -0
  18. package/dist/dts/observation/notifier.d.ts +18 -18
  19. package/dist/dts/observation/observable.d.ts +117 -34
  20. package/dist/dts/observation/update-queue.d.ts +40 -0
  21. package/dist/dts/pending-task.d.ts +20 -0
  22. package/dist/dts/platform.d.ts +23 -66
  23. package/dist/dts/polyfills.d.ts +8 -0
  24. package/dist/dts/state/exports.d.ts +3 -0
  25. package/dist/dts/state/reactive.d.ts +8 -0
  26. package/dist/dts/state/state.d.ts +141 -0
  27. package/dist/dts/state/visitor.d.ts +6 -0
  28. package/dist/dts/state/watch.d.ts +10 -0
  29. package/dist/dts/styles/css-directive.d.ts +44 -6
  30. package/dist/dts/styles/css.d.ts +19 -3
  31. package/dist/dts/styles/element-styles.d.ts +49 -63
  32. package/dist/dts/styles/host.d.ts +68 -0
  33. package/dist/dts/templating/binding-signal.d.ts +21 -0
  34. package/dist/dts/templating/binding-two-way.d.ts +39 -0
  35. package/dist/dts/templating/binding.d.ts +101 -70
  36. package/dist/dts/templating/children.d.ts +18 -15
  37. package/dist/dts/templating/compiler.d.ts +46 -28
  38. package/dist/dts/templating/dom.d.ts +41 -0
  39. package/dist/dts/templating/html-directive.d.ts +239 -45
  40. package/dist/dts/templating/markup.d.ts +48 -0
  41. package/dist/dts/templating/node-observation.d.ts +45 -30
  42. package/dist/dts/templating/ref.d.ts +6 -20
  43. package/dist/dts/templating/render.d.ts +272 -0
  44. package/dist/dts/templating/repeat.d.ts +36 -33
  45. package/dist/dts/templating/slotted.d.ts +13 -14
  46. package/dist/dts/templating/template.d.ts +28 -22
  47. package/dist/dts/templating/view.d.ts +82 -24
  48. package/dist/dts/templating/when.d.ts +3 -3
  49. package/dist/dts/testing/exports.d.ts +3 -0
  50. package/dist/dts/testing/fakes.d.ts +4 -0
  51. package/dist/dts/testing/fixture.d.ts +84 -0
  52. package/dist/dts/testing/timeout.d.ts +7 -0
  53. package/dist/{tsdoc-metadata.json → dts/tsdoc-metadata.json} +1 -1
  54. package/dist/dts/utilities.d.ts +22 -0
  55. package/dist/esm/components/attributes.js +38 -28
  56. package/dist/esm/components/{controller.js → element-controller.js} +150 -140
  57. package/dist/esm/components/fast-definitions.js +48 -46
  58. package/dist/esm/components/fast-element.js +31 -12
  59. package/dist/esm/context.js +163 -0
  60. package/dist/esm/debug.js +61 -0
  61. package/dist/esm/di/di.js +1435 -0
  62. package/dist/esm/index.debug.js +2 -0
  63. package/dist/esm/index.js +20 -14
  64. package/dist/esm/index.rollup.debug.js +3 -0
  65. package/dist/esm/index.rollup.js +2 -0
  66. package/dist/esm/interfaces.js +12 -1
  67. package/dist/esm/metadata.js +60 -0
  68. package/dist/esm/observation/arrays.js +570 -0
  69. package/dist/esm/observation/notifier.js +27 -35
  70. package/dist/esm/observation/observable.js +116 -149
  71. package/dist/esm/observation/update-queue.js +67 -0
  72. package/dist/esm/pending-task.js +16 -0
  73. package/dist/esm/platform.js +60 -42
  74. package/dist/esm/polyfills.js +85 -0
  75. package/dist/esm/state/exports.js +3 -0
  76. package/dist/esm/state/reactive.js +34 -0
  77. package/dist/esm/state/state.js +148 -0
  78. package/dist/esm/state/visitor.js +28 -0
  79. package/dist/esm/state/watch.js +36 -0
  80. package/dist/esm/styles/css-directive.js +29 -13
  81. package/dist/esm/styles/css.js +29 -42
  82. package/dist/esm/styles/element-styles.js +79 -104
  83. package/dist/esm/styles/host.js +1 -0
  84. package/dist/esm/templating/binding-signal.js +83 -0
  85. package/dist/esm/templating/binding-two-way.js +103 -0
  86. package/dist/esm/templating/binding.js +189 -159
  87. package/dist/esm/templating/children.js +33 -23
  88. package/dist/esm/templating/compiler.js +258 -152
  89. package/dist/esm/templating/dom.js +49 -0
  90. package/dist/esm/templating/html-directive.js +193 -36
  91. package/dist/esm/templating/markup.js +75 -0
  92. package/dist/esm/templating/node-observation.js +51 -45
  93. package/dist/esm/templating/ref.js +8 -25
  94. package/dist/esm/templating/render.js +391 -0
  95. package/dist/esm/templating/repeat.js +83 -79
  96. package/dist/esm/templating/slotted.js +23 -20
  97. package/dist/esm/templating/template.js +51 -93
  98. package/dist/esm/templating/view.js +125 -46
  99. package/dist/esm/templating/when.js +6 -4
  100. package/dist/esm/testing/exports.js +3 -0
  101. package/dist/esm/testing/fakes.js +76 -0
  102. package/dist/esm/testing/fixture.js +86 -0
  103. package/dist/esm/testing/timeout.js +24 -0
  104. package/dist/esm/utilities.js +44 -0
  105. package/dist/fast-element.api.json +12153 -5373
  106. package/dist/fast-element.d.ts +1448 -696
  107. package/dist/fast-element.debug.js +4107 -0
  108. package/dist/fast-element.debug.min.js +1 -0
  109. package/dist/fast-element.js +3817 -4029
  110. package/dist/fast-element.min.js +1 -1
  111. package/dist/fast-element.untrimmed.d.ts +2814 -0
  112. package/docs/api-report.md +567 -254
  113. package/docs/fast-element-2-changes.md +15 -0
  114. package/karma.conf.cjs +6 -17
  115. package/package.json +76 -15
  116. package/dist/dts/dom.d.ts +0 -112
  117. package/dist/dts/observation/array-change-records.d.ts +0 -48
  118. package/dist/dts/observation/array-observer.d.ts +0 -9
  119. package/dist/dts/observation/behavior.d.ts +0 -19
  120. package/dist/esm/dom.js +0 -207
  121. package/dist/esm/observation/array-change-records.js +0 -326
  122. package/dist/esm/observation/array-observer.js +0 -177
@@ -1,37 +1,40 @@
1
- import { AttachedBehaviorHTMLDirective } from "./html-directive.js";
2
- import { NodeObservationBehavior } from "./node-observation.js";
1
+ import { isString } from "../interfaces.js";
2
+ import { HTMLDirective } from "./html-directive.js";
3
+ import { NodeObservationDirective } from "./node-observation.js";
4
+ const slotEvent = "slotchange";
3
5
  /**
4
6
  * The runtime behavior for slotted node observation.
5
7
  * @public
6
8
  */
7
- export class SlottedBehavior extends NodeObservationBehavior {
8
- /**
9
- * Creates an instance of SlottedBehavior.
10
- * @param target - The slot element target to observe.
11
- * @param options - The options to use when observing the slot.
12
- */
13
- constructor(target, options) {
14
- super(target, options);
15
- }
9
+ export class SlottedDirective extends NodeObservationDirective {
16
10
  /**
17
11
  * Begins observation of the nodes.
12
+ * @param target - The target to observe.
18
13
  */
19
- observe() {
20
- this.target.addEventListener("slotchange", this);
14
+ observe(target) {
15
+ target.addEventListener(slotEvent, this);
21
16
  }
22
17
  /**
23
18
  * Disconnects observation of the nodes.
19
+ * @param target - The target to unobserve.
24
20
  */
25
- disconnect() {
26
- this.target.removeEventListener("slotchange", this);
21
+ disconnect(target) {
22
+ target.removeEventListener(slotEvent, this);
27
23
  }
28
24
  /**
29
- * Retrieves the nodes that should be assigned to the target.
25
+ * Retrieves the raw nodes that should be assigned to the source property.
26
+ * @param target - The target to get the node to.
30
27
  */
31
- getNodes() {
32
- return this.target.assignedNodes(this.options);
28
+ getNodes(target) {
29
+ return target.assignedNodes(this.options);
30
+ }
31
+ /** @internal */
32
+ handleEvent(event) {
33
+ const target = event.currentTarget;
34
+ this.updateTarget(this.getSource(target), this.computeNodes(target));
33
35
  }
34
36
  }
37
+ HTMLDirective.define(SlottedDirective);
35
38
  /**
36
39
  * A directive that observes the `assignedNodes()` of a slot and updates a property
37
40
  * whenever they change.
@@ -39,8 +42,8 @@ export class SlottedBehavior extends NodeObservationBehavior {
39
42
  * @public
40
43
  */
41
44
  export function slotted(propertyOrOptions) {
42
- if (typeof propertyOrOptions === "string") {
45
+ if (isString(propertyOrOptions)) {
43
46
  propertyOrOptions = { property: propertyOrOptions };
44
47
  }
45
- return new AttachedBehaviorHTMLDirective("fast-slotted", SlottedBehavior, propertyOrOptions);
48
+ return new SlottedDirective(propertyOrOptions);
46
49
  }
@@ -1,86 +1,32 @@
1
- import { DOM } from "../dom.js";
2
- import { defaultExecutionContext } from "../observation/observable.js";
3
- import { compileTemplate } from "./compiler.js";
4
- import { HTMLView } from "./view.js";
5
- import { HTMLDirective, TargetedHTMLDirective, } from "./html-directive.js";
6
- import { HTMLBindingDirective } from "./binding.js";
1
+ import { isFunction, isString } from "../interfaces.js";
2
+ import { bind, HTMLBindingDirective, oneTime } from "./binding.js";
3
+ import { Compiler } from "./compiler.js";
4
+ import { Aspect, Binding, HTMLDirective, } from "./html-directive.js";
5
+ import { nextId } from "./markup.js";
7
6
  /**
8
7
  * A template capable of creating HTMLView instances or rendering directly to DOM.
9
8
  * @public
10
9
  */
11
- /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
12
10
  export class ViewTemplate {
13
11
  /**
14
12
  * Creates an instance of ViewTemplate.
15
13
  * @param html - The html representing what this template will instantiate, including placeholders for directives.
16
- * @param directives - The directives that will be connected to placeholders in the html.
14
+ * @param factories - The directives that will be connected to placeholders in the html.
17
15
  */
18
- constructor(html, directives) {
19
- this.behaviorCount = 0;
20
- this.hasHostBehaviors = false;
21
- this.fragment = null;
22
- this.targetOffset = 0;
23
- this.viewBehaviorFactories = null;
24
- this.hostBehaviorFactories = null;
16
+ constructor(html, factories) {
17
+ this.result = null;
25
18
  this.html = html;
26
- this.directives = directives;
19
+ this.factories = factories;
27
20
  }
28
21
  /**
29
22
  * Creates an HTMLView instance based on this template definition.
30
23
  * @param hostBindingTarget - The element that host behaviors will be bound to.
31
24
  */
32
25
  create(hostBindingTarget) {
33
- if (this.fragment === null) {
34
- let template;
35
- const html = this.html;
36
- if (typeof html === "string") {
37
- template = document.createElement("template");
38
- template.innerHTML = DOM.createHTML(html);
39
- const fec = template.content.firstElementChild;
40
- if (fec !== null && fec.tagName === "TEMPLATE") {
41
- template = fec;
42
- }
43
- }
44
- else {
45
- template = html;
46
- }
47
- const result = compileTemplate(template, this.directives);
48
- this.fragment = result.fragment;
49
- this.viewBehaviorFactories = result.viewBehaviorFactories;
50
- this.hostBehaviorFactories = result.hostBehaviorFactories;
51
- this.targetOffset = result.targetOffset;
52
- this.behaviorCount =
53
- this.viewBehaviorFactories.length + this.hostBehaviorFactories.length;
54
- this.hasHostBehaviors = this.hostBehaviorFactories.length > 0;
55
- }
56
- const fragment = this.fragment.cloneNode(true);
57
- const viewFactories = this.viewBehaviorFactories;
58
- const behaviors = new Array(this.behaviorCount);
59
- const walker = DOM.createTemplateWalker(fragment);
60
- let behaviorIndex = 0;
61
- let targetIndex = this.targetOffset;
62
- let node = walker.nextNode();
63
- for (let ii = viewFactories.length; behaviorIndex < ii; ++behaviorIndex) {
64
- const factory = viewFactories[behaviorIndex];
65
- const factoryIndex = factory.targetIndex;
66
- while (node !== null) {
67
- if (targetIndex === factoryIndex) {
68
- behaviors[behaviorIndex] = factory.createBehavior(node);
69
- break;
70
- }
71
- else {
72
- node = walker.nextNode();
73
- targetIndex++;
74
- }
75
- }
26
+ if (this.result === null) {
27
+ this.result = Compiler.compile(this.html, this.factories);
76
28
  }
77
- if (this.hasHostBehaviors) {
78
- const hostFactories = this.hostBehaviorFactories;
79
- for (let i = 0, ii = hostFactories.length; i < ii; ++i, ++behaviorIndex) {
80
- behaviors[behaviorIndex] = hostFactories[i].createBehavior(hostBindingTarget);
81
- }
82
- }
83
- return new HTMLView(fragment, behaviors);
29
+ return this.result.createView(hostBindingTarget);
84
30
  }
85
31
  /**
86
32
  * Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
@@ -90,14 +36,8 @@ export class ViewTemplate {
90
36
  * host that the template is being attached to.
91
37
  */
92
38
  render(source, host, hostBindingTarget) {
93
- if (typeof host === "string") {
94
- host = document.getElementById(host);
95
- }
96
- if (hostBindingTarget === void 0) {
97
- hostBindingTarget = host;
98
- }
99
39
  const view = this.create(hostBindingTarget);
100
- view.bind(source, defaultExecutionContext);
40
+ view.bind(source);
101
41
  view.appendTo(host);
102
42
  return view;
103
43
  }
@@ -106,8 +46,15 @@ export class ViewTemplate {
106
46
  const lastAttributeNameRegex =
107
47
  /* eslint-disable-next-line no-control-regex */
108
48
  /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
49
+ function createAspectedHTML(value, prevString, add) {
50
+ const match = lastAttributeNameRegex.exec(prevString);
51
+ if (match !== null) {
52
+ Aspect.assign(value, match[2]);
53
+ }
54
+ return value.createHTML(add);
55
+ }
109
56
  /**
110
- * Transforms a template literal string into a renderable ViewTemplate.
57
+ * Transforms a template literal string into a ViewTemplate.
111
58
  * @param strings - The string fragments that are interpolated with the values.
112
59
  * @param values - The values that are interpolated with the string fragments.
113
60
  * @remarks
@@ -116,36 +63,47 @@ const lastAttributeNameRegex =
116
63
  * @public
117
64
  */
118
65
  export function html(strings, ...values) {
119
- const directives = [];
120
66
  let html = "";
67
+ const factories = Object.create(null);
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
+ };
121
74
  for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
122
75
  const currentString = strings[i];
123
- let value = values[i];
76
+ const currentValue = values[i];
77
+ let definition;
124
78
  html += currentString;
125
- if (value instanceof ViewTemplate) {
126
- const template = value;
127
- value = () => template;
128
- }
129
- if (typeof value === "function") {
130
- value = new HTMLBindingDirective(value);
79
+ if (isFunction(currentValue)) {
80
+ html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
131
81
  }
132
- if (value instanceof TargetedHTMLDirective) {
82
+ else if (isString(currentValue)) {
133
83
  const match = lastAttributeNameRegex.exec(currentString);
134
84
  if (match !== null) {
135
- value.targetName = match[2];
85
+ const directive = new HTMLBindingDirective(oneTime(() => currentValue));
86
+ Aspect.assign(directive, match[2]);
87
+ html += directive.createHTML(add);
88
+ }
89
+ else {
90
+ html += currentValue;
136
91
  }
137
92
  }
138
- if (value instanceof HTMLDirective) {
139
- // Since not all values are directives, we can't use i
140
- // as the index for the placeholder. Instead, we need to
141
- // use directives.length to get the next index.
142
- html += value.createPlaceholder(directives.length);
143
- directives.push(value);
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);
144
98
  }
145
99
  else {
146
- html += value;
100
+ if (definition.aspected) {
101
+ html += createAspectedHTML(currentValue, currentString, add);
102
+ }
103
+ else {
104
+ html += currentValue.createHTML(add);
105
+ }
147
106
  }
148
107
  }
149
- html += strings[strings.length - 1];
150
- return new ViewTemplate(html, directives);
108
+ return new ViewTemplate(html + strings[strings.length - 1], factories);
151
109
  }
@@ -1,6 +1,15 @@
1
- // A singleton Range instance used to efficiently remove ranges of DOM nodes.
2
- // See the implementation of HTMLView below for further details.
3
- const range = document.createRange();
1
+ import { ExecutionContext, Observable, SourceLifetime, } from "../observation/observable.js";
2
+ function removeNodeSequence(firstNode, lastNode) {
3
+ const parent = firstNode.parentNode;
4
+ let current = firstNode;
5
+ let next;
6
+ while (current !== lastNode) {
7
+ next = current.nextSibling;
8
+ parent.removeChild(current);
9
+ current = next;
10
+ }
11
+ parent.removeChild(lastNode);
12
+ }
4
13
  /**
5
14
  * The standard View implementation, which also implements ElementView and SyntheticView.
6
15
  * @public
@@ -11,20 +20,92 @@ export class HTMLView {
11
20
  * @param fragment - The html fragment that contains the nodes for this view.
12
21
  * @param behaviors - The behaviors to be applied to this view.
13
22
  */
14
- constructor(fragment, behaviors) {
23
+ constructor(fragment, factories, targets) {
15
24
  this.fragment = fragment;
16
- this.behaviors = behaviors;
25
+ this.factories = factories;
26
+ this.targets = targets;
27
+ this.behaviors = null;
28
+ this.unbindables = [];
17
29
  /**
18
30
  * The data that the view is bound to.
19
31
  */
20
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;
21
41
  /**
22
42
  * The execution context the view is running within.
23
43
  */
24
- this.context = null;
44
+ this.context = this;
45
+ /**
46
+ * The index of the current item within a repeat context.
47
+ */
48
+ this.index = 0;
49
+ /**
50
+ * The length of the current collection within a repeat context.
51
+ */
52
+ this.length = 0;
25
53
  this.firstChild = fragment.firstChild;
26
54
  this.lastChild = fragment.lastChild;
27
55
  }
56
+ /**
57
+ * The current event within an event handler.
58
+ */
59
+ get event() {
60
+ return ExecutionContext.getEvent();
61
+ }
62
+ /**
63
+ * Indicates whether the current item within a repeat context
64
+ * has an even index.
65
+ */
66
+ get isEven() {
67
+ return this.index % 2 === 0;
68
+ }
69
+ /**
70
+ * Indicates whether the current item within a repeat context
71
+ * has an odd index.
72
+ */
73
+ get isOdd() {
74
+ return this.index % 2 !== 0;
75
+ }
76
+ /**
77
+ * Indicates whether the current item within a repeat context
78
+ * is the first item in the collection.
79
+ */
80
+ get isFirst() {
81
+ return this.index === 0;
82
+ }
83
+ /**
84
+ * Indicates whether the current item within a repeat context
85
+ * is somewhere in the middle of the collection.
86
+ */
87
+ get isInMiddle() {
88
+ return !this.isFirst && !this.isLast;
89
+ }
90
+ /**
91
+ * Indicates whether the current item within a repeat context
92
+ * is the last item in the collection.
93
+ */
94
+ get isLast() {
95
+ return this.index === this.length - 1;
96
+ }
97
+ /**
98
+ * Returns the typed event detail of a custom event.
99
+ */
100
+ eventDetail() {
101
+ return this.event.detail;
102
+ }
103
+ /**
104
+ * Returns the typed event target of the event.
105
+ */
106
+ eventTarget() {
107
+ return this.event.target;
108
+ }
28
109
  /**
29
110
  * Appends the view's DOM nodes to the referenced node.
30
111
  * @param node - The parent node to append the view's DOM nodes to.
@@ -41,8 +122,10 @@ export class HTMLView {
41
122
  node.parentNode.insertBefore(this.fragment, node);
42
123
  }
43
124
  else {
44
- const parentNode = node.parentNode;
45
125
  const end = this.lastChild;
126
+ if (node.previousSibling === end)
127
+ return;
128
+ const parentNode = node.parentNode;
46
129
  let current = this.firstChild;
47
130
  let next;
48
131
  while (current !== end) {
@@ -74,63 +157,64 @@ export class HTMLView {
74
157
  * Once a view has been disposed, it cannot be inserted or bound again.
75
158
  */
76
159
  dispose() {
77
- const parent = this.firstChild.parentNode;
78
- const end = this.lastChild;
79
- let current = this.firstChild;
80
- let next;
81
- while (current !== end) {
82
- next = current.nextSibling;
83
- parent.removeChild(current);
84
- current = next;
85
- }
86
- parent.removeChild(end);
87
- const behaviors = this.behaviors;
88
- const oldSource = this.source;
89
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
90
- behaviors[i].unbind(oldSource);
91
- }
160
+ removeNodeSequence(this.firstChild, this.lastChild);
161
+ this.unbind();
162
+ }
163
+ onUnbind(behavior) {
164
+ this.unbindables.push(behavior);
92
165
  }
93
166
  /**
94
167
  * Binds a view's behaviors to its binding source.
95
168
  * @param source - The binding source for the view's binding behaviors.
96
169
  * @param context - The execution context to run the behaviors within.
97
170
  */
98
- bind(source, context) {
99
- const behaviors = this.behaviors;
171
+ bind(source, context = this) {
100
172
  if (this.source === source) {
101
173
  return;
102
174
  }
103
- else if (this.source !== null) {
104
- const oldSource = this.source;
175
+ let behaviors = this.behaviors;
176
+ if (behaviors === null) {
105
177
  this.source = source;
106
178
  this.context = context;
107
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
108
- const current = behaviors[i];
109
- current.unbind(oldSource);
110
- current.bind(source, context);
179
+ this.behaviors = behaviors = new Array(this.factories.length);
180
+ const factories = this.factories;
181
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
182
+ const behavior = factories[i].createBehavior();
183
+ behavior.bind(this);
184
+ behaviors[i] = behavior;
111
185
  }
112
186
  }
113
187
  else {
188
+ if (this.source !== null) {
189
+ this.evaluateUnbindables();
190
+ }
191
+ this.isBound = false;
114
192
  this.source = source;
115
193
  this.context = context;
116
194
  for (let i = 0, ii = behaviors.length; i < ii; ++i) {
117
- behaviors[i].bind(source, context);
195
+ behaviors[i].bind(this);
118
196
  }
119
197
  }
198
+ this.isBound = true;
120
199
  }
121
200
  /**
122
201
  * Unbinds a view's behaviors from its binding source.
123
202
  */
124
203
  unbind() {
125
- if (this.source === null) {
204
+ if (!this.isBound || this.source === null) {
126
205
  return;
127
206
  }
128
- const behaviors = this.behaviors;
129
- const oldSource = this.source;
130
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
131
- behaviors[i].unbind(oldSource);
132
- }
207
+ this.evaluateUnbindables();
133
208
  this.source = null;
209
+ this.context = this;
210
+ this.isBound = false;
211
+ }
212
+ evaluateUnbindables() {
213
+ const unbindables = this.unbindables;
214
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
215
+ unbindables[i].unbind(this);
216
+ }
217
+ unbindables.length = 0;
134
218
  }
135
219
  /**
136
220
  * Efficiently disposes of a contiguous range of synthetic view instances.
@@ -140,16 +224,11 @@ export class HTMLView {
140
224
  if (views.length === 0) {
141
225
  return;
142
226
  }
143
- range.setStartBefore(views[0].firstChild);
144
- range.setEndAfter(views[views.length - 1].lastChild);
145
- range.deleteContents();
227
+ removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
146
228
  for (let i = 0, ii = views.length; i < ii; ++i) {
147
- const view = views[i];
148
- const behaviors = view.behaviors;
149
- const oldSource = view.source;
150
- for (let j = 0, jj = behaviors.length; j < jj; ++j) {
151
- behaviors[j].unbind(oldSource);
152
- }
229
+ views[i].unbind();
153
230
  }
154
231
  }
155
232
  }
233
+ Observable.defineProperty(HTMLView.prototype, "index");
234
+ Observable.defineProperty(HTMLView.prototype, "length");
@@ -1,13 +1,15 @@
1
+ import { isFunction } from "../interfaces.js";
1
2
  /**
2
3
  * A directive that enables basic conditional rendering in a template.
3
- * @param binding - The condition to test for rendering.
4
+ * @param condition - The condition to test for rendering.
4
5
  * @param templateOrTemplateBinding - The template or a binding that gets
5
6
  * the template to render when the condition is true.
6
7
  * @public
7
8
  */
8
- export function when(binding, templateOrTemplateBinding) {
9
- const getTemplate = typeof templateOrTemplateBinding === "function"
9
+ export function when(condition, templateOrTemplateBinding) {
10
+ const dataBinding = isFunction(condition) ? condition : () => condition;
11
+ const templateBinding = isFunction(templateOrTemplateBinding)
10
12
  ? templateOrTemplateBinding
11
13
  : () => templateOrTemplateBinding;
12
- return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
14
+ return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
13
15
  }
@@ -0,0 +1,3 @@
1
+ export { timeout } from "./timeout.js";
2
+ export * from "./fixture.js";
3
+ export * from "./fakes.js";
@@ -0,0 +1,76 @@
1
+ import { ExecutionContext } from "../index.js";
2
+ export const Fake = Object.freeze({
3
+ executionContext(parent, parentContext) {
4
+ return {
5
+ /**
6
+ * The index of the current item within a repeat context.
7
+ */
8
+ index: 0,
9
+ /**
10
+ * The length of the current collection within a repeat context.
11
+ */
12
+ length: 0,
13
+ /**
14
+ * The parent data source within a nested context.
15
+ */
16
+ parent: parent,
17
+ /**
18
+ * The parent execution context when in nested context scenarios.
19
+ */
20
+ parentContext: parentContext,
21
+ /**
22
+ * The current event within an event handler.
23
+ */
24
+ get event() {
25
+ return ExecutionContext.getEvent();
26
+ },
27
+ /**
28
+ * Indicates whether the current item within a repeat context
29
+ * has an even index.
30
+ */
31
+ get isEven() {
32
+ return this.index % 2 === 0;
33
+ },
34
+ /**
35
+ * Indicates whether the current item within a repeat context
36
+ * has an odd index.
37
+ */
38
+ get isOdd() {
39
+ return this.index % 2 !== 0;
40
+ },
41
+ /**
42
+ * Indicates whether the current item within a repeat context
43
+ * is the first item in the collection.
44
+ */
45
+ get isFirst() {
46
+ return this.index === 0;
47
+ },
48
+ /**
49
+ * Indicates whether the current item within a repeat context
50
+ * is somewhere in the middle of the collection.
51
+ */
52
+ get isInMiddle() {
53
+ return !this.isFirst && !this.isLast;
54
+ },
55
+ /**
56
+ * Indicates whether the current item within a repeat context
57
+ * is the last item in the collection.
58
+ */
59
+ get isLast() {
60
+ return this.index === this.length - 1;
61
+ },
62
+ /**
63
+ * Returns the typed event detail of a custom event.
64
+ */
65
+ eventDetail() {
66
+ return this.event.detail;
67
+ },
68
+ /**
69
+ * Returns the typed event target of the event.
70
+ */
71
+ eventTarget() {
72
+ return this.event.target;
73
+ },
74
+ };
75
+ },
76
+ });