@microsoft/fast-html 1.0.0-alpha.2 → 1.0.0-alpha.20
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 +129 -18
- package/dist/dts/components/element.d.ts +10 -0
- package/dist/dts/components/index.d.ts +2 -0
- package/dist/dts/components/observer-map.d.ts +26 -0
- package/dist/dts/components/schema.d.ts +134 -0
- package/dist/dts/components/template.d.ts +35 -6
- package/dist/dts/components/utilities.d.ts +92 -19
- package/dist/dts/fixtures/observer-map/main.d.ts +1 -0
- package/dist/dts/fixtures/observer-map/observer-map.spec.d.ts +1 -0
- package/dist/dts/index.d.ts +1 -1
- package/dist/esm/components/element.js +27 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/components/observer-map.js +49 -0
- package/dist/esm/components/observer-map.spec.js +19 -0
- package/dist/esm/components/schema.js +215 -0
- package/dist/esm/components/schema.spec.js +257 -0
- package/dist/esm/components/template.js +160 -99
- package/dist/esm/components/utilities.js +553 -43
- package/dist/esm/components/utilities.spec.js +246 -44
- package/dist/esm/fixtures/attribute/main.js +3 -2
- package/dist/esm/fixtures/binding/binding.spec.js +6 -0
- package/dist/esm/fixtures/binding/main.js +13 -2
- package/dist/esm/fixtures/children/children.spec.js +4 -0
- package/dist/esm/fixtures/children/main.js +3 -2
- package/dist/esm/fixtures/dot-syntax/dot-syntax.spec.js +109 -2
- package/dist/esm/fixtures/dot-syntax/main.js +30 -4
- package/dist/esm/fixtures/event/event.spec.js +28 -5
- package/dist/esm/fixtures/event/main.js +21 -5
- package/dist/esm/fixtures/observer-map/main.js +304 -0
- package/dist/esm/fixtures/observer-map/observer-map.spec.js +174 -0
- package/dist/esm/fixtures/ref/main.js +3 -2
- package/dist/esm/fixtures/ref/ref.spec.js +2 -6
- package/dist/esm/fixtures/repeat/main.js +27 -2
- package/dist/esm/fixtures/repeat/repeat.spec.js +16 -6
- package/dist/esm/fixtures/slotted/main.js +15 -4
- package/dist/esm/fixtures/slotted/slotted.spec.js +18 -19
- package/dist/esm/fixtures/when/main.js +139 -2
- package/dist/esm/fixtures/when/when.spec.js +64 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/fast-html.api.json +279 -0
- package/dist/fast-html.d.ts +215 -5
- package/dist/fast-html.untrimmed.d.ts +215 -5
- package/package.json +12 -9
- 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/dist/esm/fixtures/partial/main.js +0 -31
- package/dist/esm/fixtures/partial/partial.spec.js +0 -14
- /package/dist/dts/{fixtures/partial/main.d.ts → components/observer-map.spec.d.ts} +0 -0
- /package/dist/dts/{fixtures/partial/partial.spec.d.ts → components/schema.spec.d.ts} +0 -0
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Observable } from "@microsoft/fast-element/observable.js";
|
|
2
|
+
import { defsPropertyName, refPropertyName, } from "./schema.js";
|
|
3
|
+
const openClientSideBinding = "{";
|
|
4
|
+
const closeClientSideBinding = "}";
|
|
5
|
+
const openContentBinding = "{{";
|
|
6
|
+
const closeContentBinding = "}}";
|
|
7
|
+
const openUnescapedBinding = "{{{";
|
|
8
|
+
const closeUnescapedBinding = "}}}";
|
|
3
9
|
const openTagStart = "<f-";
|
|
4
10
|
const tagEnd = ">";
|
|
5
11
|
const closeTagStart = "</f-";
|
|
6
12
|
const attributeDirectivePrefix = "f-";
|
|
13
|
+
const startInnerHTMLDiv = `<div :innerHTML="{{`;
|
|
14
|
+
const startInnerHTMLDivLength = startInnerHTMLDiv.length;
|
|
15
|
+
const endInnerHTMLDiv = `}}"></div>`;
|
|
16
|
+
const endInnerHTMLDivLength = endInnerHTMLDiv.length;
|
|
7
17
|
/**
|
|
8
18
|
* Get the index of the next matching tag
|
|
9
19
|
* @param openingTagStartSlice - The slice starting from the opening tag
|
|
@@ -56,7 +66,8 @@ export function getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, clos
|
|
|
56
66
|
function getNextDirectiveBehavior(innerHTML) {
|
|
57
67
|
const openingTagStartIndex = innerHTML.indexOf(openTagStart);
|
|
58
68
|
const openingTagStartSlice = innerHTML.slice(openingTagStartIndex);
|
|
59
|
-
const openingTagEndIndex =
|
|
69
|
+
const openingTagEndIndex = // account for f-when which may include >= or > as operators, but will always include a condition attr
|
|
70
|
+
openingTagStartSlice.indexOf(`"${tagEnd}`) + openingTagStartIndex + 2;
|
|
60
71
|
const directiveTag = innerHTML
|
|
61
72
|
.slice(openingTagStartIndex + 3, openingTagEndIndex - 1)
|
|
62
73
|
.split(" ")[0];
|
|
@@ -130,20 +141,62 @@ function getAttributeDirectiveDataBindingConfig(innerHTML, config) {
|
|
|
130
141
|
function getContentDataBindingConfig(config) {
|
|
131
142
|
return Object.assign(Object.assign({}, config), { subtype: "content" });
|
|
132
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Finds the next data binding in innerHTML and determines its type and indices
|
|
146
|
+
* @param innerHTML - The innerHTML string to search for data bindings
|
|
147
|
+
* @returns NextDataBindingBehaviorConfig containing the binding type and start indices
|
|
148
|
+
*/
|
|
149
|
+
function getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML) {
|
|
150
|
+
// {{{}}} binding
|
|
151
|
+
const openingUnescapedStartIndex = innerHTML.indexOf(openUnescapedBinding);
|
|
152
|
+
const closingUnescapedStartIndex = innerHTML.indexOf(closeUnescapedBinding);
|
|
153
|
+
// {{}} binding
|
|
154
|
+
const openingContentStartIndex = innerHTML.indexOf(openContentBinding);
|
|
155
|
+
const closingContentStartIndex = innerHTML.indexOf(closeContentBinding);
|
|
156
|
+
// {} binding
|
|
157
|
+
const openingClientStartIndex = innerHTML.indexOf(openClientSideBinding);
|
|
158
|
+
const closingClientStartIndex = innerHTML.indexOf(closeClientSideBinding);
|
|
159
|
+
if (openingUnescapedStartIndex !== -1 &&
|
|
160
|
+
openingUnescapedStartIndex <= openingContentStartIndex &&
|
|
161
|
+
openingUnescapedStartIndex <= openingClientStartIndex) {
|
|
162
|
+
// is unescaped {{{}}}
|
|
163
|
+
return {
|
|
164
|
+
openingStartIndex: openingUnescapedStartIndex,
|
|
165
|
+
closingStartIndex: closingUnescapedStartIndex,
|
|
166
|
+
bindingType: "unescaped",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
else if (openingContentStartIndex !== -1 &&
|
|
170
|
+
openingContentStartIndex <= openingClientStartIndex) {
|
|
171
|
+
// is default {{}}
|
|
172
|
+
return {
|
|
173
|
+
openingStartIndex: openingContentStartIndex,
|
|
174
|
+
closingStartIndex: closingContentStartIndex,
|
|
175
|
+
bindingType: "default",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// is client {}
|
|
179
|
+
return {
|
|
180
|
+
openingStartIndex: openingClientStartIndex,
|
|
181
|
+
closingStartIndex: closingClientStartIndex,
|
|
182
|
+
bindingType: "client",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
133
185
|
/**
|
|
134
186
|
* Get the next data binding
|
|
135
187
|
* @param innerHTML - The innerHTML string to evaluate
|
|
136
188
|
* @returns DataBindingBehaviorConfig - A configuration object
|
|
137
189
|
*/
|
|
138
190
|
function getNextDataBindingBehavior(innerHTML) {
|
|
139
|
-
const openingStartIndex = innerHTML
|
|
140
|
-
const
|
|
191
|
+
const { openingStartIndex, closingStartIndex, bindingType } = getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML);
|
|
192
|
+
const bindingLength = bindingType === "client" ? 1 : bindingType === "default" ? 2 : 3;
|
|
141
193
|
const partialConfig = {
|
|
142
194
|
type: "dataBinding",
|
|
195
|
+
bindingType,
|
|
143
196
|
openingStartIndex,
|
|
144
|
-
openingEndIndex: openingStartIndex +
|
|
197
|
+
openingEndIndex: openingStartIndex + bindingLength,
|
|
145
198
|
closingStartIndex,
|
|
146
|
-
closingEndIndex: closingStartIndex +
|
|
199
|
+
closingEndIndex: closingStartIndex + bindingLength,
|
|
147
200
|
};
|
|
148
201
|
return isAttributeDirective(innerHTML, openingStartIndex)
|
|
149
202
|
? getAttributeDirectiveDataBindingConfig(innerHTML, partialConfig)
|
|
@@ -157,7 +210,7 @@ function getNextDataBindingBehavior(innerHTML) {
|
|
|
157
210
|
* @returns DataBindingBehaviorConfig | DirectiveBehaviorConfig | null - A configuration object or null
|
|
158
211
|
*/
|
|
159
212
|
export function getNextBehavior(innerHTML) {
|
|
160
|
-
const dataBindingOpen = innerHTML.indexOf(
|
|
213
|
+
const dataBindingOpen = innerHTML.indexOf(openClientSideBinding); // client side binding will capture all bindings starting with "{"
|
|
161
214
|
const directiveBindingOpen = innerHTML.indexOf(openTagStart);
|
|
162
215
|
if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
|
|
163
216
|
return null;
|
|
@@ -167,36 +220,6 @@ export function getNextBehavior(innerHTML) {
|
|
|
167
220
|
}
|
|
168
221
|
return getNextDataBindingBehavior(innerHTML);
|
|
169
222
|
}
|
|
170
|
-
/**
|
|
171
|
-
* Gets all the partials with their IDs
|
|
172
|
-
* @param innerHTML - The innerHTML string to evaluate
|
|
173
|
-
* @param offset - The index offset from the innerHTML
|
|
174
|
-
* @param partials - The partials found
|
|
175
|
-
* @returns {[key: string]: PartialTemplateConfig}
|
|
176
|
-
*/
|
|
177
|
-
export function getAllPartials(innerHTML, offset = 0, partials = {}) {
|
|
178
|
-
const openingTag = `${openTagStart}partial`;
|
|
179
|
-
const openingTagStartIndex = innerHTML.indexOf(openingTag);
|
|
180
|
-
if (openingTagStartIndex >= 0) {
|
|
181
|
-
const openingTagStartSlice = innerHTML.slice(openingTagStartIndex);
|
|
182
|
-
const closingTag = `${closeTagStart}partial${tagEnd}`;
|
|
183
|
-
const closingTagLength = closingTag.length;
|
|
184
|
-
const matchingCloseTagIndex = getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, closingTag, openingTagStartIndex) + closingTagLength;
|
|
185
|
-
const startId = openingTagStartIndex + ' id="'.length + openingTag.length;
|
|
186
|
-
const endId = innerHTML.slice(startId).indexOf('"') + startId;
|
|
187
|
-
const id = innerHTML.slice(startId, endId);
|
|
188
|
-
const openingTagEndIndex = openingTagStartSlice.indexOf(">") + 1 + openingTagStartIndex;
|
|
189
|
-
const closingTagStartIndex = matchingCloseTagIndex - closingTagLength;
|
|
190
|
-
partials[id] = {
|
|
191
|
-
innerHTML: innerHTML.slice(openingTagEndIndex, closingTagStartIndex),
|
|
192
|
-
startIndex: openingTagEndIndex + offset,
|
|
193
|
-
endIndex: closingTagStartIndex + offset,
|
|
194
|
-
};
|
|
195
|
-
offset += matchingCloseTagIndex;
|
|
196
|
-
return getAllPartials(innerHTML.slice(matchingCloseTagIndex), offset, partials);
|
|
197
|
-
}
|
|
198
|
-
return partials;
|
|
199
|
-
}
|
|
200
223
|
/**
|
|
201
224
|
* Create a function to resolve a value from an object using a path with dot syntax.
|
|
202
225
|
* e.g. "foo.bar"
|
|
@@ -204,9 +227,38 @@ export function getAllPartials(innerHTML, offset = 0, partials = {}) {
|
|
|
204
227
|
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
205
228
|
* @returns A function to access the value from a given path.
|
|
206
229
|
*/
|
|
207
|
-
export function pathResolver(path,
|
|
230
|
+
export function pathResolver(path, contextPath, level, rootSchema) {
|
|
231
|
+
var _a, _b;
|
|
208
232
|
let splitPath = path.split(".");
|
|
209
|
-
|
|
233
|
+
let levelCount = level;
|
|
234
|
+
let self = splitPath[0] === contextPath;
|
|
235
|
+
const parentContexts = [];
|
|
236
|
+
if (level > 0 &&
|
|
237
|
+
((_b = (_a = rootSchema === null || rootSchema === void 0 ? void 0 : rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[contextPath]) === null || _b === void 0 ? void 0 : _b.$fast_context) ===
|
|
238
|
+
splitPath.at(-1)) {
|
|
239
|
+
self = true;
|
|
240
|
+
}
|
|
241
|
+
while (levelCount > 0 && !self) {
|
|
242
|
+
if (levelCount !== 1) {
|
|
243
|
+
parentContexts.push("parentContext");
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
parentContexts.push("parent");
|
|
247
|
+
}
|
|
248
|
+
levelCount--;
|
|
249
|
+
}
|
|
250
|
+
splitPath = [...parentContexts, ...splitPath];
|
|
251
|
+
return pathWithContextResolver(splitPath, self);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Creates a resolver function that can access properties from an object using a split path array
|
|
255
|
+
* @param splitPath - The dot syntax path split into an array of property names
|
|
256
|
+
* @param self - Whether the first item in the path refers to the item itself
|
|
257
|
+
* @returns A function that resolves the value from the given path on an accessible object
|
|
258
|
+
*/
|
|
259
|
+
function pathWithContextResolver(splitPath, self) {
|
|
260
|
+
const isInPreviousContext = splitPath[0] === "parent" || splitPath[0] === "parentContext";
|
|
261
|
+
if (self && !isInPreviousContext) {
|
|
210
262
|
if (splitPath.length > 1) {
|
|
211
263
|
splitPath = splitPath.slice(1);
|
|
212
264
|
}
|
|
@@ -216,9 +268,11 @@ export function pathResolver(path, self = false) {
|
|
|
216
268
|
};
|
|
217
269
|
}
|
|
218
270
|
}
|
|
219
|
-
if (
|
|
220
|
-
return (accessibleObject) => {
|
|
221
|
-
return
|
|
271
|
+
if (isInPreviousContext) {
|
|
272
|
+
return (accessibleObject, context) => {
|
|
273
|
+
return splitPath.reduce((previousAccessors, pathItem) => {
|
|
274
|
+
return previousAccessors === null || previousAccessors === void 0 ? void 0 : previousAccessors[pathItem];
|
|
275
|
+
}, context);
|
|
222
276
|
};
|
|
223
277
|
}
|
|
224
278
|
return (accessibleObject) => {
|
|
@@ -227,3 +281,459 @@ export function pathResolver(path, self = false) {
|
|
|
227
281
|
}, accessibleObject);
|
|
228
282
|
};
|
|
229
283
|
}
|
|
284
|
+
export function bindingResolver(rootPropertyName, path, parentContext, type, schema, currentContext, level) {
|
|
285
|
+
rootPropertyName = getRootPropertyName(rootPropertyName, path, currentContext, type);
|
|
286
|
+
if (type !== "event" && rootPropertyName !== null) {
|
|
287
|
+
schema.addPath({
|
|
288
|
+
pathConfig: {
|
|
289
|
+
type,
|
|
290
|
+
currentContext,
|
|
291
|
+
parentContext,
|
|
292
|
+
path,
|
|
293
|
+
},
|
|
294
|
+
rootPropertyName,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return pathResolver(path, currentContext, level, schema.getSchema(rootPropertyName));
|
|
298
|
+
}
|
|
299
|
+
export function expressionResolver(rootPropertyName, expression, parentContext, level, schema) {
|
|
300
|
+
return (x, c) => resolveChainedExpression(x, c, level, parentContext || null, expression, schema.getSchema(rootPropertyName));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Extracts all paths from a ChainedExpression, including nested expressions
|
|
304
|
+
* @param chainedExpression - The chained expression to extract paths from
|
|
305
|
+
* @returns A Set containing all unique paths found in the expression chain
|
|
306
|
+
*/
|
|
307
|
+
export function extractPathsFromChainedExpression(chainedExpression) {
|
|
308
|
+
const paths = new Set();
|
|
309
|
+
function processExpression(expr) {
|
|
310
|
+
// Check left operand (only add if it's not a literal value)
|
|
311
|
+
if (typeof expr.left === "string" && !expr.leftIsValue) {
|
|
312
|
+
paths.add(expr.left);
|
|
313
|
+
}
|
|
314
|
+
// Check right operand (only add if it's not a literal value)
|
|
315
|
+
if (typeof expr.right === "string" && !expr.rightIsValue) {
|
|
316
|
+
paths.add(expr.right);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
let current = chainedExpression;
|
|
320
|
+
while (current !== undefined) {
|
|
321
|
+
processExpression(current.expression);
|
|
322
|
+
current = current.next;
|
|
323
|
+
}
|
|
324
|
+
return paths;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Determine if the operand is a value (boolean, number, string) or an accessor.
|
|
328
|
+
* @param operand - The string to evaluate as either a literal value or property accessor
|
|
329
|
+
* @returns An object containing the parsed value and whether it represents a literal value
|
|
330
|
+
*/
|
|
331
|
+
function isOperandValue(operand) {
|
|
332
|
+
try {
|
|
333
|
+
operand = operand.replaceAll("'", '"');
|
|
334
|
+
const value = JSON.parse(operand);
|
|
335
|
+
return {
|
|
336
|
+
value,
|
|
337
|
+
isValue: true,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
return {
|
|
342
|
+
value: operand,
|
|
343
|
+
isValue: false,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Evaluates parts of an expression chain and chains them with the specified operator
|
|
349
|
+
* @param parts - Each part of an expression chain to be evaluated
|
|
350
|
+
* @param operator - The logical operator used to chain the expression parts
|
|
351
|
+
* @returns A ChainedExpression object representing the linked expressions, or void if no valid expressions found
|
|
352
|
+
*/
|
|
353
|
+
function evaluatePartsInExpressionChain(parts, operator) {
|
|
354
|
+
// Process each part recursively and chain them with ||
|
|
355
|
+
const firstPart = getExpressionChain(parts[0]);
|
|
356
|
+
if (firstPart) {
|
|
357
|
+
let current = firstPart;
|
|
358
|
+
for (let i = 1; i < parts.length; i++) {
|
|
359
|
+
const nextPart = getExpressionChain(parts[i]);
|
|
360
|
+
if (nextPart) {
|
|
361
|
+
// Find the end of the current chain
|
|
362
|
+
while (current.next) {
|
|
363
|
+
current = current.next;
|
|
364
|
+
}
|
|
365
|
+
current.next = Object.assign({ operator }, nextPart);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return firstPart;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Gets the expression chain as a configuration object
|
|
373
|
+
* @param value - The binding string value
|
|
374
|
+
* @returns - A configuration object containing information about the expression
|
|
375
|
+
*/
|
|
376
|
+
export function getExpressionChain(value) {
|
|
377
|
+
// Handle operator precedence: || has lower precedence than &&
|
|
378
|
+
// First, split by || (lowest precedence)
|
|
379
|
+
const orParts = value.split(" || ");
|
|
380
|
+
if (orParts.length > 1) {
|
|
381
|
+
const firstPart = evaluatePartsInExpressionChain(orParts, "||");
|
|
382
|
+
if (firstPart) {
|
|
383
|
+
return firstPart;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// If no ||, check for && (higher precedence)
|
|
387
|
+
const andParts = value.split(" && ");
|
|
388
|
+
if (andParts.length > 1) {
|
|
389
|
+
// Process each part recursively and chain them with &&
|
|
390
|
+
const firstPart = evaluatePartsInExpressionChain(andParts, "&&");
|
|
391
|
+
if (firstPart) {
|
|
392
|
+
return firstPart;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Handle HTML entity version of &&
|
|
396
|
+
const ampParts = value.split(" && ");
|
|
397
|
+
if (ampParts.length > 1) {
|
|
398
|
+
// Process each part recursively and chain them with &&
|
|
399
|
+
const firstPart = evaluatePartsInExpressionChain(ampParts, "&&");
|
|
400
|
+
if (firstPart) {
|
|
401
|
+
return firstPart;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// No chaining operators found, create a single expression
|
|
405
|
+
if (value.trim()) {
|
|
406
|
+
return {
|
|
407
|
+
expression: getExpression(value.trim()),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Parses a binding value string into an Expression object
|
|
414
|
+
* @param value - The binding string value to parse (e.g., "!condition", "foo == bar", "property")
|
|
415
|
+
* @returns An Expression object containing the operator, operands, and whether operands are literal values
|
|
416
|
+
*/
|
|
417
|
+
function getExpression(value) {
|
|
418
|
+
if (value[0] === "!") {
|
|
419
|
+
const left = value.slice(1);
|
|
420
|
+
const operandValue = isOperandValue(left);
|
|
421
|
+
return {
|
|
422
|
+
operator: "!",
|
|
423
|
+
left,
|
|
424
|
+
leftIsValue: operandValue.isValue,
|
|
425
|
+
right: null,
|
|
426
|
+
rightIsValue: null,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const split = value.split(" ");
|
|
430
|
+
if (split.length === 3) {
|
|
431
|
+
const operator = split[1];
|
|
432
|
+
const right = split[2];
|
|
433
|
+
const rightOperandValue = isOperandValue(right);
|
|
434
|
+
const left = split[0];
|
|
435
|
+
const leftOperandValue = isOperandValue(left);
|
|
436
|
+
return {
|
|
437
|
+
operator,
|
|
438
|
+
left: split[0],
|
|
439
|
+
leftIsValue: leftOperandValue.isValue,
|
|
440
|
+
right: rightOperandValue.isValue ? rightOperandValue.value : right,
|
|
441
|
+
rightIsValue: rightOperandValue.isValue,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
operator: "access",
|
|
446
|
+
left: value,
|
|
447
|
+
leftIsValue: false,
|
|
448
|
+
right: null,
|
|
449
|
+
rightIsValue: null,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Resolve a single expression by evaluating its operator and operands
|
|
454
|
+
* @param x - The current data context
|
|
455
|
+
* @param c - The parent context for accessing parent scope data
|
|
456
|
+
* @param level - The nesting level for context resolution
|
|
457
|
+
* @param contextPath - The current context path for property resolution
|
|
458
|
+
* @param expression - The expression object to evaluate
|
|
459
|
+
* @param rootSchema - The root JSON schema for data validation and navigation
|
|
460
|
+
* @returns The resolved value of the expression
|
|
461
|
+
*/
|
|
462
|
+
function resolveExpression(x, c, level, contextPath, expression, rootSchema) {
|
|
463
|
+
const { operator, left, right, rightIsValue } = expression;
|
|
464
|
+
switch (operator) {
|
|
465
|
+
case "!":
|
|
466
|
+
return !pathResolver(left, contextPath, level, rootSchema)(x, c);
|
|
467
|
+
case "==":
|
|
468
|
+
return (pathResolver(left, contextPath, level, rootSchema)(x, c) ==
|
|
469
|
+
(rightIsValue
|
|
470
|
+
? right
|
|
471
|
+
: pathResolver(right, contextPath, level, rootSchema)(x, c)));
|
|
472
|
+
case "!=":
|
|
473
|
+
return (pathResolver(left, contextPath, level, rootSchema)(x, c) !=
|
|
474
|
+
(rightIsValue
|
|
475
|
+
? right
|
|
476
|
+
: pathResolver(right, contextPath, level, rootSchema)(x, c)));
|
|
477
|
+
case ">=":
|
|
478
|
+
return (pathResolver(left, contextPath, level, rootSchema)(x, c) >=
|
|
479
|
+
(rightIsValue
|
|
480
|
+
? right
|
|
481
|
+
: pathResolver(right, contextPath, level, rootSchema)(x, c)));
|
|
482
|
+
case ">":
|
|
483
|
+
return (pathResolver(left, contextPath, level, rootSchema)(x, c) >
|
|
484
|
+
(rightIsValue
|
|
485
|
+
? right
|
|
486
|
+
: pathResolver(right, contextPath, level, rootSchema)(x, c)));
|
|
487
|
+
case "<=":
|
|
488
|
+
return (pathResolver(left, contextPath, level, rootSchema)(x, c) <=
|
|
489
|
+
(rightIsValue
|
|
490
|
+
? right
|
|
491
|
+
: pathResolver(right, contextPath, level, rootSchema)(x, c)));
|
|
492
|
+
case "<":
|
|
493
|
+
return (pathResolver(left, contextPath, level, rootSchema)(x, c) <
|
|
494
|
+
(rightIsValue
|
|
495
|
+
? right
|
|
496
|
+
: pathResolver(right, contextPath, level, rootSchema)(x, c)));
|
|
497
|
+
default:
|
|
498
|
+
return pathResolver(left, contextPath, level, rootSchema)(x, c);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Resolve a chained expression by evaluating expressions linked with logical operators
|
|
503
|
+
* @param x - The current data context
|
|
504
|
+
* @param c - The parent context for accessing parent scope data
|
|
505
|
+
* @param level - The nesting level for context resolution
|
|
506
|
+
* @param contextPath - The current context path for property resolution
|
|
507
|
+
* @param expression - The chained expression object containing linked expressions
|
|
508
|
+
* @param rootSchema - The root JSON schema for data validation and navigation
|
|
509
|
+
* @returns The resolved boolean result of the chained expression
|
|
510
|
+
*/
|
|
511
|
+
function resolveChainedExpression(x, c, level, contextPath, expression, rootSchema) {
|
|
512
|
+
if (expression.next) {
|
|
513
|
+
switch (expression.next.operator) {
|
|
514
|
+
case "&&":
|
|
515
|
+
case "&&":
|
|
516
|
+
return (resolveExpression(x, c, level, contextPath, expression.expression, rootSchema) &&
|
|
517
|
+
resolveChainedExpression(x, c, level, contextPath, expression.next, rootSchema));
|
|
518
|
+
case "||":
|
|
519
|
+
return (resolveExpression(x, c, level, contextPath, expression.expression, rootSchema) ||
|
|
520
|
+
resolveChainedExpression(x, c, level, contextPath, expression.next, rootSchema));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return resolveExpression(x, c, level, contextPath, expression.expression, rootSchema);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* This is the transform utility for rationalizing declarative HTML syntax
|
|
527
|
+
* with bindings in the ViewTemplate
|
|
528
|
+
* @param innerHTML The innerHTML to transform
|
|
529
|
+
* @param index The index to start the current slice of HTML to evaluate
|
|
530
|
+
*/
|
|
531
|
+
export function transformInnerHTML(innerHTML, index = 0) {
|
|
532
|
+
const sliceToEvaluate = innerHTML.slice(index);
|
|
533
|
+
const nextBinding = getNextBehavior(sliceToEvaluate);
|
|
534
|
+
let transformedInnerHTML = innerHTML;
|
|
535
|
+
if (nextBinding && nextBinding.type === "dataBinding") {
|
|
536
|
+
if (nextBinding.bindingType === "unescaped") {
|
|
537
|
+
transformedInnerHTML = `${innerHTML.slice(0, index)}${sliceToEvaluate.slice(0, nextBinding.openingStartIndex)}${startInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.openingStartIndex + 3, nextBinding.closingStartIndex)}${endInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.closingStartIndex + 3)}`;
|
|
538
|
+
return transformInnerHTML(transformedInnerHTML, index +
|
|
539
|
+
startInnerHTMLDivLength +
|
|
540
|
+
endInnerHTMLDivLength +
|
|
541
|
+
nextBinding.closingStartIndex -
|
|
542
|
+
3);
|
|
543
|
+
}
|
|
544
|
+
else if (nextBinding.bindingType === "client") {
|
|
545
|
+
return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
|
|
546
|
+
}
|
|
547
|
+
return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
|
|
548
|
+
}
|
|
549
|
+
else if (nextBinding) {
|
|
550
|
+
return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingTagEndIndex);
|
|
551
|
+
}
|
|
552
|
+
return transformedInnerHTML;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Resolves f-when
|
|
556
|
+
* @param self - Where the first item in the path path refers to the item itself (used by repeat).
|
|
557
|
+
* @param chainedExpression - The chained expression which includes the expression and the next expression
|
|
558
|
+
* if there is another in the chain
|
|
559
|
+
* @returns - A binding that resolves the chained expression logic
|
|
560
|
+
*/
|
|
561
|
+
export function resolveWhen(rootPropertyName, expression, parentContext, level, schema) {
|
|
562
|
+
const binding = expressionResolver(rootPropertyName, expression, parentContext, level, schema);
|
|
563
|
+
return (x, c) => binding(x, c);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Determines the data type of the provided data
|
|
567
|
+
* @param data - The data to analyze
|
|
568
|
+
* @returns "array" for arrays, "object" for non-null objects, "primitive" for other types
|
|
569
|
+
*/
|
|
570
|
+
function getDataType(data) {
|
|
571
|
+
if (Array.isArray(data))
|
|
572
|
+
return "array";
|
|
573
|
+
if (typeof data === "object" && data !== null)
|
|
574
|
+
return "object";
|
|
575
|
+
return "primitive";
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Assigns Observable properties to items in an array and sets up change notifications
|
|
579
|
+
* @param proxiedData - The array data to make observable
|
|
580
|
+
* @param schema - The schema defining the structure of array items
|
|
581
|
+
* @param rootSchema - The root schema for the entire data structure
|
|
582
|
+
* @returns The array with observable properties and change notifications
|
|
583
|
+
*/
|
|
584
|
+
function assignObservablesToArray(proxiedData, schema, rootSchema) {
|
|
585
|
+
const data = proxiedData.map((item) => {
|
|
586
|
+
const originalItem = Object.assign({}, item);
|
|
587
|
+
assignProxyToItemsInArray(item, originalItem, schema, rootSchema);
|
|
588
|
+
return Object.assign(item, originalItem);
|
|
589
|
+
});
|
|
590
|
+
Observable.getNotifier(data).subscribe({
|
|
591
|
+
handleChange(subject, args) {
|
|
592
|
+
args.forEach((arg) => {
|
|
593
|
+
if (arg.addedCount > 0) {
|
|
594
|
+
for (let i = arg.addedCount - 1; i >= 0; i--) {
|
|
595
|
+
const item = subject[arg.index + i];
|
|
596
|
+
const originalItem = Object.assign({}, item);
|
|
597
|
+
assignProxyToItemsInArray(item, originalItem, schema, rootSchema);
|
|
598
|
+
return Object.assign(item, originalItem);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
return data;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Extracts the definition name from a JSON Schema $ref property
|
|
608
|
+
* @param defName - The $ref string (e.g., "#/$defs/MyType")
|
|
609
|
+
* @returns The definition name (e.g., "MyType")
|
|
610
|
+
*/
|
|
611
|
+
function getDefFromRef(defName) {
|
|
612
|
+
const splitName = defName.split("/");
|
|
613
|
+
return splitName.at(-1);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Assign observables to data
|
|
617
|
+
* @param schema - The schema
|
|
618
|
+
* @param rootSchema - The root schema mapping to the root property
|
|
619
|
+
* @param data - The data
|
|
620
|
+
* @param target - The target custom element
|
|
621
|
+
* @param rootProperty - The root property
|
|
622
|
+
* @returns
|
|
623
|
+
*/
|
|
624
|
+
export function assignObservables(schema, rootSchema, data, target, rootProperty) {
|
|
625
|
+
var _a;
|
|
626
|
+
const dataType = getDataType(data);
|
|
627
|
+
let proxiedData = data;
|
|
628
|
+
switch (dataType) {
|
|
629
|
+
case "array": {
|
|
630
|
+
const context = getDefFromRef(schema[refPropertyName]);
|
|
631
|
+
proxiedData = assignObservablesToArray(proxiedData, (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context], rootSchema);
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
case "object": {
|
|
635
|
+
proxiedData = assignProxyToItemsInObject(target, rootProperty, proxiedData, schema, rootSchema);
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return proxiedData;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Assign a proxy to items in an array
|
|
643
|
+
* @param item - The array item to proxy
|
|
644
|
+
* @param originalItem - The original array item
|
|
645
|
+
* @param schema - The schema mapping to the items in the array
|
|
646
|
+
* @param rootSchema - The root schema assigned to the root property
|
|
647
|
+
*/
|
|
648
|
+
function assignProxyToItemsInArray(item, originalItem, schema, rootSchema) {
|
|
649
|
+
const itemProperties = Object.keys(item);
|
|
650
|
+
itemProperties.forEach(key => {
|
|
651
|
+
Observable.defineProperty(item, key);
|
|
652
|
+
if (originalItem[key] && schema && schema.properties) {
|
|
653
|
+
originalItem[key] = assignProxyToItemsInObject(item, key, originalItem[key], schema.properties[key], rootSchema);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Assign a proxy to items in an object
|
|
659
|
+
* @param target - The target custom element
|
|
660
|
+
* @param rootProperty - The root property
|
|
661
|
+
* @param data - The data to proxy
|
|
662
|
+
* @param schema - The schema for the data
|
|
663
|
+
* @param rootSchema - The root schema for the root property
|
|
664
|
+
* @returns a Proxy
|
|
665
|
+
*/
|
|
666
|
+
function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSchema) {
|
|
667
|
+
var _a;
|
|
668
|
+
const type = getDataType(data);
|
|
669
|
+
let proxiedData = data;
|
|
670
|
+
if (type === "object" && (schema === null || schema === void 0 ? void 0 : schema.properties)) {
|
|
671
|
+
// navigate through all items in the object
|
|
672
|
+
Object.keys(schema.properties).forEach(property => {
|
|
673
|
+
if (proxiedData[property] && schema && schema.properties) {
|
|
674
|
+
proxiedData[property] = assignProxyToItemsInObject(target, rootProperty, proxiedData[property], schema.properties[property], rootSchema);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
// assign a Proxy to the object
|
|
678
|
+
proxiedData = assignProxy(schema, rootSchema, target, rootProperty, data);
|
|
679
|
+
}
|
|
680
|
+
else if (type === "array") {
|
|
681
|
+
const context = getDefFromRef(schema.items[refPropertyName]);
|
|
682
|
+
const definition = (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context];
|
|
683
|
+
if ((definition === null || definition === void 0 ? void 0 : definition.type) === "object") {
|
|
684
|
+
proxiedData = assignObservablesToArray(proxiedData, definition, rootSchema);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return proxiedData;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Assign a proxy to an object
|
|
691
|
+
* @param schema - The current schema
|
|
692
|
+
* @param rootSchema - The root schema for the root property
|
|
693
|
+
* @param target - The target custom element
|
|
694
|
+
* @param rootProperty - The root property
|
|
695
|
+
* @param object - The object to assign the proxy to
|
|
696
|
+
* @returns Proxy object
|
|
697
|
+
*/
|
|
698
|
+
export function assignProxy(schema, rootSchema, target, rootProperty, object) {
|
|
699
|
+
if (object.$isProxy === undefined) {
|
|
700
|
+
// Create a proxy for the object that triggers Observable.notify on mutations
|
|
701
|
+
return new Proxy(object, {
|
|
702
|
+
set: (obj, prop, value) => {
|
|
703
|
+
obj[prop] = assignObservables(schema, rootSchema, value, target, rootProperty);
|
|
704
|
+
// Trigger notification for property changes
|
|
705
|
+
Observable.notify(target, rootProperty);
|
|
706
|
+
return true;
|
|
707
|
+
},
|
|
708
|
+
get: (target, key) => {
|
|
709
|
+
if (key !== "$isProxy") {
|
|
710
|
+
return target[key];
|
|
711
|
+
}
|
|
712
|
+
return true;
|
|
713
|
+
},
|
|
714
|
+
deleteProperty: (obj, prop) => {
|
|
715
|
+
if (prop in obj) {
|
|
716
|
+
delete obj[prop];
|
|
717
|
+
// Trigger notification for property deletion
|
|
718
|
+
Observable.notify(target, rootProperty);
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
return object;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Get the root property name
|
|
729
|
+
* @param rootPropertyName - The root property
|
|
730
|
+
* @param path - The dot syntax path
|
|
731
|
+
* @param context - The context created by a repeat
|
|
732
|
+
* @param type - The type of path binding
|
|
733
|
+
* @returns
|
|
734
|
+
*/
|
|
735
|
+
export function getRootPropertyName(rootPropertyName, path, context, type) {
|
|
736
|
+
return (rootPropertyName === null || context === null) && type !== "event"
|
|
737
|
+
? path.split(".")[0]
|
|
738
|
+
: rootPropertyName;
|
|
739
|
+
}
|