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

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 (104) hide show
  1. package/.eslintrc.json +1 -12
  2. package/CHANGELOG.json +387 -1
  3. package/CHANGELOG.md +74 -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 +10 -2
  8. package/dist/dts/components/fast-element.d.ts +12 -5
  9. package/dist/dts/context.d.ts +157 -0
  10. package/dist/dts/debug.d.ts +1 -0
  11. package/dist/dts/hooks.d.ts +20 -0
  12. package/dist/dts/index.d.ts +16 -15
  13. package/dist/dts/index.debug.d.ts +2 -0
  14. package/dist/dts/index.rollup.d.ts +2 -0
  15. package/dist/dts/index.rollup.debug.d.ts +3 -0
  16. package/dist/dts/interfaces.d.ts +145 -0
  17. package/dist/dts/metadata.d.ts +25 -0
  18. package/dist/dts/observation/arrays.d.ts +207 -0
  19. package/dist/dts/observation/behavior.d.ts +4 -4
  20. package/dist/dts/observation/notifier.d.ts +18 -18
  21. package/dist/dts/observation/observable.d.ts +56 -18
  22. package/dist/dts/observation/splice-strategies.d.ts +13 -0
  23. package/dist/dts/observation/update-queue.d.ts +40 -0
  24. package/dist/dts/platform.d.ts +18 -67
  25. package/dist/dts/polyfills.d.ts +8 -0
  26. package/dist/dts/styles/css-directive.d.ts +43 -5
  27. package/dist/dts/styles/css.d.ts +19 -3
  28. package/dist/dts/styles/element-styles.d.ts +42 -62
  29. package/dist/dts/templating/binding-signal.d.ts +38 -0
  30. package/dist/dts/templating/binding-two-way.d.ts +56 -0
  31. package/dist/dts/templating/binding.d.ts +233 -65
  32. package/dist/dts/templating/children.d.ts +18 -15
  33. package/dist/dts/templating/compiler.d.ts +46 -28
  34. package/dist/dts/templating/dom.d.ts +41 -0
  35. package/dist/dts/templating/html-directive.d.ts +181 -43
  36. package/dist/dts/templating/markup.d.ts +48 -0
  37. package/dist/dts/templating/node-observation.d.ts +45 -29
  38. package/dist/dts/templating/ref.d.ts +6 -12
  39. package/dist/dts/templating/repeat.d.ts +26 -14
  40. package/dist/dts/templating/slotted.d.ts +13 -14
  41. package/dist/dts/templating/template.d.ts +27 -21
  42. package/dist/dts/templating/view.d.ts +15 -22
  43. package/dist/{tsdoc-metadata.json → dts/tsdoc-metadata.json} +1 -1
  44. package/dist/dts/utilities.d.ts +40 -0
  45. package/dist/esm/components/attributes.js +25 -24
  46. package/dist/esm/components/controller.js +77 -57
  47. package/dist/esm/components/fast-definitions.js +16 -22
  48. package/dist/esm/components/fast-element.js +10 -2
  49. package/dist/esm/context.js +159 -0
  50. package/dist/esm/debug.js +30 -0
  51. package/dist/esm/hooks.js +32 -0
  52. package/dist/esm/index.debug.js +2 -0
  53. package/dist/esm/index.js +19 -14
  54. package/dist/esm/index.rollup.debug.js +3 -0
  55. package/dist/esm/index.rollup.js +2 -0
  56. package/dist/esm/interfaces.js +8 -1
  57. package/dist/esm/metadata.js +60 -0
  58. package/dist/esm/observation/arrays.js +269 -0
  59. package/dist/esm/observation/notifier.js +27 -35
  60. package/dist/esm/observation/observable.js +93 -68
  61. package/dist/esm/observation/{array-change-records.js → splice-strategies.js} +136 -62
  62. package/dist/esm/observation/update-queue.js +67 -0
  63. package/dist/esm/platform.js +36 -42
  64. package/dist/esm/polyfills.js +85 -0
  65. package/dist/esm/styles/css-directive.js +29 -13
  66. package/dist/esm/styles/css.js +27 -40
  67. package/dist/esm/styles/element-styles.js +65 -104
  68. package/dist/esm/templating/binding-signal.js +84 -0
  69. package/dist/esm/templating/binding-two-way.js +82 -0
  70. package/dist/esm/templating/binding.js +306 -153
  71. package/dist/esm/templating/children.js +33 -23
  72. package/dist/esm/templating/compiler.js +236 -152
  73. package/dist/esm/templating/dom.js +49 -0
  74. package/dist/esm/templating/html-directive.js +128 -40
  75. package/dist/esm/templating/markup.js +75 -0
  76. package/dist/esm/templating/node-observation.js +50 -45
  77. package/dist/esm/templating/ref.js +7 -16
  78. package/dist/esm/templating/repeat.js +39 -36
  79. package/dist/esm/templating/slotted.js +23 -20
  80. package/dist/esm/templating/template.js +51 -95
  81. package/dist/esm/templating/view.js +44 -43
  82. package/dist/esm/templating/when.js +2 -1
  83. package/dist/esm/utilities.js +139 -0
  84. package/dist/fast-element.api.json +11789 -5377
  85. package/dist/fast-element.d.ts +1178 -530
  86. package/dist/fast-element.debug.js +3722 -0
  87. package/dist/fast-element.debug.min.js +1 -0
  88. package/dist/fast-element.js +3484 -4033
  89. package/dist/fast-element.min.js +1 -1
  90. package/dist/fast-element.untrimmed.d.ts +2699 -0
  91. package/docs/api-report.md +472 -219
  92. package/docs/fast-element-2-changes.md +15 -0
  93. package/docs/guide/declaring-templates.md +4 -4
  94. package/docs/guide/defining-elements.md +2 -2
  95. package/docs/guide/next-steps.md +2 -2
  96. package/docs/guide/observables-and-state.md +1 -1
  97. package/docs/guide/using-directives.md +1 -1
  98. package/karma.conf.cjs +6 -17
  99. package/package.json +63 -15
  100. package/dist/dts/dom.d.ts +0 -112
  101. package/dist/dts/observation/array-change-records.d.ts +0 -48
  102. package/dist/dts/observation/array-observer.d.ts +0 -9
  103. package/dist/esm/dom.js +0 -207
  104. package/dist/esm/observation/array-observer.js +0 -177
