@microsoft/fast-html 1.0.0-alpha.41 → 1.0.0-alpha.42

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.
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Default syntax for FAST declarative templates
3
+ */
4
+ export declare const attributeDirectivePrefix: string, clientSideCloseExpression: string, clientSideOpenExpression: string, closeExpression: string, executionContextAccessor: string, openExpression: string, repeatDirectiveClose: string, repeatDirectiveOpen: string, unescapedCloseExpression: string, unescapedOpenExpression: string, whenDirectiveClose: string, whenDirectiveOpen: string;
@@ -40,7 +40,6 @@ export interface ChildrenMap {
40
40
  customElementName: string;
41
41
  attributeName: string;
42
42
  }
43
- export declare const contextPrefix: string;
44
43
  export declare const contextPrefixDot: string;
45
44
  declare const LogicalOperator: {
46
45
  AND: string;
@@ -117,13 +116,14 @@ export declare function getExpressionChain(value: string): ChainedExpression | v
117
116
  */
118
117
  export declare function transformInnerHTML(innerHTML: string, index?: number): string;
119
118
  /**
120
- * Resolves f-when
119
+ * Resolves boolean logic
120
+ * used for f-when and boolean attributes
121
121
  * @param self - Where the first item in the path path refers to the item itself (used by repeat).
122
122
  * @param chainedExpression - The chained expression which includes the expression and the next expression
123
123
  * if there is another in the chain
124
124
  * @returns - A binding that resolves the chained expression logic
125
125
  */
126
- export declare function resolveWhen(rootPropertyName: string | null, expression: ChainedExpression, parentContext: string | null, level: number, schema: Schema): (x: boolean, c: any) => any;
126
+ export declare function getBooleanBinding(rootPropertyName: string | null, expression: ChainedExpression, parentContext: string | null, level: number, schema: Schema): (x: boolean, c: any) => any;
127
127
  /**
128
128
  * Find a definition
129
129
  * This may exist as a $ref at the root or as a $ref in any anyOf or not at all
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Default syntax for FAST declarative templates
3
+ */
4
+ export const { attributeDirectivePrefix, clientSideCloseExpression, clientSideOpenExpression, closeExpression, executionContextAccessor, openExpression, repeatDirectiveClose, repeatDirectiveOpen, unescapedCloseExpression, unescapedOpenExpression, whenDirectiveClose, whenDirectiveOpen, } = {
5
+ attributeDirectivePrefix: "f-",
6
+ clientSideCloseExpression: "}",
7
+ clientSideOpenExpression: "{",
8
+ closeExpression: "}}",
9
+ executionContextAccessor: "$c",
10
+ openExpression: "{{",
11
+ unescapedCloseExpression: "}}}",
12
+ unescapedOpenExpression: "{{{",
13
+ repeatDirectiveClose: "</f-repeat>",
14
+ repeatDirectiveOpen: "<f-repeat",
15
+ whenDirectiveClose: "</f-when>",
16
+ whenDirectiveOpen: "<f-when",
17
+ };
@@ -3,7 +3,7 @@ import { attr, children, elements, FAST, FASTElement, FASTElementDefinition, fas
3
3
  import "@microsoft/fast-element/install-hydratable-view-templates.js";
4
4
  import { ObserverMap } from "./observer-map.js";
5
5
  import { Schema } from "./schema.js";
6
- import { bindingResolver, contextPrefixDot, getExpressionChain, getNextBehavior, getRootPropertyName, resolveWhen, transformInnerHTML, } from "./utilities.js";
6
+ import { bindingResolver, contextPrefixDot, getBooleanBinding, getExpressionChain, getNextBehavior, getRootPropertyName, transformInnerHTML, } from "./utilities.js";
7
7
  /**
8
8
  * Values for the observerMap element option.
9
9
  */
@@ -140,7 +140,7 @@ class TemplateElement extends FASTElement {
140
140
  switch (behaviorConfig.name) {
141
141
  case "when": {
142
142
  const expressionChain = getExpressionChain(behaviorConfig.value);
143
- const whenLogic = resolveWhen(rootPropertyName, expressionChain, parentContext, level, schema);
143
+ const whenLogic = getBooleanBinding(rootPropertyName, expressionChain, parentContext, level, schema);
144
144
  const { strings, values } = await this.resolveStringsAndValues(rootPropertyName, innerHTML.slice(behaviorConfig.openingTagEndIndex, behaviorConfig.closingTagStartIndex), self, parentContext, level, schema, observerMap);
145
145
  externalValues.push(when(whenLogic, this.resolveTemplateOrBehavior(strings, values)));
146
146
  break;
@@ -215,39 +215,57 @@ class TemplateElement extends FASTElement {
215
215
  }
216
216
  case "attribute": {
217
217
  strings.push(innerHTML.slice(0, behaviorConfig.openingStartIndex));
218
- if (behaviorConfig.aspect === "@") {
219
- const bindingHTML = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex);
220
- const openingParenthesis = bindingHTML.indexOf("(");
221
- const closingParenthesis = bindingHTML.indexOf(")");
222
- const propName = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex -
223
- (closingParenthesis - openingParenthesis) -
224
- 1);
225
- const type = "event";
226
- rootPropertyName = getRootPropertyName(rootPropertyName, propName, parentContext, type);
227
- const arg = bindingHTML.slice(openingParenthesis + 1, closingParenthesis);
228
- const binding = bindingResolver(strings.join(""), rootPropertyName, propName, parentContext, type, schema, parentContext, level);
229
- const isContextPath = propName.startsWith(contextPrefixDot);
230
- const getOwner = isContextPath
231
- ? (_x, c) => {
232
- const ownerPath = propName.split(".").slice(1, -1);
233
- return ownerPath.reduce((prev, item) => prev === null || prev === void 0 ? void 0 : prev[item], c);
218
+ let attributeBinding;
219
+ switch (behaviorConfig.aspect) {
220
+ case "@": {
221
+ const bindingHTML = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex);
222
+ const openingParenthesis = bindingHTML.indexOf("(");
223
+ const closingParenthesis = bindingHTML.indexOf(")");
224
+ const propName = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex -
225
+ (closingParenthesis - openingParenthesis) -
226
+ 1);
227
+ const type = "event";
228
+ rootPropertyName = getRootPropertyName(rootPropertyName, propName, parentContext, type);
229
+ const arg = bindingHTML.slice(openingParenthesis + 1, closingParenthesis);
230
+ const binding = bindingResolver(strings.join(""), rootPropertyName, propName, parentContext, type, schema, parentContext, level);
231
+ const isContextPath = propName.startsWith(contextPrefixDot);
232
+ const getOwner = isContextPath
233
+ ? (_x, c) => {
234
+ const ownerPath = propName.split(".").slice(1, -1);
235
+ return ownerPath.reduce((prev, item) => prev === null || prev === void 0 ? void 0 : prev[item], c);
236
+ }
237
+ : (x, _c) => x;
238
+ attributeBinding = (x, c) => binding(x, c).bind(getOwner(x, c))(...(arg === "e" ? [c.event] : []), ...(arg !== "e" && arg !== ""
239
+ ? [
240
+ bindingResolver(strings.join(""), rootPropertyName, arg, parentContext, type, schema, parentContext, level)(x, c),
241
+ ]
242
+ : []));
243
+ break;
244
+ }
245
+ case "?": {
246
+ const expression = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex);
247
+ const expressionChain = getExpressionChain(expression);
248
+ if (expressionChain === null || expressionChain === void 0 ? void 0 : expressionChain.expression.operator) {
249
+ attributeBinding = getBooleanBinding(rootPropertyName, expressionChain, parentContext, level, schema);
234
250
  }
235
- : (x, _c) => x;
236
- const attributeBinding = (x, c) => binding(x, c).bind(getOwner(x, c))(...(arg === "e" ? [c.event] : []), ...(arg !== "e" && arg !== ""
237
- ? [
238
- bindingResolver(strings.join(""), rootPropertyName, arg, parentContext, type, schema, parentContext, level)(x, c),
239
- ]
240
- : []));
241
- values.push(attributeBinding);
242
- }
243
- else {
244
- const propName = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex);
245
- const type = "access";
246
- rootPropertyName = getRootPropertyName(rootPropertyName, propName, parentContext, type);
247
- const binding = bindingResolver(strings.join(""), rootPropertyName, propName, parentContext, type, schema, parentContext, level);
248
- const attributeBinding = (x, c) => binding(x, c);
249
- values.push(attributeBinding);
251
+ else {
252
+ const propName = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex);
253
+ const type = "access";
254
+ rootPropertyName = getRootPropertyName(rootPropertyName, propName, parentContext, type);
255
+ const binding = bindingResolver(strings.join(""), rootPropertyName, propName, parentContext, type, schema, parentContext, level);
256
+ attributeBinding = (x, c) => binding(x, c);
257
+ }
258
+ break;
259
+ }
260
+ default: {
261
+ const propName = innerHTML.slice(behaviorConfig.openingEndIndex, behaviorConfig.closingStartIndex);
262
+ const type = "access";
263
+ rootPropertyName = getRootPropertyName(rootPropertyName, propName, parentContext, type);
264
+ const binding = bindingResolver(strings.join(""), rootPropertyName, propName, parentContext, type, schema, parentContext, level);
265
+ attributeBinding = (x, c) => binding(x, c);
266
+ }
250
267
  }
268
+ values.push(attributeBinding);
251
269
  await this.resolveInnerHTML(rootPropertyName, innerHTML.slice(behaviorConfig.closingEndIndex, innerHTML.length), strings, values, self, parentContext, level, schema, observerMap);
252
270
  break;
253
271
  }
