@microsoft/fast-element 1.10.0 → 2.0.0-beta.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 (97) hide show
  1. package/.eslintrc.json +1 -12
  2. package/CHANGELOG.json +396 -1
  3. package/CHANGELOG.md +68 -2
  4. package/README.md +2 -2
  5. package/dist/dts/components/attributes.d.ts +4 -1
  6. package/dist/dts/components/controller.d.ts +12 -11
  7. package/dist/dts/components/fast-definitions.d.ts +8 -2
  8. package/dist/dts/components/fast-element.d.ts +5 -4
  9. package/dist/dts/debug.d.ts +1 -0
  10. package/dist/dts/hooks.d.ts +20 -0
  11. package/dist/dts/index.d.ts +16 -15
  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 +144 -0
  16. package/dist/dts/observation/arrays.d.ts +207 -0
  17. package/dist/dts/observation/behavior.d.ts +5 -5
  18. package/dist/dts/observation/notifier.d.ts +18 -18
  19. package/dist/dts/observation/observable.d.ts +86 -29
  20. package/dist/dts/observation/splice-strategies.d.ts +13 -0
  21. package/dist/dts/observation/update-queue.d.ts +40 -0
  22. package/dist/dts/platform.d.ts +18 -67
  23. package/dist/dts/polyfills.d.ts +8 -0
  24. package/dist/dts/styles/css-directive.d.ts +43 -5
  25. package/dist/dts/styles/css.d.ts +19 -3
  26. package/dist/dts/styles/element-styles.d.ts +42 -62
  27. package/dist/dts/templating/binding.d.ts +320 -64
  28. package/dist/dts/templating/children.d.ts +18 -15
  29. package/dist/dts/templating/compiler.d.ts +47 -28
  30. package/dist/dts/templating/dom.d.ts +41 -0
  31. package/dist/dts/templating/html-directive.d.ts +179 -43
  32. package/dist/dts/templating/markup.d.ts +48 -0
  33. package/dist/dts/templating/node-observation.d.ts +45 -29
  34. package/dist/dts/templating/ref.d.ts +6 -12
  35. package/dist/dts/templating/repeat.d.ts +72 -14
  36. package/dist/dts/templating/slotted.d.ts +13 -14
  37. package/dist/dts/templating/template.d.ts +78 -23
  38. package/dist/dts/templating/view.d.ts +16 -23
  39. package/dist/dts/utilities.d.ts +40 -0
  40. package/dist/esm/components/attributes.js +25 -24
  41. package/dist/esm/components/controller.js +77 -57
  42. package/dist/esm/components/fast-definitions.js +14 -22
  43. package/dist/esm/debug.js +29 -0
  44. package/dist/esm/hooks.js +32 -0
  45. package/dist/esm/index.debug.js +2 -0
  46. package/dist/esm/index.js +19 -14
  47. package/dist/esm/index.rollup.debug.js +3 -0
  48. package/dist/esm/index.rollup.js +2 -0
  49. package/dist/esm/interfaces.js +8 -1
  50. package/dist/esm/observation/arrays.js +269 -0
  51. package/dist/esm/observation/notifier.js +75 -83
  52. package/dist/esm/observation/observable.js +80 -107
  53. package/dist/esm/observation/{array-change-records.js → splice-strategies.js} +136 -62
  54. package/dist/esm/observation/update-queue.js +67 -0
  55. package/dist/esm/platform.js +36 -42
  56. package/dist/esm/polyfills.js +85 -0
  57. package/dist/esm/styles/css-directive.js +29 -13
  58. package/dist/esm/styles/css.js +27 -40
  59. package/dist/esm/styles/element-styles.js +65 -104
  60. package/dist/esm/templating/binding.js +465 -155
  61. package/dist/esm/templating/children.js +33 -23
  62. package/dist/esm/templating/compiler.js +235 -152
  63. package/dist/esm/templating/dom.js +49 -0
  64. package/dist/esm/templating/html-directive.js +125 -40
  65. package/dist/esm/templating/markup.js +75 -0
  66. package/dist/esm/templating/node-observation.js +50 -45
  67. package/dist/esm/templating/ref.js +7 -16
  68. package/dist/esm/templating/repeat.js +38 -43
  69. package/dist/esm/templating/slotted.js +23 -20
  70. package/dist/esm/templating/template.js +71 -95
  71. package/dist/esm/templating/view.js +44 -43
  72. package/dist/esm/templating/when.js +2 -1
  73. package/dist/esm/utilities.js +139 -0
  74. package/dist/fast-element.api.json +14062 -5235
  75. package/dist/fast-element.d.ts +1434 -579
  76. package/dist/fast-element.debug.js +3824 -0
  77. package/dist/fast-element.debug.min.js +1 -0
  78. package/dist/fast-element.js +3565 -4014
  79. package/dist/fast-element.min.js +1 -1
  80. package/dist/fast-element.untrimmed.d.ts +2908 -0
  81. package/dist/tsdoc-metadata.json +1 -1
  82. package/docs/api-report.md +590 -231
  83. package/docs/fast-element-2-changes.md +15 -0
  84. package/docs/guide/declaring-templates.md +5 -4
  85. package/docs/guide/defining-elements.md +3 -2
  86. package/docs/guide/leveraging-css.md +1 -0
  87. package/docs/guide/next-steps.md +3 -2
  88. package/docs/guide/observables-and-state.md +2 -1
  89. package/docs/guide/using-directives.md +2 -1
  90. package/docs/guide/working-with-shadow-dom.md +1 -0
  91. package/karma.conf.cjs +6 -17
  92. package/package.json +48 -14
  93. package/dist/dts/dom.d.ts +0 -112
  94. package/dist/dts/observation/array-change-records.d.ts +0 -48
  95. package/dist/dts/observation/array-observer.d.ts +0 -9
  96. package/dist/esm/dom.js +0 -207
  97. package/dist/esm/observation/array-observer.js +0 -173
