@microsoft/fast-html 1.0.0-alpha.1 → 1.0.0-alpha.11
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/README.md +100 -7
- package/dist/dts/components/index.d.ts +1 -0
- package/dist/dts/components/template.d.ts +74 -0
- package/dist/dts/components/utilities.d.ts +125 -0
- package/dist/dts/components/utilities.spec.d.ts +1 -0
- package/dist/dts/debug.d.ts +3 -0
- package/dist/dts/fixtures/attribute/attribute.spec.d.ts +1 -0
- package/dist/dts/fixtures/attribute/main.d.ts +1 -0
- package/dist/dts/fixtures/binding/binding.spec.d.ts +1 -0
- package/dist/dts/fixtures/binding/main.d.ts +1 -0
- package/dist/dts/fixtures/children/children.spec.d.ts +1 -0
- package/dist/dts/fixtures/children/main.d.ts +1 -0
- package/dist/dts/fixtures/dot-syntax/dot-syntax.spec.d.ts +1 -0
- package/dist/dts/fixtures/dot-syntax/main.d.ts +1 -0
- package/dist/dts/fixtures/event/event.spec.d.ts +1 -0
- package/dist/dts/fixtures/event/main.d.ts +1 -0
- package/dist/dts/fixtures/partial/main.d.ts +1 -0
- package/dist/dts/fixtures/partial/partial.spec.d.ts +1 -0
- package/dist/dts/fixtures/ref/main.d.ts +1 -0
- package/dist/dts/fixtures/ref/ref.spec.d.ts +1 -0
- package/dist/dts/fixtures/repeat/main.d.ts +1 -0
- package/dist/dts/fixtures/repeat/repeat.spec.d.ts +1 -0
- package/dist/dts/fixtures/slotted/main.d.ts +1 -0
- package/dist/dts/fixtures/slotted/slotted.spec.d.ts +1 -0
- package/dist/dts/fixtures/when/main.d.ts +1 -0
- package/dist/dts/fixtures/when/when.spec.d.ts +1 -0
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/interfaces.d.ts +7 -0
- package/dist/dts/tsdoc-metadata.json +11 -0
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/template.js +259 -0
- package/dist/esm/components/utilities.js +465 -0
- package/dist/esm/components/utilities.spec.js +277 -0
- package/dist/esm/debug.js +3 -0
- package/dist/esm/fixtures/attribute/attribute.spec.js +23 -0
- package/dist/esm/fixtures/attribute/main.js +26 -0
- package/dist/esm/fixtures/binding/binding.spec.js +23 -0
- package/dist/esm/fixtures/binding/main.js +40 -0
- package/dist/esm/fixtures/children/children.spec.js +37 -0
- package/dist/esm/fixtures/children/main.js +31 -0
- package/dist/esm/fixtures/dot-syntax/dot-syntax.spec.js +9 -0
- package/dist/esm/fixtures/dot-syntax/main.js +23 -0
- package/dist/esm/fixtures/event/event.spec.js +28 -0
- package/dist/esm/fixtures/event/main.js +35 -0
- package/dist/esm/fixtures/partial/main.js +38 -0
- package/dist/esm/fixtures/partial/partial.spec.js +14 -0
- package/dist/esm/fixtures/ref/main.js +21 -0
- package/dist/esm/fixtures/ref/ref.spec.js +13 -0
- package/dist/esm/fixtures/repeat/main.js +27 -0
- package/dist/esm/fixtures/repeat/repeat.spec.js +29 -0
- package/dist/esm/fixtures/slotted/main.js +29 -0
- package/dist/esm/fixtures/slotted/slotted.spec.js +25 -0
- package/dist/esm/fixtures/when/main.js +198 -0
- package/dist/esm/fixtures/when/when.spec.js +82 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/interfaces.js +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/dist/fast-html.api.json +356 -0
- package/dist/fast-html.d.ts +78 -0
- package/dist/fast-html.untrimmed.d.ts +78 -0
- package/package.json +12 -6
- package/rules/attribute-directives.yml +38 -0
- package/rules/call-expression-with-event-argument.yml +41 -0
- package/rules/member-expression.yml +33 -0
- package/rules/tag-function-to-template-literal.yml +16 -0
- package/CHANGELOG.json +0 -26
- package/CHANGELOG.md +0 -14
- package/docs/api-report.api.md +0 -18
- package/webpack.common.config.js +0 -18
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
const openClientSideBinding = "{";
|
|
2
|
+
const closeClientSideBinding = "}";
|
|
3
|
+
const openContentBinding = "{{";
|
|
4
|
+
const closeContentBinding = "}}";
|
|
5
|
+
const openUnescapedBinding = "{{{";
|
|
6
|
+
const closeUnescapedBinding = "}}}";
|
|
7
|
+
const openTagStart = "<f-";
|
|
8
|
+
const tagEnd = ">";
|
|
9
|
+
const closeTagStart = "</f-";
|
|
10
|
+
const attributeDirectivePrefix = "f-";
|
|
11
|
+
const startInnerHTMLDiv = `<div :innerHTML="{{`;
|
|
12
|
+
const startInnerHTMLDivLength = startInnerHTMLDiv.length;
|
|
13
|
+
const endInnerHTMLDiv = `}}"></div>`;
|
|
14
|
+
const endInnerHTMLDivLength = endInnerHTMLDiv.length;
|
|
15
|
+
/**
|
|
16
|
+
* Get the index of the next matching tag
|
|
17
|
+
* @param openingTagStartSlice - The slice starting from the opening tag
|
|
18
|
+
* @param openingTag - The opening tag string
|
|
19
|
+
* @param closingTag - The closing tag
|
|
20
|
+
* @param openingTagStartIndex - The opening tag start index derived from the innerHTML
|
|
21
|
+
* @returns index
|
|
22
|
+
*/
|
|
23
|
+
export function getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, closingTag, openingTagStartIndex) {
|
|
24
|
+
let tagCount = 1;
|
|
25
|
+
let matchingCloseTagIndex = -1;
|
|
26
|
+
const openingTagLength = openingTag.length;
|
|
27
|
+
const closingTagLength = closingTag.length;
|
|
28
|
+
let nextSlice = openingTagStartSlice.slice(openingTagLength);
|
|
29
|
+
let nextOpenTag = nextSlice.indexOf(openingTag);
|
|
30
|
+
let nextCloseTag = nextSlice.indexOf(closingTag);
|
|
31
|
+
let tagOffset = openingTagStartIndex + openingTagLength;
|
|
32
|
+
do {
|
|
33
|
+
// if a closing tag has been found for the last open tag, decrement the tag count
|
|
34
|
+
if (nextOpenTag > nextCloseTag || nextOpenTag === -1) {
|
|
35
|
+
tagCount--;
|
|
36
|
+
if (tagCount === 0) {
|
|
37
|
+
matchingCloseTagIndex = nextCloseTag + tagOffset;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
tagOffset += nextCloseTag + closingTagLength;
|
|
41
|
+
nextSlice = nextSlice.slice(nextCloseTag + closingTagLength);
|
|
42
|
+
nextOpenTag = nextSlice.indexOf(openingTag);
|
|
43
|
+
nextCloseTag = nextSlice.indexOf(closingTag);
|
|
44
|
+
}
|
|
45
|
+
else if (nextOpenTag !== -1) {
|
|
46
|
+
tagCount++;
|
|
47
|
+
tagOffset += nextOpenTag + openingTagLength;
|
|
48
|
+
nextSlice = nextSlice.slice(nextOpenTag + openingTagLength);
|
|
49
|
+
nextOpenTag = nextSlice.indexOf(openingTag);
|
|
50
|
+
nextCloseTag = nextSlice.indexOf(closingTag);
|
|
51
|
+
}
|
|
52
|
+
if (tagCount === 0) {
|
|
53
|
+
matchingCloseTagIndex = nextCloseTag + tagOffset;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
} while (tagCount > 0);
|
|
57
|
+
return matchingCloseTagIndex;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the next directive
|
|
61
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
62
|
+
* @returns DirectiveBehaviorConfig - A configuration object
|
|
63
|
+
*/
|
|
64
|
+
function getNextDirectiveBehavior(innerHTML) {
|
|
65
|
+
const openingTagStartIndex = innerHTML.indexOf(openTagStart);
|
|
66
|
+
const openingTagStartSlice = innerHTML.slice(openingTagStartIndex);
|
|
67
|
+
const openingTagEndIndex = // account for f-when which may include >= or > as operators, but will always include a condition attr
|
|
68
|
+
openingTagStartSlice.indexOf(`"${tagEnd}`) + openingTagStartIndex + 2;
|
|
69
|
+
const directiveTag = innerHTML
|
|
70
|
+
.slice(openingTagStartIndex + 3, openingTagEndIndex - 1)
|
|
71
|
+
.split(" ")[0];
|
|
72
|
+
const directiveValue = getNextDataBindingBehavior(innerHTML);
|
|
73
|
+
const openingTag = `${openTagStart}${directiveTag}`;
|
|
74
|
+
const closingTag = `${closeTagStart}${directiveTag}${tagEnd}`;
|
|
75
|
+
const matchingCloseTagIndex = getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, closingTag, openingTagStartIndex);
|
|
76
|
+
return {
|
|
77
|
+
type: "templateDirective",
|
|
78
|
+
name: directiveTag,
|
|
79
|
+
value: innerHTML.slice(directiveValue.openingEndIndex, directiveValue.closingStartIndex),
|
|
80
|
+
openingTagStartIndex,
|
|
81
|
+
openingTagEndIndex,
|
|
82
|
+
closingTagStartIndex: matchingCloseTagIndex,
|
|
83
|
+
closingTagEndIndex: matchingCloseTagIndex + closingTag.length,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Determine if this binding is an attribute binding
|
|
88
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
89
|
+
* @param openingStartIndex - The index of the binding opening marker
|
|
90
|
+
* @returns boolean
|
|
91
|
+
*/
|
|
92
|
+
function isAttribute(innerHTML, openingStartIndex) {
|
|
93
|
+
return innerHTML.slice(openingStartIndex - 2, openingStartIndex - 1) === "=";
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Determine if this binding is an attribute directive binding
|
|
97
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
98
|
+
* @param openingStartIndex - The index of the binding opening marker
|
|
99
|
+
* @returns boolean
|
|
100
|
+
*/
|
|
101
|
+
function isAttributeDirective(innerHTML, openingStartIndex) {
|
|
102
|
+
const splitHTML = innerHTML.slice(0, openingStartIndex - 2).split(" ");
|
|
103
|
+
return splitHTML[splitHTML.length - 1].startsWith(attributeDirectivePrefix);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the attribute binding config
|
|
107
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
108
|
+
* @param config - The base configuration of the binding
|
|
109
|
+
* @returns AttributeDataBindingBehaviorConfig
|
|
110
|
+
*/
|
|
111
|
+
function getAttributeDataBindingConfig(innerHTML, config) {
|
|
112
|
+
const splitInnerHTML = innerHTML.slice(0, config.openingStartIndex).split(" ");
|
|
113
|
+
const firstCharOfAttribute = splitInnerHTML[splitInnerHTML.length - 1][0];
|
|
114
|
+
const aspect = firstCharOfAttribute === "?" ||
|
|
115
|
+
firstCharOfAttribute === "@" ||
|
|
116
|
+
firstCharOfAttribute === ":"
|
|
117
|
+
? firstCharOfAttribute
|
|
118
|
+
: null;
|
|
119
|
+
return Object.assign(Object.assign({}, config), { subtype: "attribute", aspect });
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get the attribute directive binding config
|
|
123
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
124
|
+
* @param config - The base configuration of the binding
|
|
125
|
+
* @returns AttributeDirectiveBindingBehaviorConfig
|
|
126
|
+
*/
|
|
127
|
+
function getAttributeDirectiveDataBindingConfig(innerHTML, config) {
|
|
128
|
+
const splitInnerHTML = innerHTML.slice(0, config.openingStartIndex).split(" ");
|
|
129
|
+
const lastItem = splitInnerHTML[splitInnerHTML.length - 1];
|
|
130
|
+
const equals = lastItem.indexOf("=");
|
|
131
|
+
const name = lastItem.slice(2, equals);
|
|
132
|
+
return Object.assign(Object.assign({}, config), { subtype: "attributeDirective", name: name });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get the content data binding config
|
|
136
|
+
* @param config - The base configuration of the binding
|
|
137
|
+
* @returns ContentDataBindingBehaviorConfig
|
|
138
|
+
*/
|
|
139
|
+
function getContentDataBindingConfig(config) {
|
|
140
|
+
return Object.assign(Object.assign({}, config), { subtype: "content" });
|
|
141
|
+
}
|
|
142
|
+
function getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML) {
|
|
143
|
+
// {{{}}} binding
|
|
144
|
+
const openingUnescapedStartIndex = innerHTML.indexOf(openUnescapedBinding);
|
|
145
|
+
const closingUnescapedStartIndex = innerHTML.indexOf(closeUnescapedBinding);
|
|
146
|
+
// {{}} binding
|
|
147
|
+
const openingContentStartIndex = innerHTML.indexOf(openContentBinding);
|
|
148
|
+
const closingContentStartIndex = innerHTML.indexOf(closeContentBinding);
|
|
149
|
+
// {} binding
|
|
150
|
+
const openingClientStartIndex = innerHTML.indexOf(openClientSideBinding);
|
|
151
|
+
const closingClientStartIndex = innerHTML.indexOf(closeClientSideBinding);
|
|
152
|
+
if (openingUnescapedStartIndex !== -1 &&
|
|
153
|
+
openingUnescapedStartIndex <= openingContentStartIndex &&
|
|
154
|
+
openingUnescapedStartIndex <= openingClientStartIndex) {
|
|
155
|
+
// is unescaped {{{}}}
|
|
156
|
+
return {
|
|
157
|
+
openingStartIndex: openingUnescapedStartIndex,
|
|
158
|
+
closingStartIndex: closingUnescapedStartIndex,
|
|
159
|
+
bindingType: "unescaped",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
else if (openingContentStartIndex !== -1 &&
|
|
163
|
+
openingContentStartIndex <= openingClientStartIndex) {
|
|
164
|
+
// is default {{}}
|
|
165
|
+
return {
|
|
166
|
+
openingStartIndex: openingContentStartIndex,
|
|
167
|
+
closingStartIndex: closingContentStartIndex,
|
|
168
|
+
bindingType: "default",
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// is client {}
|
|
172
|
+
return {
|
|
173
|
+
openingStartIndex: openingClientStartIndex,
|
|
174
|
+
closingStartIndex: closingClientStartIndex,
|
|
175
|
+
bindingType: "client",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get the next data binding
|
|
180
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
181
|
+
* @returns DataBindingBehaviorConfig - A configuration object
|
|
182
|
+
*/
|
|
183
|
+
function getNextDataBindingBehavior(innerHTML) {
|
|
184
|
+
const { openingStartIndex, closingStartIndex, bindingType } = getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML);
|
|
185
|
+
const bindingLength = bindingType === "client" ? 1 : bindingType === "default" ? 2 : 3;
|
|
186
|
+
const partialConfig = {
|
|
187
|
+
type: "dataBinding",
|
|
188
|
+
bindingType,
|
|
189
|
+
openingStartIndex,
|
|
190
|
+
openingEndIndex: openingStartIndex + bindingLength,
|
|
191
|
+
closingStartIndex,
|
|
192
|
+
closingEndIndex: closingStartIndex + bindingLength,
|
|
193
|
+
};
|
|
194
|
+
return isAttributeDirective(innerHTML, openingStartIndex)
|
|
195
|
+
? getAttributeDirectiveDataBindingConfig(innerHTML, partialConfig)
|
|
196
|
+
: isAttribute(innerHTML, openingStartIndex)
|
|
197
|
+
? getAttributeDataBindingConfig(innerHTML, partialConfig)
|
|
198
|
+
: getContentDataBindingConfig(partialConfig);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get the next behavior
|
|
202
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
203
|
+
* @returns DataBindingBehaviorConfig | DirectiveBehaviorConfig | null - A configuration object or null
|
|
204
|
+
*/
|
|
205
|
+
export function getNextBehavior(innerHTML) {
|
|
206
|
+
const dataBindingOpen = innerHTML.indexOf(openClientSideBinding); // client side binding will capture all bindings starting with "{"
|
|
207
|
+
const directiveBindingOpen = innerHTML.indexOf(openTagStart);
|
|
208
|
+
if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (directiveBindingOpen !== -1 && dataBindingOpen > directiveBindingOpen) {
|
|
212
|
+
return getNextDirectiveBehavior(innerHTML);
|
|
213
|
+
}
|
|
214
|
+
return getNextDataBindingBehavior(innerHTML);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Gets all the partials with their IDs
|
|
218
|
+
* @param innerHTML - The innerHTML string to evaluate
|
|
219
|
+
* @param offset - The index offset from the innerHTML
|
|
220
|
+
* @param partials - The partials found
|
|
221
|
+
* @returns {[key: string]: PartialTemplateConfig}
|
|
222
|
+
*/
|
|
223
|
+
export function getAllPartials(innerHTML, offset = 0, partials = {}) {
|
|
224
|
+
const openingTag = `${openTagStart}partial`;
|
|
225
|
+
const openingTagStartIndex = innerHTML.indexOf(openingTag);
|
|
226
|
+
if (openingTagStartIndex >= 0) {
|
|
227
|
+
const openingTagStartSlice = innerHTML.slice(openingTagStartIndex);
|
|
228
|
+
const closingTag = `${closeTagStart}partial${tagEnd}`;
|
|
229
|
+
const closingTagLength = closingTag.length;
|
|
230
|
+
const matchingCloseTagIndex = getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, closingTag, openingTagStartIndex) + closingTagLength;
|
|
231
|
+
const startId = openingTagStartIndex + ' id="'.length + openingTag.length;
|
|
232
|
+
const endId = innerHTML.slice(startId).indexOf('"') + startId;
|
|
233
|
+
const id = innerHTML.slice(startId, endId);
|
|
234
|
+
const openingTagEndIndex = openingTagStartSlice.indexOf(tagEnd) + 1 + openingTagStartIndex;
|
|
235
|
+
const closingTagStartIndex = matchingCloseTagIndex - closingTagLength;
|
|
236
|
+
partials[id] = {
|
|
237
|
+
innerHTML: innerHTML.slice(openingTagEndIndex, closingTagStartIndex),
|
|
238
|
+
startIndex: openingTagEndIndex + offset,
|
|
239
|
+
endIndex: closingTagStartIndex + offset,
|
|
240
|
+
};
|
|
241
|
+
offset += matchingCloseTagIndex;
|
|
242
|
+
return getAllPartials(innerHTML.slice(matchingCloseTagIndex), offset, partials);
|
|
243
|
+
}
|
|
244
|
+
return partials;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Create a function to resolve a value from an object using a path with dot syntax.
|
|
248
|
+
* e.g. "foo.bar"
|
|
249
|
+
* @param path - The dot syntax path to an objects property.
|
|
250
|
+
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
251
|
+
* @returns A function to access the value from a given path.
|
|
252
|
+
*/
|
|
253
|
+
export function pathResolver(path, self = false) {
|
|
254
|
+
let splitPath = path.split(".");
|
|
255
|
+
const usesContext = path.startsWith("../");
|
|
256
|
+
if (self && !usesContext) {
|
|
257
|
+
if (splitPath.length > 1) {
|
|
258
|
+
splitPath = splitPath.slice(1);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
return (accessibleObject) => {
|
|
262
|
+
return accessibleObject;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (splitPath.length === 1 && !usesContext) {
|
|
267
|
+
return (accessibleObject) => {
|
|
268
|
+
return accessibleObject === null || accessibleObject === void 0 ? void 0 : accessibleObject[splitPath[0]];
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return (accessibleObject, context) => {
|
|
272
|
+
if (usesContext) {
|
|
273
|
+
splitPath = [];
|
|
274
|
+
path.split("../").forEach(pathItem => {
|
|
275
|
+
if (pathItem === "") {
|
|
276
|
+
splitPath.unshift("parent");
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
splitPath.push(...pathItem.split("."));
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return splitPath.reduce((previousAccessors, pathItem) => {
|
|
283
|
+
return previousAccessors === null || previousAccessors === void 0 ? void 0 : previousAccessors[pathItem];
|
|
284
|
+
}, context);
|
|
285
|
+
}
|
|
286
|
+
return splitPath.reduce((previousAccessors, pathItem) => {
|
|
287
|
+
return previousAccessors === null || previousAccessors === void 0 ? void 0 : previousAccessors[pathItem];
|
|
288
|
+
}, accessibleObject);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Determine if the operand is a value (boolean, number, string) or an accessor.
|
|
293
|
+
* @param operand
|
|
294
|
+
*/
|
|
295
|
+
function isOperandValue(operand) {
|
|
296
|
+
try {
|
|
297
|
+
operand = operand.replaceAll("'", '"');
|
|
298
|
+
const value = JSON.parse(operand);
|
|
299
|
+
return {
|
|
300
|
+
value,
|
|
301
|
+
isValue: true,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
return {
|
|
306
|
+
value: operand,
|
|
307
|
+
isValue: false,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Gets the expression chain as a configuration object
|
|
313
|
+
* @param value - The binding string value
|
|
314
|
+
* @returns - A configuration object containing information about the expression
|
|
315
|
+
*/
|
|
316
|
+
export function getExpressionChain(value) {
|
|
317
|
+
const split = value.split(" ");
|
|
318
|
+
let expressionString = "";
|
|
319
|
+
let chainedExpression;
|
|
320
|
+
// split expressions by chaining operators
|
|
321
|
+
split.forEach((splitItem, index) => {
|
|
322
|
+
if (splitItem === "&&" || splitItem === "||" || splitItem === "&&") {
|
|
323
|
+
chainedExpression = {
|
|
324
|
+
expression: getExpression(expressionString),
|
|
325
|
+
next: Object.assign({ operator: splitItem }, getExpressionChain(split.slice(index + 1).join(" "))),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
expressionString = `${expressionString ? `${expressionString} ` : ""}${splitItem}`;
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
if (chainedExpression) {
|
|
333
|
+
return chainedExpression;
|
|
334
|
+
}
|
|
335
|
+
if (expressionString) {
|
|
336
|
+
return {
|
|
337
|
+
expression: getExpression(expressionString),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return void 0;
|
|
341
|
+
}
|
|
342
|
+
function getExpression(value) {
|
|
343
|
+
if (value[0] === "!") {
|
|
344
|
+
return {
|
|
345
|
+
operator: "!",
|
|
346
|
+
left: value.slice(1),
|
|
347
|
+
right: null,
|
|
348
|
+
rightIsValue: null,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const split = value.split(" ");
|
|
352
|
+
if (split.length === 3) {
|
|
353
|
+
const operator = split[1];
|
|
354
|
+
const { value, isValue } = isOperandValue(split[2]);
|
|
355
|
+
return {
|
|
356
|
+
operator,
|
|
357
|
+
left: split[0],
|
|
358
|
+
right: isValue ? value : split[2],
|
|
359
|
+
rightIsValue: isValue,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
operator: "access",
|
|
364
|
+
left: value,
|
|
365
|
+
right: null,
|
|
366
|
+
rightIsValue: null,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Resolve a single expression
|
|
371
|
+
* @param x - The context
|
|
372
|
+
* @param c - The parent context
|
|
373
|
+
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
374
|
+
* @param expression - The expression being evaluated
|
|
375
|
+
* @returns - A function resolving the binding for this expression
|
|
376
|
+
*/
|
|
377
|
+
function resolveExpression(x, c, self, expression) {
|
|
378
|
+
const { operator, left, right, rightIsValue } = expression;
|
|
379
|
+
switch (operator) {
|
|
380
|
+
case "!":
|
|
381
|
+
return !pathResolver(left, self)(x, c);
|
|
382
|
+
case "==":
|
|
383
|
+
return (pathResolver(left, self)(x, c) ==
|
|
384
|
+
(rightIsValue ? right : pathResolver(right, self)(x, c)));
|
|
385
|
+
case "!=":
|
|
386
|
+
return (pathResolver(left, self)(x, c) !=
|
|
387
|
+
(rightIsValue ? right : pathResolver(right, self)(x, c)));
|
|
388
|
+
case ">=":
|
|
389
|
+
return (pathResolver(left, self)(x, c) >=
|
|
390
|
+
(rightIsValue ? right : pathResolver(right, self)(x, c)));
|
|
391
|
+
case ">":
|
|
392
|
+
return (pathResolver(left, self)(x, c) >
|
|
393
|
+
(rightIsValue ? right : pathResolver(right, self)(x, c)));
|
|
394
|
+
case "<=":
|
|
395
|
+
return (pathResolver(left, self)(x, c) <=
|
|
396
|
+
(rightIsValue ? right : pathResolver(right, self)(x, c)));
|
|
397
|
+
case "<":
|
|
398
|
+
return (pathResolver(left, self)(x, c) <
|
|
399
|
+
(rightIsValue ? right : pathResolver(right, self)(x, c)));
|
|
400
|
+
default:
|
|
401
|
+
return pathResolver(left, self)(x, c);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Resolve a chained expression
|
|
406
|
+
* @param x - The context
|
|
407
|
+
* @param c - The parent context
|
|
408
|
+
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
409
|
+
* @param expression - The expression being evaluated
|
|
410
|
+
* @param next - The next expression to be chained
|
|
411
|
+
* @returns - A resolved return value for a binding
|
|
412
|
+
*/
|
|
413
|
+
function resolveChainedExpression(x, c, self, expression, next) {
|
|
414
|
+
if (next) {
|
|
415
|
+
switch (next.operator) {
|
|
416
|
+
case "&&":
|
|
417
|
+
case "&&":
|
|
418
|
+
return (resolveExpression(x, c, self, expression) &&
|
|
419
|
+
resolveChainedExpression(x, c, self, next.expression, next.next));
|
|
420
|
+
case "||":
|
|
421
|
+
return (resolveExpression(x, c, self, expression) ||
|
|
422
|
+
resolveChainedExpression(x, c, self, next.expression, next.next));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return resolveExpression(x, c, self, expression);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* This is the transform utility for rationalizing declarative HTML syntax
|
|
429
|
+
* with bindings in the ViewTemplate
|
|
430
|
+
* @param innerHTML The innerHTML to transform
|
|
431
|
+
* @param index The index to start the current slice of HTML to evaluate
|
|
432
|
+
*/
|
|
433
|
+
export function transformInnerHTML(innerHTML, index = 0) {
|
|
434
|
+
const sliceToEvaluate = innerHTML.slice(index);
|
|
435
|
+
const nextBinding = getNextBehavior(sliceToEvaluate);
|
|
436
|
+
let transformedInnerHTML = innerHTML;
|
|
437
|
+
if (nextBinding && nextBinding.type === "dataBinding") {
|
|
438
|
+
if (nextBinding.bindingType === "unescaped") {
|
|
439
|
+
transformedInnerHTML = `${innerHTML.slice(0, index)}${sliceToEvaluate.slice(0, nextBinding.openingStartIndex)}${startInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.openingStartIndex + 3, nextBinding.closingStartIndex)}${endInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.closingStartIndex + 3)}`;
|
|
440
|
+
return transformInnerHTML(transformedInnerHTML, index +
|
|
441
|
+
startInnerHTMLDivLength +
|
|
442
|
+
endInnerHTMLDivLength +
|
|
443
|
+
nextBinding.closingStartIndex -
|
|
444
|
+
3);
|
|
445
|
+
}
|
|
446
|
+
else if (nextBinding.bindingType === "client") {
|
|
447
|
+
return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
|
|
448
|
+
}
|
|
449
|
+
return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
|
|
450
|
+
}
|
|
451
|
+
else if (nextBinding) {
|
|
452
|
+
return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingTagEndIndex);
|
|
453
|
+
}
|
|
454
|
+
return transformedInnerHTML;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Resolves f-when
|
|
458
|
+
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
459
|
+
* @param chainedExpression - The chained expression which includes the expression and the next expression
|
|
460
|
+
* if there is another in the chain
|
|
461
|
+
* @returns - A binding that resolves the chained expression logic
|
|
462
|
+
*/
|
|
463
|
+
export function resolveWhen(self, { expression, next }) {
|
|
464
|
+
return (x, c) => resolveChainedExpression(x, c, self, expression, next);
|
|
465
|
+
}
|