@@ -1,186 +1,270 @@
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
+ Aspect.assign(currentPart);
119
+ context.addFactory(currentPart, parentId, nodeId, nodeIndex);
123
120
  }
124
- context.targetIndex--;
121
+ lastNode = currentNode;
125
122
  }
123
+ next.index = nodeIndex + 1;
124
+ next.node = lastNode.nextSibling;
125
+ return next;
126
126
  }
127
+ function compileChildren(context, parent, parentId) {
128
+ let nodeIndex = 0;
129
+ let childNode = parent.firstChild;
130
+ while (childNode) {
131
+ /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
132
+ const result = compileNode(context, parentId, childNode, nodeIndex);
133
+ childNode = result.node;
134
+ nodeIndex = result.index;
135
+ }
136
+ }
137
+ function compileNode(context, parentId, node, nodeIndex) {
138
+ const nodeId = targetIdFrom(parentId, nodeIndex);
139
+ switch (node.nodeType) {
140
+ case 1: // element node
141
+ compileAttributes(context, parentId, node, nodeId, nodeIndex);
142
+ compileChildren(context, node, nodeId);
143
+ break;
144
+ case 3: // text node
145
+ return compileContent(context, node, parentId, nodeId, nodeIndex);
146
+ case 8: // comment
147
+ const parts = Parser.parse(node.data, context.directives);
148
+ if (parts !== null) {
149
+ context.addFactory(
150
+ /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
151
+ Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
152
+ }
153
+ break;
154
+ }
155
+ next.index = nodeIndex + 1;
156
+ next.node = node.nextSibling;
157
+ return next;
158
+ }
159
+ function isMarker(node, directives) {
160
+ return (node &&
161
+ node.nodeType == 8 &&
162
+ Parser.parse(node.data, directives) !== null);
163
+ }
164
+ const templateTag = "TEMPLATE";
165
+ const policyOptions = { createHTML: html => html };
166
+ let htmlPolicy = globalThis.trustedTypes
167
+ ? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
168
+ : policyOptions;
169
+ const fastHTMLPolicy = htmlPolicy;
127
170
  /**
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.
171
+ * Common APIs related to compilation.
137
172
  * @public
138
173
  */
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
- }
174
+ export const Compiler = {
175
+ /**
176
+ * Sets the HTML trusted types policy used by the compiler.
177
+ * @param policy - The policy to set for HTML.
178
+ * @remarks
179
+ * This API can only be called once, for security reasons. It should be
180
+ * called by the application developer at the start of their program.
181
+ */
182
+ setHTMLPolicy(policy) {
183
+ if (htmlPolicy !== fastHTMLPolicy) {
184
+ throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
162
185
  }
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
- }
186
+ htmlPolicy = policy;
187
+ },
188
+ /**
189
+ * Compiles a template and associated directives into a compilation
190
+ * result which can be used to create views.
191
+ * @param html - The html string or template element to compile.
192
+ * @param directives - The directives referenced by the template.
193
+ * @remarks
194
+ * The template that is provided for compilation is altered in-place
195
+ * and cannot be compiled again. If the original template must be preserved,
196
+ * it is recommended that you clone the original and pass the clone to this API.
197
+ * @public
198
+ */
199
+ compile(html, directives) {
200
+ let template;
201
+ if (isString(html)) {
202
+ template = document.createElement(templateTag);
203
+ template.innerHTML = htmlPolicy.createHTML(html);
204
+ const fec = template.content.firstElementChild;
205
+ if (fec !== null && fec.tagName === templateTag) {
206
+ template = fec;
207
+ }
208
+ }
209
+ else {
210
+ template = html;
211
+ }
212
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
213
+ const fragment = document.adoptNode(template.content);
214
+ const context = new CompilationContext(fragment, directives);
215
+ compileAttributes(context, "", template, /* host */ "h", 0, true);
216
+ if (
217
+ // If the first node in a fragment is a marker, that means it's an unstable first node,
218
+ // because something like a when, repeat, etc. could add nodes before the marker.
219
+ // To mitigate this, we insert a stable first node. However, if we insert a node,
220
+ // that will alter the result of the TreeWalker. So, we also need to offset the target index.
221
+ isMarker(fragment.firstChild, directives) ||
222
+ // Or if there is only one node and a directive, it means the template's content
223
+ // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
224
+ // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
225
+ (fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
226
+ fragment.insertBefore(document.createComment(""), fragment.firstChild);
227
+ }
228
+ compileChildren(context, fragment, /* root */ "r");
229
+ next.node = null; // prevent leaks
230
+ return context.freeze();
231
+ },
232
+ /**
233
+ * Sets the default compilation strategy that will be used by the ViewTemplate whenever
234
+ * it needs to compile a view preprocessed with the html template function.
235
+ * @param strategy - The compilation strategy to use when compiling templates.
236
+ */
237
+ setDefaultStrategy(strategy) {
238
+ this.compile = strategy;
239
+ },
240
+ /**
241
+ * Aggregates an array of strings and directives into a single directive.
242
+ * @param parts - A heterogeneous array of static strings interspersed with
243
+ * directives.
244
+ * @returns A single inline directive that aggregates the behavior of all the parts.
245
+ */
246
+ aggregate(parts) {
247
+ if (parts.length === 1) {
248
+ return parts[0];
249
+ }
250
+ let sourceAspect;
251
+ const partCount = parts.length;
252
+ const finalParts = parts.map((x) => {
253
+ if (isString(x)) {
254
+ return () => x;
255
+ }
256
+ sourceAspect = x.sourceAspect || sourceAspect;
257
+ return x.binding;
258
+ });
259
+ const binding = (scope, context) => {
260
+ let output = "";
261
+ for (let i = 0; i < partCount; ++i) {
262
+ output += finalParts[i](scope, context);
263
+ }
264
+ return output;
265
+ };
266
+ const directive = bind(binding);
267
+ Aspect.assign(directive, sourceAspect);
268
+ return directive;
269
+ },
270
+ };
@@ -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
+ });
@@ -1,64 +1,152 @@
1
- import { DOM } from "../dom.js";
1
+ import { createTypeRegistry } from "../platform.js";
2
+ import { Markup } from "./markup.js";
3
+ const registry = createTypeRegistry();
2
4
  /**
3
5
  * Instructs the template engine to apply behavior to a node.
4
6
  * @public
5
7
  */