@@ -1,45 +1,55 @@
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";
3
4
  /**
4
5
  * The runtime behavior for child node observation.
5
6
  * @public
6
7
  */
7
- export class ChildrenBehavior extends NodeObservationBehavior {
8
+ export class ChildrenDirective extends NodeObservationDirective {
8
9
  /**
9
- * Creates an instance of ChildrenBehavior.
10
- * @param target - The element target to observe children on.
11
- * @param options - The options to use when observing the element children.
10
+ * Creates an instance of ChildrenDirective.
11
+ * @param options - The options to use in configuring the child observation behavior.
12
12
  */
13
- constructor(target, options) {
14
- super(target, options);
15
- this.observer = null;
13
+ constructor(options) {
14
+ super(options);
15
+ this.observerProperty = `${this.id}-o`;
16
+ this.handleEvent = (mutations, observer) => {
17
+ const target = observer.target;
18
+ this.updateTarget(this.getSource(target), this.computeNodes(target));
19
+ };
16
20
  options.childList = true;
17
21
  }
18
22
  /**
19
23
  * Begins observation of the nodes.
24
+ * @param target - The target to observe.
20
25
  */
21
- observe() {
22
- if (this.observer === null) {
23
- this.observer = new MutationObserver(this.handleEvent.bind(this));
24
- }
25
- this.observer.observe(this.target, this.options);
26
+ observe(target) {
27
+ var _a;
28
+ const observer = (_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = new MutationObserver(this.handleEvent));
29
+ observer.target = target;
30
+ observer.observe(target, this.options);
26
31
  }
27
32
  /**
28
33
  * Disconnects observation of the nodes.
34
+ * @param target - The target to unobserve.
29
35
  */
30
- disconnect() {
31
- this.observer.disconnect();
36
+ disconnect(target) {
37
+ const observer = target[this.observerProperty];
38
+ observer.target = null;
39
+ observer.disconnect();
32
40
  }
33
41
  /**
34
- * Retrieves the nodes that should be assigned to the target.
42
+ * Retrieves the raw nodes that should be assigned to the source property.
43
+ * @param target - The target to get the node to.
35
44
  */
36
- getNodes() {
37
- if ("subtree" in this.options) {
38
- return Array.from(this.target.querySelectorAll(this.options.selector));
45
+ getNodes(target) {
46
+ if ("selector" in this.options) {
47
+ return Array.from(target.querySelectorAll(this.options.selector));
39
48
  }
40
- return Array.from(this.target.childNodes);
49
+ return Array.from(target.childNodes);
41
50
  }
42
51
  }
52
+ HTMLDirective.define(ChildrenDirective);
43
53
  /**
44
54
  * A directive that observes the `childNodes` of an element and updates a property
45
55
  * whenever they change.
@@ -47,10 +57,10 @@ export class ChildrenBehavior extends NodeObservationBehavior {
47
57
  * @public
48
58
  */
49
59
  export function children(propertyOrOptions) {
50
- if (typeof propertyOrOptions === "string") {
60
+ if (isString(propertyOrOptions)) {
51
61
  propertyOrOptions = {
52
62
  property: propertyOrOptions,
53
63
  };
54
64
  }
55
- return new AttachedBehaviorHTMLDirective("fast-children", ChildrenBehavior, propertyOrOptions);
65
+ return new ChildrenDirective(propertyOrOptions);
56
66
  }
@@ -1,186 +1,269 @@
1
- import { _interpolationEnd, _interpolationStart, DOM } from "../dom.js";
2
- import { HTMLBindingDirective } from "./binding.js";
3
- let sharedContext = null;
1
+ import { isString } from "../interfaces.js";
2
+ import { FAST } from "../platform.js";
3
+ import { Parser } from "./markup.js";
4
+ import { bind, oneTime } from "./binding.js";
5
+ import { Aspect } from "./html-directive.js";
6
+ import { HTMLView } from "./view.js";
7
+ const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
8
+ const descriptorCache = {};
9
+ // used to prevent creating lots of objects just to track node and index while compiling
10
+ const next = {
11
+ index: 0,
12
+ node: null,
13
+ };
4
14
  class CompilationContext {
5
- addFactory(factory) {
6
- factory.targetIndex = this.targetIndex;
7
- this.behaviorFactories.push(factory);
15
+ constructor(fragment, directives) {
16
+ this.fragment = fragment;
17
+ this.directives = directives;
18
+ this.proto = null;
19
+ this.nodeIds = new Set();
20
+ this.descriptors = {};
21
+ this.factories = [];
8
22
  }
9
- captureContentBinding(directive) {
10
- directive.targetAtContent();
11
- this.addFactory(directive);
12
- }
13
- reset() {
14
- this.behaviorFactories = [];
15
- this.targetIndex = -1;
16
- }
17
- release() {
18
- /* eslint-disable-next-line @typescript-eslint/no-this-alias */
19
- sharedContext = this;
20
- }
21
- static borrow(directives) {
22
- const shareable = sharedContext || new CompilationContext();
23
- shareable.directives = directives;
24
- shareable.reset();
25
- sharedContext = null;
26
- return shareable;
27
- }
28
- }
29
- function createAggregateBinding(parts) {
30
- if (parts.length === 1) {
31
- return parts[0];
23
+ addFactory(factory, parentId, nodeId, targetIndex) {
24
+ if (!this.nodeIds.has(nodeId)) {
25
+ this.nodeIds.add(nodeId);
26
+ this.addTargetDescriptor(parentId, nodeId, targetIndex);
27
+ }
28
+ factory.nodeId = nodeId;
29
+ this.factories.push(factory);
32
30
  }
33
- let targetName;
34
- const partCount = parts.length;
35
- const finalParts = parts.map((x) => {
36
- if (typeof x === "string") {
37
- return () => x;
38
- }
39
- targetName = x.targetName || targetName;
40
- return x.binding;
41
- });
42
- const binding = (scope, context) => {
43
- let output = "";
44
- for (let i = 0; i < partCount; ++i) {
45
- output += finalParts[i](scope, context);
46
- }
47
- return output;
48
- };
49
- const directive = new HTMLBindingDirective(binding);
50
- directive.targetName = targetName;
51
- return directive;
52
- }
53
- const interpolationEndLength = _interpolationEnd.length;
54
- function parseContent(context, value) {
55
- const valueParts = value.split(_interpolationStart);
56
- if (valueParts.length === 1) {
57
- return null;
31
+ freeze() {
32
+ this.proto = Object.create(null, this.descriptors);
33
+ return this;
58
34
  }
59
- const bindingParts = [];
60
- for (let i = 0, ii = valueParts.length; i < ii; ++i) {
61
- const current = valueParts[i];
62
- const index = current.indexOf(_interpolationEnd);
63
- let literal;
64
- if (index === -1) {
65
- literal = current;
35
+ addTargetDescriptor(parentId, targetId, targetIndex) {
36
+ const descriptors = this.descriptors;
37
+ if (targetId === "r" || // root
38
+ targetId === "h" || // host
39
+ descriptors[targetId]) {
40
+ return;
66
41
  }
67
- else {
68
- const directiveIndex = parseInt(current.substring(0, index));
69
- bindingParts.push(context.directives[directiveIndex]);
70
- literal = current.substring(index + interpolationEndLength);
42
+ if (!descriptors[parentId]) {
43
+ const index = parentId.lastIndexOf(".");
44
+ const grandparentId = parentId.substring(0, index);
45
+ const childIndex = parseInt(parentId.substring(index + 1));
46
+ this.addTargetDescriptor(grandparentId, parentId, childIndex);
71
47
  }
72
- if (literal !== "") {
73
- bindingParts.push(literal);
48
+ let descriptor = descriptorCache[targetId];
49
+ if (!descriptor) {
50
+ const field = `_${targetId}`;
51
+ descriptorCache[targetId] = descriptor = {
52
+ get() {
53
+ var _a;
54
+ return ((_a = this[field]) !== null && _a !== void 0 ? _a : (this[field] = this[parentId].childNodes[targetIndex]));
55
+ },
56
+ };
74
57
  }
58
+ descriptors[targetId] = descriptor;
59
+ }
60
+ createView(hostBindingTarget) {
61
+ const fragment = this.fragment.cloneNode(true);
62
+ const targets = Object.create(this.proto);
63
+ targets.r = fragment;
64
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : fragment;
65
+ for (const id of this.nodeIds) {
66
+ targets[id]; // trigger locator
67
+ }
68
+ return new HTMLView(fragment, this.factories, targets);
75
69
  }
76
- return bindingParts;
77
70
  }
78
- function compileAttributes(context, node, includeBasicValues = false) {
71
+ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBasicValues = false) {
79
72
  const attributes = node.attributes;
73
+ const directives = context.directives;
80
74
  for (let i = 0, ii = attributes.length; i < ii; ++i) {
81
75
  const attr = attributes[i];
82
76
  const attrValue = attr.value;
83
- const parseResult = parseContent(context, attrValue);
77
+ const parseResult = Parser.parse(attrValue, directives);
84
78
  let result = null;
85
79
  if (parseResult === null) {
86
80
  if (includeBasicValues) {
87
- result = new HTMLBindingDirective(() => attrValue);
88
- result.targetName = attr.name;
81
+ result = bind(() => attrValue, oneTime);
82
+ Aspect.assign(result, attr.name);
89
83
  }
90
84
  }
91
85
  else {
92
- result = createAggregateBinding(parseResult);
86
+ /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
87
+ result = Compiler.aggregate(parseResult);
93
88
  }
94
89
  if (result !== null) {
95
90
  node.removeAttributeNode(attr);
96
91
  i--;
97
92
  ii--;
98
- context.addFactory(result);
93
+ context.addFactory(result, parentId, nodeId, nodeIndex);
99
94
  }
100
95
  }
101
96
  }
102
- function compileContent(context, node, walker) {
103
- const parseResult = parseContent(context, node.textContent);
104
- if (parseResult !== null) {
105
- let lastNode = node;
106
- for (let i = 0, ii = parseResult.length; i < ii; ++i) {
107
- const currentPart = parseResult[i];
108
- const currentNode = i === 0
109
- ? node
110
- : lastNode.parentNode.insertBefore(document.createTextNode(""), lastNode.nextSibling);
111
- if (typeof currentPart === "string") {
112
- currentNode.textContent = currentPart;
113
- }
114
- else {
115
- currentNode.textContent = " ";
116
- context.captureContentBinding(currentPart);
117
- }
118
- lastNode = currentNode;
119
- context.targetIndex++;
120
- if (currentNode !== node) {
121
- walker.nextNode();
122
- }
97
+ function compileContent(context, node, parentId, nodeId, nodeIndex) {
98
+ const parseResult = Parser.parse(node.textContent, context.directives);
99
+ if (parseResult === null) {
100
+ next.node = node.nextSibling;
101
+ next.index = nodeIndex + 1;
102
+ return next;
103
+ }
104
+ let currentNode;
105
+ let lastNode = (currentNode = node);
106
+ for (let i = 0, ii = parseResult.length; i < ii; ++i) {
107
+ const currentPart = parseResult[i];
108
+ if (i !== 0) {
109
+ nodeIndex++;
110
+ nodeId = targetIdFrom(parentId, nodeIndex);
111
+ currentNode = lastNode.parentNode.insertBefore(document.createTextNode(""), lastNode.nextSibling);
112
+ }
113
+ if (isString(currentPart)) {
114
+ currentNode.textContent = currentPart;
115
+ }
116
+ else {
117
+ currentNode.textContent = " ";
118
+ context.addFactory(currentPart, parentId, nodeId, nodeIndex);
123
119
  }
124
- context.targetIndex--;
120
+ lastNode = currentNode;
125
121
  }
122
+ next.index = nodeIndex + 1;
123
+ next.node = lastNode.nextSibling;
124
+ return next;
126
125
  }
126
+ function compileChildren(context, parent, parentId) {
127
+ let nodeIndex = 0;
128
+ let childNode = parent.firstChild;
129
+ while (childNode) {
130
+ /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
131
+ const result = compileNode(context, parentId, childNode, nodeIndex);
132
+ childNode = result.node;
133
+ nodeIndex = result.index;
134
+ }
135
+ }
136
+ function compileNode(context, parentId, node, nodeIndex) {
137
+ const nodeId = targetIdFrom(parentId, nodeIndex);
138
+ switch (node.nodeType) {
139
+ case 1: // element node
140
+ compileAttributes(context, parentId, node, nodeId, nodeIndex);
141
+ compileChildren(context, node, nodeId);
142
+ break;
143
+ case 3: // text node
144
+ return compileContent(context, node, parentId, nodeId, nodeIndex);
145
+ case 8: // comment
146
+ const parts = Parser.parse(node.data, context.directives);
147
+ if (parts !== null) {
148
+ context.addFactory(
149
+ /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
150
+ Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
151
+ }
152
+ break;
153
+ }
154
+ next.index = nodeIndex + 1;
155
+ next.node = node.nextSibling;
156
+ return next;
157
+ }
158
+ function isMarker(node, directives) {
159
+ return (node &&
160
+ node.nodeType == 8 &&
161
+ Parser.parse(node.data, directives) !== null);
162
+ }
163
+ const templateTag = "TEMPLATE";
164
+ const policyOptions = { createHTML: html => html };
165
+ let htmlPolicy = globalThis.trustedTypes
166
+ ? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
167
+ : policyOptions;
168
+ const fastHTMLPolicy = htmlPolicy;
127
169
  /**
128
- * Compiles a template and associated directives into a raw compilation
129
- * result which include a cloneable DocumentFragment and factories capable
130
- * of attaching runtime behavior to nodes within the fragment.
131
- * @param template - The template to compile.
132
- * @param directives - The directives referenced by the template.
133
- * @remarks
134
- * The template that is provided for compilation is altered in-place
135
- * and cannot be compiled again. If the original template must be preserved,
136
- * it is recommended that you clone the original and pass the clone to this API.
170
+ * Common APIs related to compilation.
137
171
  * @public
138
172
  */
139
- export function compileTemplate(template, directives) {
140
- const fragment = template.content;
141
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
142
- document.adoptNode(fragment);
143
- const context = CompilationContext.borrow(directives);
144
- compileAttributes(context, template, true);
145
- const hostBehaviorFactories = context.behaviorFactories;
146
- context.reset();
147
- const walker = DOM.createTemplateWalker(fragment);
148
- let node;
149
- while ((node = walker.nextNode())) {
150
- context.targetIndex++;
151
- switch (node.nodeType) {
152
- case 1: // element node
153
- compileAttributes(context, node);
154
- break;
155
- case 3: // text node
156
- compileContent(context, node, walker);
157
- break;
158
- case 8: // comment
159
- if (DOM.isMarker(node)) {
160
- context.addFactory(directives[DOM.extractDirectiveIndexFromMarker(node)]);
161
- }
173
+ export const Compiler = {
174
+ /**
175
+ * Sets the HTML trusted types policy used by the compiler.
176
+ * @param policy - The policy to set for HTML.
177
+ * @remarks
178
+ * This API can only be called once, for security reasons. It should be
179
+ * called by the application developer at the start of their program.
180
+ */
181
+ setHTMLPolicy(policy) {
182
+ if (htmlPolicy !== fastHTMLPolicy) {
183
+ throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
162
184
  }
163
- }
164
- let targetOffset = 0;
165
- if (
166
- // If the first node in a fragment is a marker, that means it's an unstable first node,
167
- // because something like a when, repeat, etc. could add nodes before the marker.
168
- // To mitigate this, we insert a stable first node. However, if we insert a node,
169
- // that will alter the result of the TreeWalker. So, we also need to offset the target index.
170
- DOM.isMarker(fragment.firstChild) ||
171
- // Or if there is only one node and a directive, it means the template's content
172
- // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
173
- // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
174
- (fragment.childNodes.length === 1 && directives.length)) {
175
- fragment.insertBefore(document.createComment(""), fragment.firstChild);
176
- targetOffset = -1;
177
- }
178
- const viewBehaviorFactories = context.behaviorFactories;
179
- context.release();
180
- return {
181
- fragment,
182
- viewBehaviorFactories,
183
- hostBehaviorFactories,
184
- targetOffset,
185
- };
186
- }
185
+ htmlPolicy = policy;
186
+ },
187
+ /**
188
+ * Compiles a template and associated directives into a compilation
189
+ * result which can be used to create views.
190
+ * @param html - The html string or template element to compile.
191
+ * @param directives - The directives referenced by the template.
192
+ * @remarks
193
+ * The template that is provided for compilation is altered in-place
194
+ * and cannot be compiled again. If the original template must be preserved,
195
+ * it is recommended that you clone the original and pass the clone to this API.
196
+ * @public
197
+ */
198
+ compile(html, directives) {
199
+ let template;
200
+ if (isString(html)) {
201
+ template = document.createElement(templateTag);
202
+ template.innerHTML = htmlPolicy.createHTML(html);
203
+ const fec = template.content.firstElementChild;
204
+ if (fec !== null && fec.tagName === templateTag) {
205
+ template = fec;
206
+ }
207
+ }
208
+ else {
209
+ template = html;
210
+ }
211
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
212
+ const fragment = document.adoptNode(template.content);
213
+ const context = new CompilationContext(fragment, directives);
214
+ compileAttributes(context, "", template, /* host */ "h", 0, true);
215
+ if (
216
+ // If the first node in a fragment is a marker, that means it's an unstable first node,
217
+ // because something like a when, repeat, etc. could add nodes before the marker.
218
+ // To mitigate this, we insert a stable first node. However, if we insert a node,
219
+ // that will alter the result of the TreeWalker. So, we also need to offset the target index.
220
+ isMarker(fragment.firstChild, directives) ||
221
+ // Or if there is only one node and a directive, it means the template's content
222
+ // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
223
+ // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
224
+ (fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
225
+ fragment.insertBefore(document.createComment(""), fragment.firstChild);
226
+ }
227
+ compileChildren(context, fragment, /* root */ "r");
228
+ next.node = null; // prevent leaks
229
+ return context.freeze();
230
+ },
231
+ /**
232
+ * Sets the default compilation strategy that will be used by the ViewTemplate whenever
233
+ * it needs to compile a view preprocessed with the html template function.
234
+ * @param strategy - The compilation strategy to use when compiling templates.
235
+ */
236
+ setDefaultStrategy(strategy) {
237
+ this.compile = strategy;
238
+ },
239
+ /**
240
+ * Aggregates an array of strings and directives into a single directive.
241
+ * @param parts - A heterogeneous array of static strings interspersed with
242
+ * directives.
243
+ * @returns A single inline directive that aggregates the behavior of all the parts.
244
+ */
245
+ aggregate(parts) {
246
+ if (parts.length === 1) {
247
+ return parts[0];
248
+ }
249
+ let sourceAspect;
250
+ const partCount = parts.length;
251
+ const finalParts = parts.map((x) => {
252
+ if (isString(x)) {
253
+ return () => x;
254
+ }
255
+ sourceAspect = x.sourceAspect || sourceAspect;
256
+ return x.binding;
257
+ });
258
+ const binding = (scope, context) => {
259
+ let output = "";
260
+ for (let i = 0; i < partCount; ++i) {
261
+ output += finalParts[i](scope, context);
262
+ }
263
+ return output;
264
+ };
265
+ const directive = bind(binding);
266
+ Aspect.assign(directive, sourceAspect);
267
+ return directive;
268
+ },
269
+ };
@@ -0,0 +1,49 @@
1
+ import { Updates } from "../observation/update-queue.js";
2
+ /**
3
+ * Common DOM APIs.
4
+ * @public
5
+ */
6
+ export const DOM = Object.freeze({
7
+ /**
8
+ * @deprecated
9
+ * Use Updates.enqueue().
10
+ */
11
+ queueUpdate: Updates.enqueue,
12
+ /**
13
+ * @deprecated
14
+ * Use Updates.next()
15
+ */
16
+ nextUpdate: Updates.next,
17
+ /**
18
+ * @deprecated
19
+ * Use Updates.process()
20
+ */
21
+ processUpdates: Updates.process,
22
+ /**
23
+ * Sets an attribute value on an element.
24
+ * @param element - The element to set the attribute value on.
25
+ * @param attributeName - The attribute name to set.
26
+ * @param value - The value of the attribute to set.
27
+ * @remarks
28
+ * If the value is `null` or `undefined`, the attribute is removed, otherwise
29
+ * it is set to the provided value using the standard `setAttribute` API.
30
+ */
31
+ setAttribute(element, attributeName, value) {
32
+ value === null || value === undefined
33
+ ? element.removeAttribute(attributeName)
34
+ : element.setAttribute(attributeName, value);
35
+ },
36
+ /**
37
+ * Sets a boolean attribute value.
38
+ * @param element - The element to set the boolean attribute value on.
39
+ * @param attributeName - The attribute name to set.
40
+ * @param value - The value of the attribute to set.
41
+ * @remarks
42
+ * If the value is true, the attribute is added; otherwise it is removed.
43
+ */
44
+ setBooleanAttribute(element, attributeName, value) {
45
+ value
46
+ ? element.setAttribute(attributeName, "")
47
+ : element.removeAttribute(attributeName);
48
+ },
49
+ });