@@ -1,17 +1,7 @@
1
1
  import { Observable } from "@microsoft/fast-element/observable.js";
2
2
  import { defsPropertyName, fastContextMetaData, refPropertyName, Schema, } from "./schema.js";
3
- const openClientSideBinding = "{";
4
- const closeClientSideBinding = "}";
5
- const openContentBinding = "{{";
6
- const closeContentBinding = "}}";
7
- const openUnescapedBinding = "{{{";
8
- const closeUnescapedBinding = "}}}";
9
- const openTagStart = "<f-";
10
- const tagEnd = ">";
11
- const closeTagStart = "</f-";
12
- const attributeDirectivePrefix = "f-";
13
- export const contextPrefix = "$c";
14
- export const contextPrefixDot = `${contextPrefix}.`;
3
+ import { attributeDirectivePrefix, clientSideCloseExpression, clientSideOpenExpression, closeExpression, executionContextAccessor, openExpression, repeatDirectiveClose, repeatDirectiveOpen, unescapedCloseExpression, unescapedOpenExpression, whenDirectiveClose, whenDirectiveOpen, } from "./syntax.js";
4
+ export const contextPrefixDot = `${executionContextAccessor}.`;
15
5
  const startInnerHTMLDiv = `<div :innerHTML="{{`;
