@microsoft/fast-html 1.0.0-alpha.39 → 1.0.0-alpha.40

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.
@@ -72,7 +72,7 @@ export declare function getIndexOfNextMatchingTag(openingTagStartSlice: string,
72
72
  * @param innerHTML - The innerHTML string to evaluate
73
73
  * @returns DataBindingBehaviorConfig | DirectiveBehaviorConfig | null - A configuration object or null
74
74
  */
75
- export declare function getNextBehavior(innerHTML: string): DataBindingBehaviorConfig | TemplateDirectiveBehaviorConfig | null;
75
+ export declare function getNextBehavior(innerHTML: string, offset?: number): DataBindingBehaviorConfig | TemplateDirectiveBehaviorConfig | null;
76
76
  /**
77
77
  * Create a function to resolve a value from an object using a path with dot syntax.
78
78
  * e.g. "foo.bar"
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.47.0"
8
+ "packageVersion": "7.57.7"
9
9
  }
10
10
  ]
11
11
  }
@@ -246,16 +246,67 @@ function getNextDataBindingBehavior(innerHTML) {
246
246
  * @param innerHTML - The innerHTML string to evaluate
247
247
  * @returns DataBindingBehaviorConfig | DirectiveBehaviorConfig | null - A configuration object or null
248
248
  */
249
- export function getNextBehavior(innerHTML) {
250
- const dataBindingOpen = innerHTML.indexOf(openClientSideBinding); // client side binding will capture all bindings starting with "{"
251
- const directiveBindingOpen = innerHTML.indexOf(openTagStart);
252
- if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
253
- return null;
254
- }
255
- if (directiveBindingOpen !== -1 && dataBindingOpen > directiveBindingOpen) {
256
- return getNextDirectiveBehavior(innerHTML);
249
+ export function getNextBehavior(innerHTML, offset = 0) {
250
+ // eslint-disable-next-line no-constant-condition
251
+ while (true) {
252
+ const currentSlice = innerHTML.slice(offset);
253
+ // client side binding will capture all bindings starting with "{"
254
+ const dataBindingOpen = currentSlice.indexOf(openClientSideBinding);
255
+ const directiveBindingOpen = currentSlice.indexOf(openTagStart);
256
+ const nextDataBindingBehavior = getNextDataBindingBehavior(currentSlice);
257
+ if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
258
+ return null;
259
+ }
260
+ if (dataBindingOpen !== -1 &&
261
+ nextDataBindingBehavior.bindingType === "client" &&
262
+ !isLegitimateClientSideBinding(nextDataBindingBehavior)) {
263
+ offset = nextDataBindingBehavior.closingEndIndex + offset;
264
+ continue;
265
+ }
266
+ if (directiveBindingOpen !== -1 &&
267
+ (dataBindingOpen === -1 || dataBindingOpen > directiveBindingOpen)) {
268
+ return offsetDirective(getNextDirectiveBehavior(currentSlice), offset);
269
+ }
270
+ return offsetDataBinding(nextDataBindingBehavior, offset);
257
271
  }
258
- return getNextDataBindingBehavior(innerHTML);
272
+ }
273
+ /**
274
+ * Apply an offset to a data binding
275
+ * @param config DataBindingBehaviorConfig
276
+ * @param offset number
277
+ * @returns DataBindingBehaviorConfig
278
+ */
279
+ function offsetDataBinding(config, offset) {
280
+ config.openingStartIndex = config.openingStartIndex + offset;
281
+ config.openingEndIndex = config.openingEndIndex + offset;
282
+ config.closingStartIndex = config.closingStartIndex + offset;
283
+ config.closingEndIndex = config.closingEndIndex + offset;
284
+ return config;
285
+ }
286
+ /**
287
+ * Apply an offset to a directive
288
+ * @param config TemplateDirectiveBehaviorConfig
289
+ * @param offset number
290
+ * @returns TemplateDirectiveBehaviorConfig
291
+ */
292
+ function offsetDirective(config, offset) {
293
+ config.openingTagStartIndex = config.openingTagStartIndex + offset;
294
+ config.openingTagEndIndex = config.openingTagEndIndex + offset;
295
+ config.closingTagStartIndex = config.closingTagStartIndex + offset;
296
+ config.closingTagEndIndex = config.closingTagEndIndex + offset;
297
+ return config;
298
+ }
299
+ /**
300
+ * Determine if this client side binding is legitimate.
301
+ * Single-brace (client) bindings are only valid for events, properties, and attribute directives.
302
+ * Checking for this prevents CSS/JS curly braces from being misinterpreted as bindings.
303
+ * @param result
304
+ * @returns
305
+ */
306
+ function isLegitimateClientSideBinding(result) {
307
+ return ((result.subtype === "attribute" &&
308
+ (result.aspect === "@" || result.aspect === ":")) ||
309
+ result.subtype === "attributeDirective");
259
310
  }
260
311
  /**
261
312
  * Create a function to resolve a value from an object using a path with dot syntax.
@@ -40,6 +40,48 @@ test.describe("utilities", async () => {
40
40
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(29);
41
41
  expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingEndIndex).toEqual(30);
42
42
  });
43
+ test("skip single-brace content bindings (e.g. CSS braces)", async () => {
44
+ const innerHTML = "<style>.foo { color: red }</style>";
45
+ const templateResult = getNextBehavior(innerHTML);
46
+ expect(templateResult).toBeNull();
47
+ });
48
+ test("skip single-brace non-event, non-property attribute bindings", async () => {
49
+ const innerHTML1 = "<input type=\"{type}\">";
50
+ const templateResult1 = getNextBehavior(innerHTML1);
51
+ expect(templateResult1).toBeNull();
52
+ const innerHTML2 = "<input ?disabled=\"{disabled}\">";
53
+ const templateResult2 = getNextBehavior(innerHTML2);
54
+ expect(templateResult2).toBeNull();
55
+ });
56
+ test("find double-brace binding after skipped single-brace content", async () => {
57
+ const innerHTML = "<style>.foo { color: red } .bar { color: blue }</style><span>{{name}}</span>";
58
+ const templateResult = getNextBehavior(innerHTML);
59
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
60
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("content");
61
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("default");
62
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.openingStartIndex).toEqual(61);
63
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.closingStartIndex).toEqual(67);
64
+ });
65
+ test("find event binding after skipped single-brace content", async () => {
66
+ const innerHTML = "<style>.foo { color: red }</style><button @click=\"{handler()}\">";
67
+ const templateResult = getNextBehavior(innerHTML);
68
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
69
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
70
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.aspect).toEqual("@");
71
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("client");
72
+ });
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}\">";
75
+ const templateResult = getNextBehavior(innerHTML);
76
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.type).toEqual("dataBinding");
77
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.subtype).toEqual("attribute");
78
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.aspect).toEqual(":");
79
+ expect(templateResult === null || templateResult === void 0 ? void 0 : templateResult.bindingType).toEqual("client");
80
+ });
81
+ test("ensure if there are expected missing {} this does not cause parsing issues", async () => {
82
+ const innerHTML = "<f-when value=\"missing\">";
83
+ expect(getNextBehavior(innerHTML)).toBeTruthy();
84
+ });
43
85
  });
44
86
  test.describe("templates", async () => {
45
87
  test("when directive", async () => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "metadata": {
3
3
  "toolPackage": "@microsoft/api-extractor",
4
- "toolVersion": "7.47.0",
4
+ "toolVersion": "7.57.7",
5
5
  "schemaVersion": 1011,
6
6
  "oldestForwardsCompatibleVersion": 1001,
7
7
  "tsdocConfig": {
@@ -114,6 +114,22 @@
114
114
  "tagName": "@virtual",
115
115
  "syntaxKind": "modifier"
116
116
  },
117
+ {
118
+ "tagName": "@jsx",
119
+ "syntaxKind": "block"
120
+ },
121
+ {
122
+ "tagName": "@jsxRuntime",
123
+ "syntaxKind": "block"
124
+ },
125
+ {
126
+ "tagName": "@jsxFrag",
127
+ "syntaxKind": "block"
128
+ },
129
+ {
130
+ "tagName": "@jsxImportSource",
131
+ "syntaxKind": "block"
132
+ },
117
133
  {
118
134
  "tagName": "@betaDocumentation",
119
135
  "syntaxKind": "modifier"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/fast-html",
3
- "version": "1.0.0-alpha.39",
3
+ "version": "1.0.0-alpha.40",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Microsoft",