@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.
- package/dist/dts/components/syntax.d.ts +4 -0
- package/dist/dts/components/utilities.d.ts +3 -3
- package/dist/esm/components/syntax.js +17 -0
- package/dist/esm/components/template.js +51 -33
- package/dist/esm/components/utilities.js +38 -32
- package/dist/esm/components/utilities.spec.js +55 -49
- package/package.json +4 -6
|
@@ -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
|
|
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
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
4
|
-
const
|
|
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
|
|
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(`"
|
|
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(
|
|
191
|
-
const closingUnescapedStartIndex = innerHTML.indexOf(
|
|
187
|
+
const openingUnescapedStartIndex = innerHTML.indexOf(unescapedOpenExpression);
|
|
188
|
+
const closingUnescapedStartIndex = innerHTML.indexOf(unescapedCloseExpression);
|
|
192
189
|
// {{}} binding
|
|
193
|
-
const openingContentStartIndex = innerHTML.indexOf(
|
|
194
|
-
const closingContentStartIndex = innerHTML.indexOf(
|
|
190
|
+
const openingContentStartIndex = innerHTML.indexOf(openExpression);
|
|
191
|
+
const closingContentStartIndex = innerHTML.indexOf(closeExpression);
|
|
195
192
|
// {} binding
|
|
196
|
-
const openingClientStartIndex = innerHTML.indexOf(
|
|
197
|
-
const closingClientStartIndex = innerHTML.indexOf(
|
|
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(
|
|
257
|
-
const
|
|
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
|
|
324
|
-
if (splitPath[0] ===
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
49
|
+
const innerHTML1 = '<input type="{type}">';
|
|
50
50
|
const templateResult1 = getNextBehavior(innerHTML1);
|
|
51
51
|
expect(templateResult1).toBeNull();
|
|
52
|
-
const innerHTML2 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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("
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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": "
|
|
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
|
-
"
|
|
32
|
-
"
|
|
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",
|