16
6
  const startInnerHTMLDivLength = startInnerHTMLDiv.length;
17
7
  const endInnerHTMLDiv = `}}"></div>`;
@@ -92,16 +82,23 @@ export function getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, clos
92
82
  * @returns DirectiveBehaviorConfig - A configuration object
93
83
  */
94
84
  function getNextDirectiveBehavior(innerHTML) {
95
- const openingTagStartIndex = innerHTML.indexOf(openTagStart);
85
+ const whenIndex = innerHTML.indexOf(whenDirectiveOpen);
86
+ const repeatIndex = innerHTML.indexOf(repeatDirectiveOpen);
87
+ const isWhen = whenIndex !== -1 && (repeatIndex === -1 || whenIndex < repeatIndex);
88
+ let openingTag = repeatDirectiveOpen;
89
+ let closingTag = repeatDirectiveClose;
90
+ let directiveTag = "repeat";
91
+ let openingTagStartIndex = repeatIndex;
92
+ if (isWhen) {
93
+ openingTag = whenDirectiveOpen;
94
+ closingTag = whenDirectiveClose;
95
+ directiveTag = "when";
96
+ openingTagStartIndex = whenIndex;
97
+ }
96
98
  const openingTagStartSlice = innerHTML.slice(openingTagStartIndex);
97
99
  const openingTagEndIndex = // account for f-when which may include >= or > as operators, but will always include a condition attr
98
- openingTagStartSlice.indexOf(`"${tagEnd}`) + openingTagStartIndex + 2;
99
- const directiveTag = innerHTML
100
- .slice(openingTagStartIndex + 3, openingTagEndIndex - 1)
101
- .split(" ")[0];
100
+ openingTagStartSlice.indexOf(`">`) + openingTagStartIndex + 2;
102
101
  const directiveValue = getNextDataBindingBehavior(innerHTML);
103
- const openingTag = `${openTagStart}${directiveTag}`;
104
- const closingTag = `${closeTagStart}${directiveTag}${tagEnd}`;
105
102
  const matchingCloseTagIndex = getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, closingTag, openingTagStartIndex);
