@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.
Files changed (52) hide show
  1. package/README.md +129 -18
  2. package/dist/dts/components/element.d.ts +10 -0
  3. package/dist/dts/components/index.d.ts +2 -0
  4. package/dist/dts/components/observer-map.d.ts +26 -0
  5. package/dist/dts/components/schema.d.ts +134 -0
  6. package/dist/dts/components/template.d.ts +35 -6
  7. package/dist/dts/components/utilities.d.ts +92 -19
  8. package/dist/dts/fixtures/observer-map/main.d.ts +1 -0
  9. package/dist/dts/fixtures/observer-map/observer-map.spec.d.ts +1 -0
  10. package/dist/dts/index.d.ts +1 -1
  11. package/dist/esm/components/element.js +27 -0
  12. package/dist/esm/components/index.js +2 -0
  13. package/dist/esm/components/observer-map.js +49 -0
  14. package/dist/esm/components/observer-map.spec.js +19 -0
  15. package/dist/esm/components/schema.js +215 -0
  16. package/dist/esm/components/schema.spec.js +257 -0
  17. package/dist/esm/components/template.js +160 -99
  18. package/dist/esm/components/utilities.js +553 -43
  19. package/dist/esm/components/utilities.spec.js +246 -44
  20. package/dist/esm/fixtures/attribute/main.js +3 -2
  21. package/dist/esm/fixtures/binding/binding.spec.js +6 -0
  22. package/dist/esm/fixtures/binding/main.js +13 -2
  23. package/dist/esm/fixtures/children/children.spec.js +4 -0
  24. package/dist/esm/fixtures/children/main.js +3 -2
  25. package/dist/esm/fixtures/dot-syntax/dot-syntax.spec.js +109 -2
  26. package/dist/esm/fixtures/dot-syntax/main.js +30 -4
  27. package/dist/esm/fixtures/event/event.spec.js +28 -5
  28. package/dist/esm/fixtures/event/main.js +21 -5
  29. package/dist/esm/fixtures/observer-map/main.js +304 -0
  30. package/dist/esm/fixtures/observer-map/observer-map.spec.js +174 -0
  31. package/dist/esm/fixtures/ref/main.js +3 -2
  32. package/dist/esm/fixtures/ref/ref.spec.js +2 -6
  33. package/dist/esm/fixtures/repeat/main.js +27 -2
  34. package/dist/esm/fixtures/repeat/repeat.spec.js +16 -6
  35. package/dist/esm/fixtures/slotted/main.js +15 -4
  36. package/dist/esm/fixtures/slotted/slotted.spec.js +18 -19
  37. package/dist/esm/fixtures/when/main.js +139 -2
  38. package/dist/esm/fixtures/when/when.spec.js +64 -1
  39. package/dist/esm/index.js +1 -1
  40. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  41. package/dist/fast-html.api.json +279 -0
  42. package/dist/fast-html.d.ts +215 -5
  43. package/dist/fast-html.untrimmed.d.ts +215 -5
  44. package/package.json +12 -9
  45. package/rules/attribute-directives.yml +38 -0
  46. package/rules/call-expression-with-event-argument.yml +41 -0
  47. package/rules/member-expression.yml +33 -0
  48. package/rules/tag-function-to-template-literal.yml +16 -0
  49. package/dist/esm/fixtures/partial/main.js +0 -31
  50. package/dist/esm/fixtures/partial/partial.spec.js +0 -14
  51. /package/dist/dts/{fixtures/partial/main.d.ts → components/observer-map.spec.d.ts} +0 -0
  52. /package/dist/dts/{fixtures/partial/partial.spec.d.ts → components/schema.spec.d.ts} +0 -0
@@ -1,9 +1,19 @@
1
- const openBinding = "{{";
2
- const closeBinding = "}}";
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 = openingTagStartSlice.indexOf(tagEnd) + openingTagStartIndex + 1;
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.indexOf(openBinding);
140
- const closingStartIndex = innerHTML.indexOf(closeBinding);
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 + 2,
197
+ openingEndIndex: openingStartIndex + bindingLength,
145
198
  closingStartIndex,
146
- closingEndIndex: closingStartIndex + 2,
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(openBinding);
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, self = false) {
230
+ export function pathResolver(path, contextPath, level, rootSchema) {
231
+ var _a, _b;
208
232
  let splitPath = path.split(".");
209
- if (self) {
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 (splitPath.length === 1) {
220
- return (accessibleObject) => {
221
- return accessibleObject === null || accessibleObject === void 0 ? void 0 : accessibleObject[splitPath[0]];
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(" &amp;&amp; ");
397
+ if (ampParts.length > 1) {
398
+ // Process each part recursively and chain them with &amp;&amp;
399
+ const firstPart = evaluatePartsInExpressionChain(ampParts, "&amp;&amp;");
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 "&amp;&amp;":
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
+ }