6
- export class HTMLDirective {
7
- constructor() {
8
- /**
9
- * The index of the DOM node to which the created behavior will apply.
10
- */
11
- this.targetIndex = 0;
12
- }
13
- }
8
+ export const HTMLDirective = Object.freeze({
9
+ /**
10
+ * Gets the directive definition associated with the instance.
11
+ * @param instance - The directive instance to retrieve the definition for.
12
+ */
13
+ getForInstance: registry.getForInstance,
14
+ /**
15
+ * Gets the directive definition associated with the specified type.
16
+ * @param type - The directive type to retrieve the definition for.
17
+ */
18
+ getByType: registry.getByType,
19
+ /**
20
+ * Defines an HTMLDirective based on the options.
21
+ * @param type - The type to define as a directive.
22
+ * @param options - Options that specify the directive's application.
23
+ */
24
+ define(type, options) {
25
+ options = options || {};
26
+ options.type = type;
27
+ registry.register(options);
28
+ return type;
29
+ },
30
+ });
14
31
  /**
15
- * A {@link HTMLDirective} that targets a named attribute or property on a node.
32
+ * Decorator: Defines an HTMLDirective.
33
+ * @param options - Provides options that specify the directive's application.
16
34
  * @public
17
35
  */
18
- export class TargetedHTMLDirective extends HTMLDirective {
19
- constructor() {
20
- super(...arguments);
21
- /**
22
- * Creates a placeholder string based on the directive's index within the template.
23
- * @param index - The index of the directive within the template.
24
- */
25
- this.createPlaceholder = DOM.createInterpolationPlaceholder;
26
- }
36
+ export function htmlDirective(options) {
37
+ /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
38
+ return function (type) {
39
+ HTMLDirective.define(type, options);
40
+ };
27
41
  }
28
42
  /**
29
- * A directive that attaches special behavior to an element via a custom attribute.
43
+ * The type of HTML aspect to target.
30
44
  * @public
31
45
  */
32
- export class AttachedBehaviorHTMLDirective extends HTMLDirective {
46
+ export const Aspect = Object.freeze({
47
+ /**
48
+ * Not aspected.
49
+ */
50
+ none: 0,
51
+ /**
52
+ * An attribute.
53
+ */
54
+ attribute: 1,
55
+ /**
56
+ * A boolean attribute.
57
+ */
58
+ booleanAttribute: 2,
59
+ /**
60
+ * A property.
61
+ */
62
+ property: 3,
63
+ /**
64
+ * Content
65
+ */
66
+ content: 4,
67
+ /**
68
+ * A token list.
69
+ */
70
+ tokenList: 5,
71
+ /**
72
+ * An event.
73
+ */
74
+ event: 6,
33
75
  /**
34
76
  *
35
- * @param name - The name of the behavior; used as a custom attribute on the element.
36
- * @param behavior - The behavior to instantiate and attach to the element.
37
- * @param options - Options to pass to the behavior during creation.
77
+ * @param directive - The directive to assign the aspect to.
78
+ * @param value - The value to base the aspect determination on.
79
+ * @remarks
80
+ * If a falsy value is provided, then the content aspect will be assigned.
81
+ */
82
+ assign(directive, value) {
83
+ if (!value) {
84
+ directive.aspectType = Aspect.content;
85
+ return;
86
+ }
87
+ directive.sourceAspect = value;
88
+ switch (value[0]) {
89
+ case ":":
90
+ directive.targetAspect = value.substring(1);
91
+ switch (directive.targetAspect) {
92
+ case "innerHTML":
93
+ directive.aspectType = Aspect.property;
94
+ break;
95
+ case "classList":
96
+ directive.aspectType = Aspect.tokenList;
97
+ break;
98
+ default:
99
+ directive.aspectType = Aspect.property;
100
+ break;
101
+ }
102
+ break;
103
+ case "?":
104
+ directive.targetAspect = value.substring(1);
105
+ directive.aspectType = Aspect.booleanAttribute;
106
+ break;
107
+ case "@":
108
+ directive.targetAspect = value.substring(1);
109
+ directive.aspectType = Aspect.event;
110
+ break;
111
+ default:
112
+ if (value === "class") {
113
+ directive.targetAspect = "className";
114
+ directive.aspectType = Aspect.property;
115
+ }
116
+ else {
117
+ directive.targetAspect = value;
118
+ directive.aspectType = Aspect.attribute;
119
+ }
120
+ break;
121
+ }
122
+ },
123
+ });
124
+ /**
125
+ * A base class used for attribute directives that don't need internal state.
126
+ * @public
127
+ */
128
+ export class StatelessAttachedAttributeDirective {
129
+ /**
130
+ * Creates an instance of RefDirective.
131
+ * @param options - The options to use in configuring the directive.
38
132
  */
39
- constructor(name, behavior, options) {
40
- super();
41
- this.name = name;
42
- this.behavior = behavior;
133
+ constructor(options) {
43
134
  this.options = options;
44
135
  }
45
136
  /**
46
- * Creates a placeholder string based on the directive's index within the template.
47
- * @param index - The index of the directive within the template.
48
- * @remarks
49
- * Creates a custom attribute placeholder.
137
+ * Creates a behavior.
138
+ * @param targets - The targets available for behaviors to be attached to.
50
139
  */
51
- createPlaceholder(index) {
52
- return DOM.createCustomAttributePlaceholder(this.name, index);
140
+ createBehavior(targets) {
141
+ return this;
53
142
  }
54
143
  /**
55
- * Creates a behavior for the provided target node.
56
- * @param target - The node instance to create the behavior for.
144
+ * Creates a placeholder string based on the directive's index within the template.
145
+ * @param index - The index of the directive within the template.
57
146
  * @remarks
58
- * Creates an instance of the `behavior` type this directive was constructed with
59
- * and passes the target and options to that `behavior`'s constructor.
147
+ * Creates a custom attribute placeholder.
60
148
  */
61
- createBehavior(target) {
62
- return new this.behavior(target, this.options);
149
+ createHTML(add) {
150
+ return Markup.attribute(add(this));
63
151
  }
64
152
  }