106
103
  return {
107
104
  type: "templateDirective",
@@ -187,14 +184,14 @@ function getContentDataBindingConfig(config) {
187
184
  */
188
185
  function getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML) {
189
186
  // {{{}}} binding
190
- const openingUnescapedStartIndex = innerHTML.indexOf(openUnescapedBinding);
191
- const closingUnescapedStartIndex = innerHTML.indexOf(closeUnescapedBinding);
187
+ const openingUnescapedStartIndex = innerHTML.indexOf(unescapedOpenExpression);
188
+ const closingUnescapedStartIndex = innerHTML.indexOf(unescapedCloseExpression);
192
189
  // {{}} binding
193
- const openingContentStartIndex = innerHTML.indexOf(openContentBinding);
194
- const closingContentStartIndex = innerHTML.indexOf(closeContentBinding);
190
+ const openingContentStartIndex = innerHTML.indexOf(openExpression);
191
+ const closingContentStartIndex = innerHTML.indexOf(closeExpression);
195
192
  // {} binding
196
- const openingClientStartIndex = innerHTML.indexOf(openClientSideBinding);
197
- const closingClientStartIndex = innerHTML.indexOf(closeClientSideBinding);
193
+ const openingClientStartIndex = innerHTML.indexOf(clientSideOpenExpression);
194
+ const closingClientStartIndex = innerHTML.indexOf(clientSideCloseExpression);
198
195
  if (openingUnescapedStartIndex !== -1 &&
199
196
  openingUnescapedStartIndex <= openingContentStartIndex &&
200
197
  openingUnescapedStartIndex <= openingClientStartIndex) {
@@ -253,8 +250,14 @@ export function getNextBehavior(innerHTML, offset = 0) {
253
250
  while (true) {
254
251
  const currentSlice = innerHTML.slice(offset);
255
252
  // client side binding will capture all bindings starting with "{"
256
- const dataBindingOpen = currentSlice.indexOf(openClientSideBinding);
257
- const directiveBindingOpen = currentSlice.indexOf(openTagStart);
253
+ const dataBindingOpen = currentSlice.indexOf(clientSideOpenExpression);
254
+ const whenDirectiveIndex = currentSlice.indexOf(whenDirectiveOpen);
255
+ const repeatDirectiveIndex = currentSlice.indexOf(repeatDirectiveOpen);
256
+ const directiveBindingOpen = whenDirectiveIndex === -1
257
+ ? repeatDirectiveIndex
258
+ : repeatDirectiveIndex === -1
259
+ ? whenDirectiveIndex
260
+ : Math.min(whenDirectiveIndex, repeatDirectiveIndex);
258
261
  const nextDataBindingBehavior = getNextDataBindingBehavior(currentSlice);
259
262
  if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
260
263
  return null;
@@ -320,8 +323,8 @@ function isLegitimateClientSideBinding(result) {
320
323
  export function pathResolver(path, contextPath, level, rootSchema) {
321
324
  var _a, _b;
322
325
  let splitPath = path.split(".");
323
- // Explicit context access via contextPrefix — resolve directly from ExecutionContext
324
- if (splitPath[0] === contextPrefix) {
326
+ // Explicit context access via executionContextAccessor — resolve directly from ExecutionContext
327
+ if (splitPath[0] === executionContextAccessor) {
325
328
  const contextAccessPath = splitPath.slice(1);
326
329
  return (_accessibleObject, context) => {
327
330
  return contextAccessPath.reduce((prev, item) => prev === null || prev === void 0 ? void 0 : prev[item], context);
@@ -461,7 +464,7 @@ function isOperandValue(operand) {
461
464
  isValue: true,
462
465
  };
463
466
  }
464
- catch (e) {
467
+ catch {
465
468
  return {
466
469
  value: operand,
467
470
  isValue: false,
@@ -604,9 +607,11 @@ function resolveExpression(x, c, level, contextPath, expression, rootSchema) {
604
607
  return !resolvedLeft;
605
608
  }
606
609
  case Operator.EQUALS: {
610
+ // biome-ignore lint/suspicious/noDoubleEquals: Breaks prior existing functionality - see when fixture
607
611
  return resolvedLeft == resolvedRight;
608
612
  }
609
613
  case Operator.NOT_EQUALS: {
614
+ // biome-ignore lint/suspicious/noDoubleEquals: Breaks prior existing functionality - see when fixture
610
615
  return resolvedLeft != resolvedRight;
611
616
  }
612
617
  case Operator.GREATER_THAN_OR_EQUALS: {
@@ -691,13 +696,14 @@ export function transformInnerHTML(innerHTML, index = 0) {
691
696
  return transformedInnerHTML;
692
697
  }
693
698
  /**
694
- * Resolves f-when
699
+ * Resolves boolean logic
700
+ * used for f-when and boolean attributes
695
701
  * @param self - Where the first item in the path path refers to the item itself (used by repeat).
696
702
  * @param chainedExpression - The chained expression which includes the expression and the next expression
697
703
  * if there is another in the chain
698
704
  * @returns - A binding that resolves the chained expression logic
699
705
  */
700
- export function resolveWhen(rootPropertyName, expression, parentContext, level, schema) {
706
+ export function getBooleanBinding(rootPropertyName, expression, parentContext, level, schema) {
701
707
  const binding = expressionResolver(rootPropertyName, expression, parentContext, level, schema);
702
708
  return (x, c) => binding(x, c);
703
709
  }
@@ -1,6 +1,6 @@
1
1
  import { expect, test } from "@playwright/test";
2
2
  import { refPropertyName, Schema } from "./schema.js";
3
- import { getNextBehavior, getIndexOfNextMatchingTag, pathResolver, transformInnerHTML, getExpressionChain, extractPathsFromChainedExpression, getChildrenMap, findDef, resolveWhen, } from "./utilities.js";
3
+ import { extractPathsFromChainedExpression, findDef, getBooleanBinding, getChildrenMap, getExpressionChain, getIndexOfNextMatchingTag, getNextBehavior, pathResolver, transformInnerHTML, } from "./utilities.js";
4
4
  test.describe("utilities", async () => {
5
5
  test.describe("content", async () => {
6
6
  test("get the next content binding", async () => {
@@ -17,7 +17,7 @@ test.describe("utilities", async () => {
17
17
  });
18
18
  test.describe("attributes", async () => {
19
19
  test("get the next attribute binding", async () => {
20
- const innerHTML = "<input type=\"{{type}}\" disabled>";
20
+ const innerHTML = '<input type="{{type}}" disabled>';
21
21
  const templateResult = getNextBehavior(innerHTML);
22
22
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
23
23
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
@@ -29,7 +29,7 @@ test.describe("utilities", async () => {
29
29
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingEndIndex).toEqual(21);
30
30
  });
31
31
  test("get the next attribute event binding", async () => {
32
- const innerHTML = "<input @click=\"{handleClick()}\">";
32
+ const innerHTML = '<input @click="{handleClick()}">';
33
33
  const templateResult = getNextBehavior(innerHTML);
34
34
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
35
35
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
@@ -46,10 +46,10 @@ test.describe("utilities", async () => {
46
46
  expect(templateResult).toBeNull();
47
47
  });
48
48
  test("skip single-brace non-event, non-property attribute bindings", async () => {
49
- const innerHTML1 = "<input type=\"{type}\">";
49
+ const innerHTML1 = '<input type="{type}">';
50
50
  const templateResult1 = getNextBehavior(innerHTML1);
51
51
  expect(templateResult1).toBeNull();
52
- const innerHTML2 = "<input ?disabled=\"{disabled}\">";
52
+ const innerHTML2 = '<input ?disabled="{disabled}">';
53
53
  const templateResult2 = getNextBehavior(innerHTML2);
54
54
  expect(templateResult2).toBeNull();
55
55
  });
@@ -63,7 +63,7 @@ test.describe("utilities", async () => {
63
63
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(67);
64
64
  });
65
65
  test("find event binding after skipped single-brace content", async () => {
66
- const innerHTML = "<style>.foo { color: red }</style><button @click=\"{handler()}\">";
66
+ const innerHTML = '<style>.foo { color: red }</style><button @click="{handler()}">';
67
67
  const templateResult = getNextBehavior(innerHTML);
68
68
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
69
69
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
@@ -71,7 +71,7 @@ test.describe("utilities", async () => {
71
71
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("client");
72
72
  });
73
73
  test("find property binding after skipped single-brace content", async () => {
74
- const innerHTML = "<style>.foo { color: red } .bar { color: blue }</style><button :value=\"{someValue}\">";
74
+ const innerHTML = '<style>.foo { color: red } .bar { color: blue }</style><button :value="{someValue}">';
75
75
  const templateResult = getNextBehavior(innerHTML);
76
76
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
77
77
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
@@ -79,13 +79,13 @@ test.describe("utilities", async () => {
79
79
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("client");
80
80
  });
81
81
  test("ensure if there are expected missing {} this does not cause parsing issues", async () => {
82
- const innerHTML = "<f-when value=\"missing\">";
82
+ const innerHTML = '<f-when value="missing">';
83
83
  expect(getNextBehavior(innerHTML)).toBeTruthy();
84
84
  });
85
85
  });
86
86
  test.describe("templates", async () => {
87
87
  test("when directive", async () => {
88
- const innerHTML = "<f-when value=\"{{show}}\">Hello world</f-when>";
88
+ const innerHTML = '<f-when value="{{show}}">Hello world</f-when>';
89
89
  const templateResult = getNextBehavior(innerHTML);
90
90
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("templateDirective");
91
91
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingTagStartIndex).toEqual(0);
@@ -94,7 +94,7 @@ test.describe("utilities", async () => {
94
94
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingTagEndIndex).toEqual(45);
95
95
  });
96
96
  test("when directive with content", async () => {
97
- const innerHTML = "Hello pluto<f-when value=\"{{show}}\">Hello world</f-when>";
97
+ const innerHTML = 'Hello pluto<f-when value="{{show}}">Hello world</f-when>';
98
98
  const templateResult = getNextBehavior(innerHTML);
99
99
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("templateDirective");
100
100
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingTagStartIndex).toEqual(11);
@@ -103,7 +103,7 @@ test.describe("utilities", async () => {
103
103
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingTagEndIndex).toEqual(56);
104
104
  });
105
105
  test("when directive with binding", async () => {
106
- const innerHTML = "<f-when value=\"{{show}}\">{{text}}</f-when>";
106
+ const innerHTML = '<f-when value="{{show}}">{{text}}</f-when>';
107
107
  const templateResult = getNextBehavior(innerHTML);
108
108
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("templateDirective");
109
109
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingTagStartIndex).toEqual(0);
@@ -111,10 +111,16 @@ test.describe("utilities", async () => {
111
111
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingTagStartIndex).toEqual(33);
112
112
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingTagEndIndex).toEqual(42);
113
113
  });
114
+ test("should not treat an unknown f- tag as a directive", async () => {
115
+ const innerHTML = '<f-foo value="{{bar}}">Hello</f-foo>';
116
+ const templateResult = getNextBehavior(innerHTML);
117
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
118
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
119
+ });
114
120
  });
115
121
  test.describe("attributes", async () => {
116
122
  test("children directive", async () => {
117
- const innerHTML = "<ul f-children=\"{list}\"></ul>";
123
+ const innerHTML = '<ul f-children="{list}"></ul>';
118
124
  const result = getNextBehavior(innerHTML);
119
125
  expect(result === null || result === void 0 ? void 0 : result.type).toEqual("dataBinding");
120
126
  expect(result === null || result === void 0 ? void 0 : result.subtype).toEqual("attributeDirective");
@@ -126,7 +132,7 @@ test.describe("utilities", async () => {
126
132
  expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(22);
127
133
  });
128
134
  test("slotted directive", async () => {
129
- const innerHTML = "<slot f-slotted=\"{slottedNodes}\"></slot>";
135
+ const innerHTML = '<slot f-slotted="{slottedNodes}"></slot>';
130
136
  const result = getNextBehavior(innerHTML);
131
137
  expect(result === null || result === void 0 ? void 0 : result.type).toEqual("dataBinding");
132
138
  expect(result === null || result === void 0 ? void 0 : result.subtype).toEqual("attributeDirective");
@@ -138,7 +144,7 @@ test.describe("utilities", async () => {
138
144
  expect(result === null || result === void 0 ? void 0 : result.closingEndIndex).toEqual(31);
139
145
  });
140
146
  test("ref directive", async () => {
141
- const innerHTML = "<video f-ref=\"{video}\"></video>";
147
+ const innerHTML = '<video f-ref="{video}"></video>';
142
148
  const result = getNextBehavior(innerHTML);
143
149
  expect(result === null || result === void 0 ? void 0 : result.type).toEqual("dataBinding");
144
150
  expect(result === null || result === void 0 ? void 0 : result.subtype).toEqual("attributeDirective");
@@ -214,7 +220,7 @@ test.describe("utilities", async () => {
214
220
  leftIsValue: false,
215
221
  right: null,
216
222
  rightIsValue: null,
217
- }
223
+ },
218
224
  });
219
225
  });
220
226
  test("should resolve a falsy value", async () => {
@@ -225,7 +231,7 @@ test.describe("utilities", async () => {
225
231
  leftIsValue: false,
226
232
  right: null,
227
233
  rightIsValue: null,
228
- }
234
+ },
229
235
  });
230
236
  });
231
237
  test("should resolve a path not equal to string value", async () => {
@@ -236,7 +242,7 @@ test.describe("utilities", async () => {
236
242
  leftIsValue: false,
237
243
  right: "test",
238
244
  rightIsValue: true,
239
- }
245
+ },
240
246
  });
241
247
  });
242
248
  test("should resolve a path not equal to boolean value", async () => {
@@ -247,7 +253,7 @@ test.describe("utilities", async () => {
247
253
  leftIsValue: false,
248
254
  right: false,
249
255
  rightIsValue: true,
250
- }
256
+ },
251
257
  });
252
258
  });
253
259
  test("should resolve a path not equal to numerical value", async () => {
@@ -258,7 +264,7 @@ test.describe("utilities", async () => {
258
264
  leftIsValue: false,
259
265
  right: 5,
260
266
  rightIsValue: true,
261
- }
267
+ },
262
268
  });
263
269
  });
264
270
  test("should resolve chained expressions", async () => {
@@ -278,8 +284,8 @@ test.describe("utilities", async () => {
278
284
  leftIsValue: false,
279
285
  right: "baz",
280
286
  rightIsValue: true,
281
- }
282
- }
287
+ },
288
+ },
283
289
  });
284
290
  expect(getExpressionChain("foo && bar")).toEqual({
285
291
  expression: {
@@ -297,8 +303,8 @@ test.describe("utilities", async () => {
297
303
  leftIsValue: false,
298
304
  right: null,
299
305
  rightIsValue: null,
300
- }
301
- }
306
+ },
307
+ },
302
308
  });
303
309
  });
304
310
  });
@@ -401,7 +407,7 @@ test.describe("utilities", async () => {
401
407
  expect(paths.has("app.user.profile.settings.theme")).toBe(true);
402
408
  });
403
409
  });
404
- test.describe("resolveWhen - default case truthiness evaluation", async () => {
410
+ test.describe("getBooleanBinding - default case truthiness evaluation", async () => {
405
411
  // Helper to create a basic schema for testing
406
412
  const createTestSchema = (rootPropertyName, propertyName) => {
407
413
  const schema = new Schema("test-element");
@@ -420,98 +426,98 @@ test.describe("utilities", async () => {
420
426
  test("should evaluate boolean true as truthy", async () => {
421
427
  const schema = createTestSchema("testData", "boolTrue");
422
428
  const expression = getExpressionChain("boolTrue");
423
- const resolver = resolveWhen("testData", expression, null, 0, schema);
429
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
424
430
  const result = resolver({ boolTrue: true }, null);
425
431
  expect(result).toBe(true);
426
432
  });
427
433
  test("should evaluate boolean false as falsy", async () => {
428
434
  const schema = createTestSchema("testData", "boolFalse");
429
435
  const expression = getExpressionChain("boolFalse");
430
- const resolver = resolveWhen("testData", expression, null, 0, schema);
436
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
431
437
  const result = resolver({ boolFalse: false }, null);
432
438
  expect(result).toBe(false);
433
439
  });
434
440
  test("should evaluate number 0 as falsy", async () => {
435
441
  const schema = createTestSchema("testData", "numberZero");
436
442
  const expression = getExpressionChain("numberZero");
437
- const resolver = resolveWhen("testData", expression, null, 0, schema);
443
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
438
444
  const result = resolver({ numberZero: 0 }, null);
439
445
  expect(result).toBe(false);
440
446
  });
441
447
  test("should evaluate positive number as truthy", async () => {
442
448
  const schema = createTestSchema("testData", "numberPositive");
443
449
  const expression = getExpressionChain("numberPositive");
444
- const resolver = resolveWhen("testData", expression, null, 0, schema);
450
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
445
451
  const result = resolver({ numberPositive: 42 }, null);
446
452
  expect(result).toBe(true);
447
453
  });
448
454
  test("should evaluate negative number as truthy", async () => {
449
455
  const schema = createTestSchema("testData", "numberNegative");
450
456
  const expression = getExpressionChain("numberNegative");
451
- const resolver = resolveWhen("testData", expression, null, 0, schema);
457
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
452
458
  const result = resolver({ numberNegative: -5 }, null);
453
459
  expect(result).toBe(true);
454
460
  });
455
461
  test("should evaluate empty string as falsy", async () => {
456
462
  const schema = createTestSchema("testData", "stringEmpty");
457
463
  const expression = getExpressionChain("stringEmpty");
458
- const resolver = resolveWhen("testData", expression, null, 0, schema);
464
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
459
465
  const result = resolver({ stringEmpty: "" }, null);
460
466
  expect(result).toBe(false);
461
467
  });
462
468
  test("should evaluate non-empty string as truthy", async () => {
463
469
  const schema = createTestSchema("testData", "stringNonEmpty");
464
470
  const expression = getExpressionChain("stringNonEmpty");
465
- const resolver = resolveWhen("testData", expression, null, 0, schema);
471
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
466
472
  const result = resolver({ stringNonEmpty: "hello" }, null);
467
473
  expect(result).toBe(true);
468
474
  });
469
475
  test("should evaluate null as falsy", async () => {
470
476
  const schema = createTestSchema("testData", "objectNull");
471
477
  const expression = getExpressionChain("objectNull");
472
- const resolver = resolveWhen("testData", expression, null, 0, schema);
478
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
473
479
  const result = resolver({ objectNull: null }, null);
474
480
  expect(result).toBe(false);
475
481
  });
476
482
  test("should evaluate undefined as falsy", async () => {
477
483
  const schema = createTestSchema("testData", "undefinedProp");
478
484
  const expression = getExpressionChain("undefinedProp");
479
- const resolver = resolveWhen("testData", expression, null, 0, schema);
485
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
480
486
  const result = resolver({ undefinedProp: undefined }, null);
481
487
  expect(result).toBe(false);
482
488
  });
483
489
  test("should evaluate non-null object as truthy", async () => {
484
490
  const schema = createTestSchema("testData", "objectNonNull");
485
491
  const expression = getExpressionChain("objectNonNull");
486
- const resolver = resolveWhen("testData", expression, null, 0, schema);
492
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
487
493
  const result = resolver({ objectNonNull: { foo: "bar" } }, null);
488
494
  expect(result).toBe(true);
489
495
  });
490
496
  test("should evaluate empty array as truthy", async () => {
491
497
  const schema = createTestSchema("testData", "arrayEmpty");
492
498
  const expression = getExpressionChain("arrayEmpty");
493
- const resolver = resolveWhen("testData", expression, null, 0, schema);
499
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
494
500
  const result = resolver({ arrayEmpty: [] }, null);
495
501
  expect(result).toBe(true);
496
502
  });
497
503
  test("should evaluate non-empty array as truthy", async () => {
498
504
  const schema = createTestSchema("testData", "arrayNonEmpty");
499
505
  const expression = getExpressionChain("arrayNonEmpty");
500
- const resolver = resolveWhen("testData", expression, null, 0, schema);
506
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
501
507
  const result = resolver({ arrayNonEmpty: [1, 2, 3] }, null);
502
508
  expect(result).toBe(true);
503
509
  });
504
510
  test("should evaluate string with only whitespace as truthy", async () => {
505
511
  const schema = createTestSchema("testData", "stringWhitespace");
506
512
  const expression = getExpressionChain("stringWhitespace");
507
- const resolver = resolveWhen("testData", expression, null, 0, schema);
513
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
508
514
  const result = resolver({ stringWhitespace: " " }, null);
509
515
  expect(result).toBe(true);
510
516
  });
511
517
  test("should evaluate number NaN as truthy", async () => {
512
518
  const schema = createTestSchema("testData", "numberNaN");
513
519
  const expression = getExpressionChain("numberNaN");
514
- const resolver = resolveWhen("testData", expression, null, 0, schema);
520
+ const resolver = getBooleanBinding("testData", expression, null, 0, schema);
515
521
  const result = resolver({ numberNaN: NaN }, null);
516
522
  expect(result).toBe(true);
517
523
  });
@@ -548,37 +554,37 @@ test.describe("utilities", async () => {
548
554
  test.describe("findDef", async () => {
549
555
  test("should resolve from the root of a schema", async () => {
550
556
  expect(findDef({
551
- [refPropertyName]: "#/$defs/MyType"
557
+ [refPropertyName]: "#/$defs/MyType",
552
558
  })).toEqual("MyType");
553
559
  });
554
560
  test("should resolve as null from an anyOf array containing a reference to another component", async () => {
555
561
  expect(findDef({
556
562
  anyOf: [
557
563
  {
558
- [refPropertyName]: "https://fast.design/schemas/test-element/c.json"
559
- }
560
- ]
564
+ [refPropertyName]: "https://fast.design/schemas/test-element/c.json",
565
+ },
566
+ ],
561
567
  })).toEqual(null);
562
568
  });
563
569
  test("should resolve from an anyOf array containing a reference to a $def", async () => {
564
570
  expect(findDef({
565
571
  anyOf: [
566
572
  {
567
- [refPropertyName]: "#/$defs/MyType"
568
- }
569
- ]
573
+ [refPropertyName]: "#/$defs/MyType",
574
+ },
575
+ ],
570
576
  })).toEqual("MyType");
571
577
  });
572
578
  test("should resolve from an anyOf array containing a reference to another component and a reference to a $def", async () => {
573
579
  expect(findDef({
574
580
  anyOf: [
575
581
  {
576
- [refPropertyName]: "https://fast.design/schemas/test-element/c.json"
582
+ [refPropertyName]: "https://fast.design/schemas/test-element/c.json",
577
583
  },
578
584
  {
579
- [refPropertyName]: "#/$defs/MyType"
580
- }
581
- ]
585
+ [refPropertyName]: "#/$defs/MyType",
586
+ },
587
+ ],
582
588
  })).toEqual("MyType");
583
589
  });
584
590
  test("should resolve as null if not found", async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/fast-html",
3
- "version": "1.0.0-alpha.41",
3
+ "version": "1.0.0-alpha.42",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Microsoft",
@@ -23,17 +23,15 @@
23
23
  "scripts": {
24
24
  "build:tsc": "tsc -p ./tsconfig.json",
25
25
  "build": "npm run build:tsc && npm run doc",
26
- "clean": "rimraf dist temp test-results",
26
+ "clean": "clean dist temp test-results",
27
27
  "dev:full": "concurrently -k -n fast-element,fast-html,server \"npm run dev --workspace=@microsoft/fast-element\" \"npm:watch\" \"npm:test-server\"",
28
28
  "dev": "concurrently -k -n tsc,server \"npm run watch\" \"npm run test-server\"",
29
29
  "doc:ci": "api-extractor run",
30
30
  "doc": "npm run doc:ci -- --local",
31
- "eslint:fix": "npm run eslint -- --fix",
32
- "eslint": "eslint . --ext .ts",
31
+ "lint": "biome lint .",
32
+ "lint:fix": "biome lint --fix .",
33
33
  "install-playwright-browsers": "npm run playwright install",
34
34
  "prepublishOnly": "npm run clean && npm run build",
35
- "prettier:diff": "prettier --config ../../.prettierrc \"**/*.{ts,html}\" --list-different",
36
- "prettier": "prettier --config ../../.prettierrc --write \"**/*.{ts,html}\"",
37
35
  "test-server": "npx vite test/ --clearScreen false",
38
36
  "test:playwright": "playwright test",
39
37
  "test:rules": "sg test --skip-snapshot-tests",