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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +242 -18
  2. package/dist/dts/components/element.d.ts +10 -0
  3. package/dist/dts/components/index.d.ts +3 -1
  4. package/dist/dts/components/observer-map.d.ts +27 -0
  5. package/dist/dts/components/schema.d.ts +144 -0
  6. package/dist/dts/components/template.d.ts +83 -7
  7. package/dist/dts/components/utilities.d.ts +126 -37
  8. package/dist/dts/index.d.ts +1 -1
  9. package/dist/dts/tsdoc-metadata.json +1 -1
  10. package/dist/esm/components/element.js +73 -0
  11. package/dist/esm/components/index.js +3 -1
  12. package/dist/esm/components/observer-map.js +68 -0
  13. package/dist/esm/components/observer-map.spec.js +39 -0
  14. package/dist/esm/components/schema.js +250 -0
  15. package/dist/esm/components/schema.spec.js +484 -0
  16. package/dist/esm/components/template.js +235 -213
  17. package/dist/esm/components/utilities.js +990 -64
  18. package/dist/esm/components/utilities.spec.js +522 -93
  19. package/dist/esm/index.js +1 -1
  20. package/dist/fast-html.api.json +350 -1
  21. package/dist/fast-html.d.ts +283 -6
  22. package/dist/fast-html.untrimmed.d.ts +283 -6
  23. package/package.json +27 -38
  24. package/rules/attribute-directives.yml +38 -0
  25. package/rules/call-expression-with-event-argument.yml +41 -0
  26. package/rules/member-expression.yml +33 -0
  27. package/rules/tag-function-to-template-literal.yml +16 -0
  28. package/dist/dts/fixtures/binding/binding.spec.d.ts +0 -1
  29. package/dist/dts/fixtures/binding/main.d.ts +0 -1
  30. package/dist/dts/fixtures/children/children.spec.d.ts +0 -1
  31. package/dist/dts/fixtures/children/main.d.ts +0 -1
  32. package/dist/dts/fixtures/dot-syntax/dot-syntax.spec.d.ts +0 -1
  33. package/dist/dts/fixtures/dot-syntax/main.d.ts +0 -1
  34. package/dist/dts/fixtures/event/event.spec.d.ts +0 -1
  35. package/dist/dts/fixtures/event/main.d.ts +0 -1
  36. package/dist/dts/fixtures/partial/main.d.ts +0 -1
  37. package/dist/dts/fixtures/partial/partial.spec.d.ts +0 -1
  38. package/dist/dts/fixtures/ref/main.d.ts +0 -1
  39. package/dist/dts/fixtures/ref/ref.spec.d.ts +0 -1
  40. package/dist/dts/fixtures/repeat/main.d.ts +0 -1
  41. package/dist/dts/fixtures/repeat/repeat.spec.d.ts +0 -1
  42. package/dist/dts/fixtures/slotted/main.d.ts +0 -1
  43. package/dist/dts/fixtures/slotted/slotted.spec.d.ts +0 -1
  44. package/dist/dts/fixtures/when/main.d.ts +0 -1
  45. package/dist/dts/fixtures/when/when.spec.d.ts +0 -1
  46. package/dist/esm/fixtures/attribute/attribute.spec.js +0 -23
  47. package/dist/esm/fixtures/attribute/main.js +0 -19
  48. package/dist/esm/fixtures/binding/binding.spec.js +0 -17
  49. package/dist/esm/fixtures/binding/main.js +0 -19
  50. package/dist/esm/fixtures/children/children.spec.js +0 -33
  51. package/dist/esm/fixtures/children/main.js +0 -24
  52. package/dist/esm/fixtures/dot-syntax/dot-syntax.spec.js +0 -9
  53. package/dist/esm/fixtures/dot-syntax/main.js +0 -16
  54. package/dist/esm/fixtures/event/event.spec.js +0 -12
  55. package/dist/esm/fixtures/event/main.js +0 -16
  56. package/dist/esm/fixtures/partial/main.js +0 -31
  57. package/dist/esm/fixtures/partial/partial.spec.js +0 -14
  58. package/dist/esm/fixtures/ref/main.js +0 -14
  59. package/dist/esm/fixtures/ref/ref.spec.js +0 -13
  60. package/dist/esm/fixtures/repeat/main.js +0 -19
  61. package/dist/esm/fixtures/repeat/repeat.spec.js +0 -26
  62. package/dist/esm/fixtures/slotted/main.js +0 -22
  63. package/dist/esm/fixtures/slotted/slotted.spec.js +0 -25
  64. package/dist/esm/fixtures/when/main.js +0 -146
  65. package/dist/esm/fixtures/when/when.spec.js +0 -82
  66. package/dist/esm/tsconfig.tsbuildinfo +0 -1
  67. /package/dist/dts/{fixtures/attribute/attribute.spec.d.ts → components/observer-map.spec.d.ts} +0 -0
  68. /package/dist/dts/{fixtures/attribute/main.d.ts → components/schema.spec.d.ts} +0 -0
@@ -1,9 +1,45 @@
1
- const openBinding = "{{";
2
- const closeBinding = "}}";
1
+ import { Observable } from "@microsoft/fast-element/observable.js";
2
+ import { defsPropertyName, fastContextMetaData, refPropertyName, Schema, } 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;
17
+ const LogicalOperator = {
18
+ AND: "&&",
19
+ OR: "||",
20
+ };
21
+ const ComparisonOperator = {
22
+ ACCESS: "access",
23
+ EQUALS: "==",
24
+ GREATER_THAN: ">",
25
+ GREATER_THAN_OR_EQUALS: ">=",
26
+ LESS_THAN: "<",
27
+ LESS_THAN_OR_EQUALS: "<=",
28
+ NOT: "!",
29
+ NOT_EQUALS: "!=",
30
+ };
31
+ const Operator = {
32
+ ...LogicalOperator,
33
+ ...ComparisonOperator,
34
+ };
35
+ /**
36
+ * A map of proxied objects
37
+ */
38
+ const objectTargetsMap = new WeakMap();
39
+ /**
40
+ * A map of arrays being observered
41
+ */
42
+ const observedArraysMap = new WeakMap();
7
43
  /**
8
44
  * Get the index of the next matching tag
9
45
  * @param openingTagStartSlice - The slice starting from the opening tag
@@ -108,7 +144,11 @@ function getAttributeDataBindingConfig(innerHTML, config) {
108
144
  firstCharOfAttribute === ":"
109
145
  ? firstCharOfAttribute
110
146
  : null;
111
- return Object.assign(Object.assign({}, config), { subtype: "attribute", aspect });
147
+ return {
148
+ ...config,
149
+ subtype: "attribute",
150
+ aspect,
151
+ };
112
152
  }
113
153
  /**
114
154
  * Get the attribute directive binding config
@@ -121,7 +161,11 @@ function getAttributeDirectiveDataBindingConfig(innerHTML, config) {
121
161
  const lastItem = splitInnerHTML[splitInnerHTML.length - 1];
122
162
  const equals = lastItem.indexOf("=");
123
163
  const name = lastItem.slice(2, equals);
124
- return Object.assign(Object.assign({}, config), { subtype: "attributeDirective", name: name });
164
+ return {
165
+ ...config,
166
+ subtype: "attributeDirective",
167
+ name: name,
168
+ };
125
169
  }
126
170
  /**
127
171
  * Get the content data binding config
@@ -129,7 +173,51 @@ function getAttributeDirectiveDataBindingConfig(innerHTML, config) {
129
173
  * @returns ContentDataBindingBehaviorConfig
130
174
  */
131
175
  function getContentDataBindingConfig(config) {
132
- return Object.assign(Object.assign({}, config), { subtype: "content" });
176
+ return {
177
+ ...config,
178
+ subtype: "content",
179
+ };
180
+ }
181
+ /**
182
+ * Finds the next data binding in innerHTML and determines its type and indices
183
+ * @param innerHTML - The innerHTML string to search for data bindings
184
+ * @returns NextDataBindingBehaviorConfig containing the binding type and start indices
185
+ */
186
+ function getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML) {
187
+ // {{{}}} binding
188
+ const openingUnescapedStartIndex = innerHTML.indexOf(openUnescapedBinding);
189
+ const closingUnescapedStartIndex = innerHTML.indexOf(closeUnescapedBinding);
190
+ // {{}} binding
191
+ const openingContentStartIndex = innerHTML.indexOf(openContentBinding);
192
+ const closingContentStartIndex = innerHTML.indexOf(closeContentBinding);
193
+ // {} binding
194
+ const openingClientStartIndex = innerHTML.indexOf(openClientSideBinding);
195
+ const closingClientStartIndex = innerHTML.indexOf(closeClientSideBinding);
196
+ if (openingUnescapedStartIndex !== -1 &&
197
+ openingUnescapedStartIndex <= openingContentStartIndex &&
198
+ openingUnescapedStartIndex <= openingClientStartIndex) {
199
+ // is unescaped {{{}}}
200
+ return {
201
+ openingStartIndex: openingUnescapedStartIndex,
202
+ closingStartIndex: closingUnescapedStartIndex,
203
+ bindingType: "unescaped",
204
+ };
205
+ }
206
+ else if (openingContentStartIndex !== -1 &&
207
+ openingContentStartIndex <= openingClientStartIndex) {
208
+ // is default {{}}
209
+ return {
210
+ openingStartIndex: openingContentStartIndex,
211
+ closingStartIndex: closingContentStartIndex,
212
+ bindingType: "default",
213
+ };
214
+ }
215
+ // is client {}
216
+ return {
217
+ openingStartIndex: openingClientStartIndex,
218
+ closingStartIndex: closingClientStartIndex,
219
+ bindingType: "client",
220
+ };
133
221
  }
134
222
  /**
135
223
  * Get the next data binding
@@ -137,14 +225,15 @@ function getContentDataBindingConfig(config) {
137
225
  * @returns DataBindingBehaviorConfig - A configuration object
138
226
  */
139
227
  function getNextDataBindingBehavior(innerHTML) {
140
- const openingStartIndex = innerHTML.indexOf(openBinding);
141
- const closingStartIndex = innerHTML.indexOf(closeBinding);
228
+ const { openingStartIndex, closingStartIndex, bindingType } = getIndexAndBindingTypeOfNextDataBindingBehavior(innerHTML);
229
+ const bindingLength = bindingType === "client" ? 1 : bindingType === "default" ? 2 : 3;
142
230
  const partialConfig = {
143
231
  type: "dataBinding",
232
+ bindingType,
144
233
  openingStartIndex,
145
- openingEndIndex: openingStartIndex + 2,
234
+ openingEndIndex: openingStartIndex + bindingLength,
146
235
  closingStartIndex,
147
- closingEndIndex: closingStartIndex + 2,
236
+ closingEndIndex: closingStartIndex + bindingLength,
148
237
  };
149
238
  return isAttributeDirective(innerHTML, openingStartIndex)
150
239
  ? getAttributeDirectiveDataBindingConfig(innerHTML, partialConfig)
@@ -157,46 +246,67 @@ function getNextDataBindingBehavior(innerHTML) {
157
246
  * @param innerHTML - The innerHTML string to evaluate
158
247
  * @returns DataBindingBehaviorConfig | DirectiveBehaviorConfig | null - A configuration object or null
159
248
  */
160
- export function getNextBehavior(innerHTML) {
161
- const dataBindingOpen = innerHTML.indexOf(openBinding);
162
- const directiveBindingOpen = innerHTML.indexOf(openTagStart);
163
- if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
164
- return null;
165
- }
166
- if (directiveBindingOpen !== -1 && dataBindingOpen > directiveBindingOpen) {
167
- return getNextDirectiveBehavior(innerHTML);
249
+ export function getNextBehavior(innerHTML, offset = 0) {
250
+ // eslint-disable-next-line no-constant-condition
251
+ while (true) {
252
+ const currentSlice = innerHTML.slice(offset);
253
+ // client side binding will capture all bindings starting with "{"
254
+ const dataBindingOpen = currentSlice.indexOf(openClientSideBinding);
255
+ const directiveBindingOpen = currentSlice.indexOf(openTagStart);
256
+ const nextDataBindingBehavior = getNextDataBindingBehavior(currentSlice);
257
+ if (dataBindingOpen === -1 && directiveBindingOpen === -1) {
258
+ return null;
259
+ }
260
+ if (dataBindingOpen !== -1 &&
261
+ nextDataBindingBehavior.bindingType === "client" &&
262
+ !isLegitimateClientSideBinding(nextDataBindingBehavior)) {
263
+ offset = nextDataBindingBehavior.closingEndIndex + offset;
264
+ continue;
265
+ }
266
+ if (directiveBindingOpen !== -1 &&
267
+ (dataBindingOpen === -1 || dataBindingOpen > directiveBindingOpen)) {
268
+ return offsetDirective(getNextDirectiveBehavior(currentSlice), offset);
269
+ }
270
+ return offsetDataBinding(nextDataBindingBehavior, offset);
168
271
  }
169
- return getNextDataBindingBehavior(innerHTML);
170
272
  }
171
273
  /**
172
- * Gets all the partials with their IDs
173
- * @param innerHTML - The innerHTML string to evaluate
174
- * @param offset - The index offset from the innerHTML
175
- * @param partials - The partials found
176
- * @returns {[key: string]: PartialTemplateConfig}
177
- */
178
- export function getAllPartials(innerHTML, offset = 0, partials = {}) {
179
- const openingTag = `${openTagStart}partial`;
180
- const openingTagStartIndex = innerHTML.indexOf(openingTag);
181
- if (openingTagStartIndex >= 0) {
182
- const openingTagStartSlice = innerHTML.slice(openingTagStartIndex);
183
- const closingTag = `${closeTagStart}partial${tagEnd}`;
184
- const closingTagLength = closingTag.length;
185
- const matchingCloseTagIndex = getIndexOfNextMatchingTag(openingTagStartSlice, openingTag, closingTag, openingTagStartIndex) + closingTagLength;
186
- const startId = openingTagStartIndex + ' id="'.length + openingTag.length;
187
- const endId = innerHTML.slice(startId).indexOf('"') + startId;
188
- const id = innerHTML.slice(startId, endId);
189
- const openingTagEndIndex = openingTagStartSlice.indexOf(tagEnd) + 1 + openingTagStartIndex;
190
- const closingTagStartIndex = matchingCloseTagIndex - closingTagLength;
191
- partials[id] = {
192
- innerHTML: innerHTML.slice(openingTagEndIndex, closingTagStartIndex),
193
- startIndex: openingTagEndIndex + offset,
194
- endIndex: closingTagStartIndex + offset,
195
- };
196
- offset += matchingCloseTagIndex;
197
- return getAllPartials(innerHTML.slice(matchingCloseTagIndex), offset, partials);
198
- }
199
- return partials;
274
+ * Apply an offset to a data binding
275
+ * @param config DataBindingBehaviorConfig
276
+ * @param offset number
277
+ * @returns DataBindingBehaviorConfig
278
+ */
279
+ function offsetDataBinding(config, offset) {
280
+ config.openingStartIndex = config.openingStartIndex + offset;
281
+ config.openingEndIndex = config.openingEndIndex + offset;
282
+ config.closingStartIndex = config.closingStartIndex + offset;
283
+ config.closingEndIndex = config.closingEndIndex + offset;
284
+ return config;
285
+ }
286
+ /**
287
+ * Apply an offset to a directive
288
+ * @param config TemplateDirectiveBehaviorConfig
289
+ * @param offset number
290
+ * @returns TemplateDirectiveBehaviorConfig
291
+ */
292
+ function offsetDirective(config, offset) {
293
+ config.openingTagStartIndex = config.openingTagStartIndex + offset;
294
+ config.openingTagEndIndex = config.openingTagEndIndex + offset;
295
+ config.closingTagStartIndex = config.closingTagStartIndex + offset;
296
+ config.closingTagEndIndex = config.closingTagEndIndex + offset;
297
+ return config;
298
+ }
299
+ /**
300
+ * Determine if this client side binding is legitimate.
301
+ * Single-brace (client) bindings are only valid for events, properties, and attribute directives.
302
+ * Checking for this prevents CSS/JS curly braces from being misinterpreted as bindings.
303
+ * @param result
304
+ * @returns
305
+ */
306
+ function isLegitimateClientSideBinding(result) {
307
+ return ((result.subtype === "attribute" &&
308
+ (result.aspect === "@" || result.aspect === ":")) ||
309
+ result.subtype === "attributeDirective");
200
310
  }
201
311
  /**
202
312
  * Create a function to resolve a value from an object using a path with dot syntax.
@@ -205,9 +315,38 @@ export function getAllPartials(innerHTML, offset = 0, partials = {}) {
205
315
  * @param self - Where the first item in the path path refers to the item itself (used by repeat).
206
316
  * @returns A function to access the value from a given path.
207
317
  */
208
- export function pathResolver(path, self = false) {
318
+ export function pathResolver(path, contextPath, level, rootSchema) {
319
+ var _a, _b;
209
320
  let splitPath = path.split(".");
210
- if (self) {
321
+ let levelCount = level;
322
+ let self = splitPath[0] === contextPath;
323
+ const parentContexts = [];
324
+ if (level > 0 &&
325
+ ((_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[fastContextMetaData]) ===
326
+ splitPath.at(-1)) {
327
+ self = true;
328
+ }
329
+ while (levelCount > 0 && !self) {
330
+ if (levelCount !== 1) {
331
+ parentContexts.push("parentContext");
332
+ }
333
+ else {
334
+ parentContexts.push("parent");
335
+ }
336
+ levelCount--;
337
+ }
338
+ splitPath = [...parentContexts, ...splitPath];
339
+ return pathWithContextResolver(splitPath, self);
340
+ }
341
+ /**
342
+ * Creates a resolver function that can access properties from an object using a split path array
343
+ * @param splitPath - The dot syntax path split into an array of property names
344
+ * @param self - Whether the first item in the path refers to the item itself
345
+ * @returns A function that resolves the value from the given path on an accessible object
346
+ */
347
+ function pathWithContextResolver(splitPath, self) {
348
+ const isInPreviousContext = splitPath[0] === "parent" || splitPath[0] === "parentContext";
349
+ if (self && !isInPreviousContext) {
211
350
  if (splitPath.length > 1) {
212
351
  splitPath = splitPath.slice(1);
213
352
  }
@@ -217,9 +356,11 @@ export function pathResolver(path, self = false) {
217
356
  };
218
357
  }
219
358
  }
220
- if (splitPath.length === 1) {
221
- return (accessibleObject) => {
222
- return accessibleObject === null || accessibleObject === void 0 ? void 0 : accessibleObject[splitPath[0]];
359
+ if (isInPreviousContext) {
360
+ return (accessibleObject, context) => {
361
+ return splitPath.reduce((previousAccessors, pathItem) => {
362
+ return previousAccessors === null || previousAccessors === void 0 ? void 0 : previousAccessors[pathItem];
363
+ }, context);
223
364
  };
224
365
  }
225
366
  return (accessibleObject) => {
@@ -228,12 +369,74 @@ export function pathResolver(path, self = false) {
228
369
  }, accessibleObject);
229
370
  };
230
371
  }
372
+ export function bindingResolver(previousString, rootPropertyName, path, parentContext, type, schema, currentContext, level) {
373
+ rootPropertyName = getRootPropertyName(rootPropertyName, path, currentContext, type);
374
+ if (type !== "event" && rootPropertyName !== null) {
375
+ const childrenMap = getChildrenMap(previousString);
376
+ schema.addPath({
377
+ pathConfig: {
378
+ type,
379
+ currentContext,
380
+ parentContext,
381
+ path,
382
+ },
383
+ rootPropertyName,
384
+ childrenMap,
385
+ });
386
+ }
387
+ return pathResolver(path, currentContext, level, schema.getSchema(rootPropertyName));
388
+ }
389
+ export function expressionResolver(rootPropertyName, expression, parentContext, level, schema) {
390
+ // Extract all paths from the expression and add them to the schema
391
+ if (rootPropertyName !== null) {
392
+ const paths = extractPathsFromChainedExpression(expression);
393
+ paths.forEach(path => {
394
+ schema.addPath({
395
+ pathConfig: {
396
+ type: "access",
397
+ currentContext: parentContext,
398
+ parentContext: null,
399
+ path,
400
+ },
401
+ rootPropertyName,
402
+ childrenMap: null,
403
+ });
404
+ });
405
+ }
406
+ return (x, c) => resolveChainedExpression(x, c, level, parentContext || null, expression, schema.getSchema(rootPropertyName));
407
+ }
408
+ /**
409
+ * Extracts all paths from a ChainedExpression, including nested expressions
410
+ * @param chainedExpression - The chained expression to extract paths from
411
+ * @returns A Set containing all unique paths found in the expression chain
412
+ */
413
+ export function extractPathsFromChainedExpression(chainedExpression) {
414
+ const paths = new Set();
415
+ function processExpression(expr) {
416
+ // Check left operand (only add if it's not a literal value)
417
+ if (typeof expr.left === "string" && !expr.leftIsValue) {
418
+ paths.add(expr.left);
419
+ }
420
+ // Check right operand (only add if it's not a literal value)
421
+ if (typeof expr.right === "string" && !expr.rightIsValue) {
422
+ paths.add(expr.right);
423
+ }
424
+ }
425
+ let current = chainedExpression;
426
+ while (current !== undefined) {
427
+ processExpression(current.expression);
428
+ current = current.next;
429
+ }
430
+ return paths;
431
+ }
231
432
  /**
232
433
  * Determine if the operand is a value (boolean, number, string) or an accessor.
233
- * @param operand
434
+ * @param operand - The string to evaluate as either a literal value or property accessor
435
+ * @returns An object containing the parsed value and whether it represents a literal value
234
436
  */
235
437
  function isOperandValue(operand) {
236
438
  try {
439
+ operand = operand.replaceAll("'", '"');
237
440
  const value = JSON.parse(operand);
238
441
  return {
239
442
  value,
@@ -248,34 +451,757 @@ function isOperandValue(operand) {
248
451
  }
249
452
  }
250
453
  /**
251
- * Get the operator used.
252
- * @param value the binded value
253
- * @returns Operator
454
+ * Evaluates parts of an expression chain and chains them with the specified operator
455
+ * @param parts - Each part of an expression chain to be evaluated
456
+ * @param operator - The logical operator used to chain the expression parts
457
+ * @returns A ChainedExpression object representing the linked expressions, or void if no valid expressions found
254
458
  */
255
- export function getOperator(value) {
256
- if (value[0] === "!") {
459
+ function evaluatePartsInExpressionChain(parts, operator) {
460
+ // Process each part recursively and chain them with ||
461
+ const firstPart = getExpressionChain(parts[0]);
462
+ if (firstPart) {
463
+ let current = firstPart;
464
+ for (let i = 1; i < parts.length; i++) {
465
+ const nextPart = getExpressionChain(parts[i]);
466
+ if (nextPart) {
467
+ // Find the end of the current chain
468
+ while (current.next) {
469
+ current = current.next;
470
+ }
471
+ current.next = {
472
+ operator,
473
+ ...nextPart,
474
+ };
475
+ }
476
+ }
477
+ return firstPart;
478
+ }
479
+ }
480
+ /**
481
+ * Gets the expression chain as a configuration object
482
+ * @param value - The binding string value
483
+ * @returns - A configuration object containing information about the expression
484
+ */
485
+ export function getExpressionChain(value) {
486
+ // Decode HTML entities in the expression value first
487
+ const decodedValue = decodeExpressionOperators(value);
488
+ // Handle operator precedence: || has lower precedence than &&
489
+ // First, split by || (lowest precedence)
490
+ const orParts = decodedValue.split(/\s*\|\|\s*/);
491
+ if (orParts.length > 1) {
492
+ const firstPart = evaluatePartsInExpressionChain(orParts, Operator.OR);
493
+ if (firstPart) {
494
+ return firstPart;
495
+ }
496
+ }
497
+ // If no ||, check for && (higher precedence)
498
+ const andParts = decodedValue.split(/\s*&&\s*/);
499
+ if (andParts.length > 1) {
500
+ // Process each part recursively and chain them with &&
501
+ const firstPart = evaluatePartsInExpressionChain(andParts, "&&");
502
+ if (firstPart) {
503
+ return firstPart;
504
+ }
505
+ }
506
+ // No chaining operators found, create a single expression
507
+ if (decodedValue.trim()) {
257
508
  return {
258
- operator: "!",
259
- left: value.slice(1),
509
+ expression: getExpression(decodedValue.trim()),
510
+ };
511
+ }
512
+ return void 0;
513
+ }
514
+ /**
515
+ * Parses a binding value string into an Expression object
516
+ * @param value - The binding string value to parse (e.g., "!condition", "foo == bar", "property")
517
+ * @returns An Expression object containing the operator, operands, and whether operands are literal values
518
+ */
519
+ function getExpression(value) {
520
+ if (value[0] === Operator.NOT) {
521
+ const left = value.slice(1);
522
+ const operandValue = isOperandValue(left);
523
+ return {
524
+ operator: Operator.NOT,
525
+ left,
526
+ leftIsValue: operandValue.isValue,
260
527
  right: null,
261
528
  rightIsValue: null,
262
529
  };
263
530
  }
264
- const split = value.split(" ");
531
+ const split = value.split(/\s*([=!]=|[><]=?)\s*/);
265
532
  if (split.length === 3) {
266
533
  const operator = split[1];
267
- const { value, isValue } = isOperandValue(split[2]);
534
+ const right = split[2];
535
+ const rightOperandValue = isOperandValue(right);
536
+ const left = split[0];
537
+ const leftOperandValue = isOperandValue(left);
268
538
  return {
269
539
  operator,
270
540
  left: split[0],
271
- right: isValue ? value : split[2],
272
- rightIsValue: isValue,
541
+ leftIsValue: leftOperandValue.isValue,
542
+ right: rightOperandValue.isValue ? rightOperandValue.value : right,
543
+ rightIsValue: rightOperandValue.isValue,
273
544
  };
274
545
  }
275
546
  return {
276
- operator: "access",
547
+ operator: Operator.ACCESS,
277
548
  left: value,
549
+ leftIsValue: false,
278
550
  right: null,
279
551
  rightIsValue: null,
280
552
  };
281
553
  }
554
+ /**
555
+ * Decodes HTML entities within expression strings only (for operators like &&, <, >)
556
+ * This is safer than decoding the entire template as it preserves HTML-encoded content
557
+ * and only decodes operators needed for expression evaluation
558
+ * @param expression - The expression string to decode
559
+ * @returns The expression with operators decoded
560
+ */
561
+ function decodeExpressionOperators(expression) {
562
+ return expression
563
+ .replace(/&amp;&amp;/g, Operator.AND)
564
+ .replace(/&lt;/g, Operator.LESS_THAN)
565
+ .replace(/&gt;/g, Operator.GREATER_THAN);
566
+ }
567
+ /**
568
+ * Resolve a single expression by evaluating its operator and operands
569
+ * @param x - The current data context
570
+ * @param c - The parent context for accessing parent scope data
571
+ * @param level - The nesting level for context resolution
572
+ * @param contextPath - The current context path for property resolution
573
+ * @param expression - The expression object to evaluate
574
+ * @param rootSchema - The root JSON schema for data validation and navigation
575
+ * @returns The resolved value of the expression
576
+ */
577
+ function resolveExpression(x, c, level, contextPath, expression, rootSchema) {
578
+ const { operator, left, right, rightIsValue } = expression;
579
+ const resolvedLeft = pathResolver(left, contextPath, level, rootSchema)(x, c);
580
+ let resolvedRight = right;
581
+ if (!rightIsValue && typeof right === "string") {
582
+ resolvedRight = pathResolver(right, contextPath, level, rootSchema)(x, c);
583
+ }
584
+ switch (operator) {
585
+ case Operator.NOT: {
586
+ return !resolvedLeft;
587
+ }
588
+ case Operator.EQUALS: {
589
+ return resolvedLeft == resolvedRight;
590
+ }
591
+ case Operator.NOT_EQUALS: {
592
+ return resolvedLeft != resolvedRight;
593
+ }
594
+ case Operator.GREATER_THAN_OR_EQUALS: {
595
+ return resolvedLeft >= resolvedRight;
596
+ }
597
+ case Operator.GREATER_THAN: {
598
+ return resolvedLeft > resolvedRight;
599
+ }
600
+ case Operator.LESS_THAN_OR_EQUALS: {
601
+ return resolvedLeft <= resolvedRight;
602
+ }
603
+ case Operator.LESS_THAN: {
604
+ return resolvedLeft < resolvedRight;
605
+ }
606
+ default: {
607
+ if (typeof resolvedLeft === "boolean") {
608
+ return resolvedLeft;
609
+ }
610
+ if (typeof resolvedLeft === "number") {
611
+ return resolvedLeft !== 0;
612
+ }
613
+ if (typeof resolvedLeft === "string") {
614
+ return resolvedLeft.length > 0;
615
+ }
616
+ return !!resolvedLeft;
617
+ }
618
+ }
619
+ }
620
+ /**
621
+ * Resolve a chained expression by evaluating expressions linked with logical operators
622
+ * @param x - The current data context
623
+ * @param c - The parent context for accessing parent scope data
624
+ * @param level - The nesting level for context resolution
625
+ * @param contextPath - The current context path for property resolution
626
+ * @param expression - The chained expression object containing linked expressions
627
+ * @param rootSchema - The root JSON schema for data validation and navigation
628
+ * @returns The resolved boolean result of the chained expression
629
+ */
630
+ function resolveChainedExpression(x, c, level, contextPath, expression, rootSchema) {
631
+ const { expression: expr, next } = expression;
632
+ const resolvedLeft = resolveExpression(x, c, level, contextPath, expr, rootSchema);
633
+ if (next) {
634
+ const resolvedRight = resolveChainedExpression(x, c, level, contextPath, next, rootSchema);
635
+ switch (next.operator) {
636
+ case Operator.AND: {
637
+ return resolvedLeft && resolvedRight;
638
+ }
639
+ case Operator.OR: {
640
+ return resolvedLeft || resolvedRight;
641
+ }
642
+ }
643
+ }
644
+ return resolvedLeft;
645
+ }
646
+ /**
647
+ * This is the transform utility for rationalizing declarative HTML syntax
648
+ * with bindings in the ViewTemplate
649
+ * @param innerHTML The innerHTML to transform
650
+ * @param index The index to start the current slice of HTML to evaluate
651
+ */
652
+ export function transformInnerHTML(innerHTML, index = 0) {
653
+ const sliceToEvaluate = innerHTML.slice(index);
654
+ const nextBinding = getNextBehavior(sliceToEvaluate);
655
+ let transformedInnerHTML = innerHTML;
656
+ if (nextBinding && nextBinding.type === "dataBinding") {
657
+ if (nextBinding.bindingType === "unescaped") {
658
+ transformedInnerHTML = `${innerHTML.slice(0, index)}${sliceToEvaluate.slice(0, nextBinding.openingStartIndex)}${startInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.openingStartIndex + 3, nextBinding.closingStartIndex)}${endInnerHTMLDiv}${sliceToEvaluate.slice(nextBinding.closingStartIndex + 3)}`;
659
+ return transformInnerHTML(transformedInnerHTML, index +
660
+ startInnerHTMLDivLength +
661
+ endInnerHTMLDivLength +
662
+ nextBinding.closingStartIndex -
663
+ 3);
664
+ }
665
+ else if (nextBinding.bindingType === "client") {
666
+ return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
667
+ }
668
+ return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingEndIndex);
669
+ }
670
+ else if (nextBinding) {
671
+ return transformInnerHTML(transformedInnerHTML, index + nextBinding.closingTagEndIndex);
672
+ }
673
+ return transformedInnerHTML;
674
+ }
675
+ /**
676
+ * Resolves f-when
677
+ * @param self - Where the first item in the path path refers to the item itself (used by repeat).
678
+ * @param chainedExpression - The chained expression which includes the expression and the next expression
679
+ * if there is another in the chain
680
+ * @returns - A binding that resolves the chained expression logic
681
+ */
682
+ export function resolveWhen(rootPropertyName, expression, parentContext, level, schema) {
683
+ const binding = expressionResolver(rootPropertyName, expression, parentContext, level, schema);
684
+ return (x, c) => binding(x, c);
685
+ }
686
+ /**
687
+ * Determines the data type of the provided data
688
+ * @param data - The data to analyze
689
+ * @returns "array" for arrays, "object" for non-null objects, "primitive" for other types
690
+ */
691
+ function getDataType(data) {
692
+ if (Array.isArray(data))
693
+ return "array";
694
+ if (typeof data === "object" && data !== null)
695
+ return "object";
696
+ return "primitive";
697
+ }
698
+ /**
699
+ * Get properties from an anyOf array
700
+ * @param anyOf - The anyOf array in a JSON schema
701
+ * @returns The array item matching a ref if it exists
702
+ */
703
+ function getSchemaPropertiesFromAnyOf(anyOf) {
704
+ let propertiesFromAnyOf = null;
705
+ for (const anyOfItem of anyOf) {
706
+ if (anyOfItem[refPropertyName]) {
707
+ const splitRef = anyOfItem[refPropertyName].split("/");
708
+ const customElement = splitRef.slice(-2)[0];
709
+ const attributeName = splitRef.slice(-1)[0].slice(0, -5);
710
+ if (Schema.jsonSchemaMap.has(customElement)) {
711
+ const customElementSchemaMap = Schema.jsonSchemaMap.get(customElement);
712
+ propertiesFromAnyOf = customElementSchemaMap.get(attributeName);
713
+ }
714
+ }
715
+ }
716
+ return propertiesFromAnyOf;
717
+ }
718
+ /**
719
+ * Gets a properties definition if one exists
720
+ * @param schema - The JSON schema to get properties from
721
+ * @returns A JSON schema with properties or null
722
+ */
723
+ function getSchemaProperties(schema) {
724
+ if (schema === null || schema === void 0 ? void 0 : schema.properties) {
725
+ return schema.properties;
726
+ }
727
+ else if (schema === null || schema === void 0 ? void 0 : schema.anyOf) {
728
+ return getSchemaPropertiesFromAnyOf(schema.anyOf);
729
+ }
730
+ return null;
731
+ }
732
+ /**
733
+ * Assigns Observable properties to items in an array and sets up change notifications
734
+ * @param proxiedData - The array data to make observable
735
+ * @param schema - The schema defining the structure of array items
736
+ * @param rootSchema - The root schema for the entire data structure
737
+ * @returns The array with observable properties and change notifications
738
+ */
739
+ function assignObservablesToArray(proxiedData, schema, rootSchema, target, rootProperty) {
740
+ const data = proxiedData.map((item) => {
741
+ const originalItem = Object.assign({}, item);
742
+ assignProxyToItemsInArray(item, originalItem, schema, rootSchema);
743
+ return Object.assign(item, originalItem);
744
+ });
745
+ Observable.getNotifier(data).subscribe({
746
+ handleChange(subject, args) {
747
+ args.forEach((arg) => {
748
+ if (arg.addedCount > 0) {
749
+ for (let i = arg.addedCount - 1; i >= 0; i--) {
750
+ const item = subject[arg.index + i];
751
+ const originalItem = Object.assign({}, item);
752
+ assignProxyToItemsInArray(item, originalItem, schema, rootSchema);
753
+ Object.assign(item, originalItem);
754
+ }
755
+ // Notify observers of the target object's root property
756
+ Observable.notify(target, rootProperty);
757
+ }
758
+ });
759
+ },
760
+ });
761
+ return data;
762
+ }
763
+ /**
764
+ * Extracts the definition name from a JSON Schema $ref property
765
+ * @param defName - The $ref string (e.g., "#/$defs/MyType")
766
+ * @returns The definition name (e.g., "MyType")
767
+ */
768
+ function getDefFromRef(defName) {
769
+ const splitName = defName.split("/");
770
+ return splitName.at(-1);
771
+ }
772
+ /**
773
+ * Find a definition
774
+ * This may exist as a $ref at the root or as a $ref in any anyOf or not at all
775
+ * if the Observer Map has not been enabled on a child component
776
+ * @param schema - The JSON schema to find the ref in
777
+ * @returns The definition or null
778
+ */
779
+ export function findDef(schema) {
780
+ const defStartingString = "#/$defs";
781
+ if (schema[refPropertyName] &&
782
+ schema[refPropertyName].startsWith(defStartingString)) {
783
+ return getDefFromRef(schema[refPropertyName]);
784
+ }
785
+ if (schema.anyOf) {
786
+ const index = schema.anyOf.findIndex((anyOfItem) => {
787
+ return (!!anyOfItem[refPropertyName] &&
788
+ anyOfItem[refPropertyName].startsWith(defStartingString));
789
+ });
790
+ if (index > -1) {
791
+ const ref = schema.anyOf[index][refPropertyName];
792
+ if (ref.startsWith(defStartingString)) {
793
+ return getDefFromRef(ref);
794
+ }
795
+ }
796
+ }
797
+ return null;
798
+ }
799
+ /**
800
+ * Subscribe to a notifier on data that is an observed array
801
+ * @param data - The array being observed
802
+ * @param updateArrayObservables - The function to call to update the array item
803
+ */
804
+ function assignSubscribeToObservableArray(data, updateArrayObservables) {
805
+ Observable.getNotifier(data).subscribe({
806
+ handleChange(subject, args) {
807
+ args.forEach((arg) => {
808
+ updateArrayObservables();
809
+ });
810
+ },
811
+ });
812
+ }
813
+ /**
814
+ * Assign observables to data
815
+ * @param schema - The schema
816
+ * @param rootSchema - The root schema mapping to the root property
817
+ * @param data - The data
818
+ * @param target - The target custom element
819
+ * @param rootProperty - The root property
820
+ * @returns
821
+ */
822
+ export function assignObservables(schema, rootSchema, data, target, rootProperty) {
823
+ var _a;
824
+ const dataType = getDataType(data);
825
+ let proxiedData = data;
826
+ switch (dataType) {
827
+ case "array": {
828
+ const context = findDef(schema);
829
+ if (context) {
830
+ proxiedData = assignObservablesToArray(proxiedData, (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context], rootSchema, target, rootProperty);
831
+ if (!observedArraysMap.has(proxiedData)) {
832
+ observedArraysMap.set(proxiedData, assignSubscribeToObservableArray(proxiedData, () => {
833
+ var _a;
834
+ return assignObservablesToArray(proxiedData, (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context], rootSchema, target, rootProperty);
835
+ }));
836
+ }
837
+ }
838
+ break;
839
+ }
840
+ case "object": {
841
+ proxiedData = assignProxyToItemsInObject(target, rootProperty, proxiedData, schema, rootSchema);
842
+ break;
843
+ }
844
+ }
845
+ return proxiedData;
846
+ }
847
+ /**
848
+ * Assign a proxy to items in an array
849
+ * @param proxiableItem - The array item to proxy
850
+ * @param originalItem - The original array item
851
+ * @param schema - The schema mapping to the items in the array
852
+ * @param rootSchema - The root schema assigned to the root property
853
+ */
854
+ function assignProxyToItemsInArray(proxiableItem, originalItem, schema, rootSchema) {
855
+ const schemaProperties = getSchemaProperties(schema);
856
+ getObjectProperties(proxiableItem, schemaProperties).forEach(key => {
857
+ // Initialize the property as undefined if it doesn't exist
858
+ if (!(key in originalItem)) {
859
+ originalItem[key] = undefined;
860
+ }
861
+ // Assign the proxy first
862
+ originalItem[key] = assignProxyToItemsInObject(proxiableItem, key, originalItem[key], schemaProperties[key], rootSchema);
863
+ // Then make the property observable
864
+ Observable.defineProperty(proxiableItem, key);
865
+ });
866
+ }
867
+ /**
868
+ * Get an objects properties as agreed upon between the schema and data
869
+ * @param data - The data
870
+ * @param schemaProperties - The schema properties
871
+ * @returns A list of strings the schema properties enumerate (includes properties not present in data)
872
+ */
873
+ function getObjectProperties(data, schemaProperties) {
874
+ const dataKeys = Object.keys(data);
875
+ const schemaPropertyKeys = Object.keys(schemaProperties !== null && schemaProperties !== void 0 ? schemaProperties : {});
876
+ // Return all schema properties that are either in the data or in the schema
877
+ // This ensures properties defined in schema but missing from data get initialized
878
+ const allKeys = new Set([...dataKeys, ...schemaPropertyKeys]);
879
+ return Array.from(allKeys).filter(function (key) {
880
+ return schemaPropertyKeys.indexOf(key) !== -1;
881
+ });
882
+ }
883
+ /**
884
+ * Assign a proxy to items in an object
885
+ * @param target - The target custom element
886
+ * @param rootProperty - The root property
887
+ * @param data - The data to proxy
888
+ * @param schema - The schema for the data
889
+ * @param rootSchema - The root schema for the root property
890
+ * @returns a Proxy
891
+ */
892
+ function assignProxyToItemsInObject(target, rootProperty, data, schema, rootSchema) {
893
+ var _a;
894
+ const type = getDataType(data);
895
+ const schemaProperties = getSchemaProperties(schema);
896
+ let proxiedData = data;
897
+ if (type === "object" && schemaProperties) {
898
+ // navigate through all items in the object
899
+ getObjectProperties(proxiedData, schemaProperties).forEach(property => {
900
+ proxiedData[property] = assignProxyToItemsInObject(target, rootProperty, proxiedData[property], schemaProperties[property], rootSchema);
901
+ });
902
+ // assign a Proxy to the object
903
+ proxiedData = assignProxy(schema, rootSchema, target, rootProperty, proxiedData);
904
+ // Add this target to the object's target list
905
+ addTargetToObject(proxiedData, target, rootProperty);
906
+ }
907
+ else if (type === "array") {
908
+ const context = findDef(schema.items);
909
+ if (context) {
910
+ const definition = (_a = rootSchema[defsPropertyName]) === null || _a === void 0 ? void 0 : _a[context];
911
+ if ((definition === null || definition === void 0 ? void 0 : definition.type) === "object") {
912
+ proxiedData = assignObservablesToArray(proxiedData, definition, rootSchema, target, rootProperty);
913
+ if (!observedArraysMap.has(proxiedData)) {
914
+ observedArraysMap.set(proxiedData, assignObservablesToArray(proxiedData, definition, rootSchema, target, rootProperty));
915
+ }
916
+ }
917
+ }
918
+ }
919
+ return proxiedData;
920
+ }
921
+ /**
922
+ * Add a target to an object's target list
923
+ * @param object - The object to associate with the target
924
+ * @param target - The target custom element
925
+ * @param rootProperty - The root property name
926
+ */
927
+ function addTargetToObject(object, target, rootProperty) {
928
+ if (!objectTargetsMap.has(object)) {
929
+ objectTargetsMap.set(object, []);
930
+ }
931
+ const targets = objectTargetsMap.get(object);
932
+ targets.push({ target, rootProperty });
933
+ }
934
+ /**
935
+ * Get all targets for an object
936
+ * @param object - The object to get targets for
937
+ * @returns Array of target info objects
938
+ */
939
+ function getTargetsForObject(object) {
940
+ return objectTargetsMap.get(object) || [];
941
+ }
942
+ /**
943
+ * Notify any observables mapped to the object
944
+ * @param targetObject The object that is mapped to a target and rootProperty
945
+ */
946
+ function notifyObservables(targetObject) {
947
+ getTargetsForObject(targetObject).forEach((targetItem) => {
948
+ // Trigger notification for property changes
949
+ Observable.notify(targetItem.target, targetItem.rootProperty);
950
+ });
951
+ }
952
+ /**
953
+ * Assign a proxy to an object
954
+ * @param schema - The current schema
955
+ * @param rootSchema - The root schema for the root property
956
+ * @param target - The target custom element
957
+ * @param rootProperty - The root property
958
+ * @param object - The object to assign the proxy to
959
+ * @returns Proxy object
960
+ */
961
+ export function assignProxy(schema, rootSchema, target, rootProperty, object) {
962
+ if (!object.$isProxy) {
963
+ // Create a proxy for the object that triggers Observable.notify on mutations
964
+ const proxy = new Proxy(object, {
965
+ set: (obj, prop, value) => {
966
+ const currentValue = obj[prop];
967
+ if (deepEqual(currentValue, value)) {
968
+ return true;
969
+ }
970
+ obj[prop] = assignObservables(schema, rootSchema, value, target, rootProperty);
971
+ notifyObservables(proxy);
972
+ return true;
973
+ },
974
+ get: (target, key) => {
975
+ if (key !== "$isProxy") {
976
+ return target[key];
977
+ }
978
+ return true;
979
+ },
980
+ deleteProperty: (obj, prop) => {
981
+ if (prop in obj) {
982
+ delete obj[prop];
983
+ notifyObservables(proxy);
984
+ return true;
985
+ }
986
+ return false;
987
+ },
988
+ });
989
+ return proxy;
990
+ }
991
+ return object;
992
+ }
993
+ /**
994
+ * Get the root property name
995
+ * @param rootPropertyName - The root property
996
+ * @param path - The dot syntax path
997
+ * @param context - The context created by a repeat
998
+ * @param type - The type of path binding
999
+ * @returns
1000
+ */
1001
+ export function getRootPropertyName(rootPropertyName, path, context, type) {
1002
+ return (rootPropertyName === null || context === null) && type !== "event"
1003
+ ? path.split(".")[0]
1004
+ : rootPropertyName;
1005
+ }
1006
+ /**
1007
+ * Get details of bindings to the attributes of child custom elements
1008
+ * @param previousString - The previous string before the binding
1009
+ * @returns null, or a custom element name and attribute name
1010
+ */
1011
+ export function getChildrenMap(previousString) {
1012
+ if (typeof previousString === "string" &&
1013
+ isAttribute(previousString, previousString.length)) {
1014
+ const customElementName = getAttributesCustomElementName(previousString);
1015
+ if (customElementName) {
1016
+ return {
1017
+ customElementName,
1018
+ attributeName: getAttributeName(previousString),
1019
+ };
1020
+ }
1021
+ }
1022
+ return null;
1023
+ }
1024
+ /**
1025
+ * Get the HTML element that is passing the attribute binding
1026
+ * @param previousString - The previous string before the binding
1027
+ * @returns null if this is not a custom element, or the custom element that is passing the binding as an attribute
1028
+ */
1029
+ function getAttributesCustomElementName(previousString) {
1030
+ const indexOfElementTagStart = previousString.lastIndexOf("<") + 1;
1031
+ const indexOfElementTagEnd = previousString.slice(indexOfElementTagStart).indexOf(" ") +
1032
+ indexOfElementTagStart;
1033
+ const elementName = previousString.slice(indexOfElementTagStart, indexOfElementTagEnd);
1034
+ if (elementName.includes("-")) {
1035
+ return elementName;
1036
+ }
1037
+ return null;
1038
+ }
1039
+ /**
1040
+ * Gets a non-aspected attribute name
1041
+ * @param previousString - The previous string before the binding
1042
+ * @returns The attribute name with any aspects (:, ?, @) removed
1043
+ */
1044
+ function getAttributeName(previousString) {
1045
+ const indexOfAttributeStart = previousString.lastIndexOf(" ") + 1;
1046
+ const indexOfAttributeEnd = previousString.slice(indexOfAttributeStart).indexOf("=") + indexOfAttributeStart;
1047
+ const attributeName = previousString.slice(indexOfAttributeStart, indexOfAttributeEnd);
1048
+ const potentialAspect = attributeName.charAt(0);
1049
+ if (potentialAspect === ":" || potentialAspect === "@" || potentialAspect === "?") {
1050
+ return attributeName.slice(1);
1051
+ }
1052
+ return attributeName;
1053
+ }
1054
+ /**
1055
+ * Determine if an object has an observable accessor for a backing field
1056
+ * @param object - The object to check
1057
+ * @param backingField - The backing field name
1058
+ * @returns True if the object has an observable accessor for the backing field, false otherwise
1059
+ */
1060
+ function hasObservableAccessor(object, backingField) {
1061
+ const accessors = Observable.getAccessors(object);
1062
+ if (!accessors) {
1063
+ return false;
1064
+ }
1065
+ return accessors.some((accessor) => accessor.name === backingField);
1066
+ }
1067
+ /**
1068
+ * Determine if a key should be skipped during deep comparison
1069
+ *
1070
+ * @param object - The object to check
1071
+ * @param key - The key to evaluate
1072
+ * @returns True if the key should be skipped during comparison, false otherwise
1073
+ */
1074
+ function shouldSkipKey(object, key) {
1075
+ if (key[0] !== "_" || key === "_") {
1076
+ return false;
1077
+ }
1078
+ return hasObservableAccessor(object, key.slice(1));
1079
+ }
1080
+ /**
1081
+ * Get comparable keys from an object, excluding those that should be skipped
1082
+ *
1083
+ * @param object - The object to extract keys from
1084
+ * @returns An array of keys that should be compared
1085
+ */
1086
+ function getComparableKeys(object) {
1087
+ const hasOwn = Object.prototype.hasOwnProperty;
1088
+ const keys = [];
1089
+ for (const key in object) {
1090
+ if (!hasOwn.call(object, key) || shouldSkipKey(object, key)) {
1091
+ continue;
1092
+ }
1093
+ keys.push(key);
1094
+ }
1095
+ return keys;
1096
+ }
1097
+ /**
1098
+ * Deeply compares two objects for equality.
1099
+ *
1100
+ * @param obj1 - First object to compare
1101
+ * @param obj2 - Second object to compare
1102
+ * @returns True if the objects are deeply equal, false otherwise
1103
+ */
1104
+ export function deepEqual(obj1, obj2) {
1105
+ if (Object.is(obj1, obj2)) {
1106
+ return true;
1107
+ }
1108
+ if (obj1 == null || obj2 == null) {
1109
+ return false;
1110
+ }
1111
+ const type1 = typeof obj1;
1112
+ const type2 = typeof obj2;
1113
+ if (type1 !== type2 || type1 !== "object") {
1114
+ return false;
1115
+ }
1116
+ const isArray1 = Array.isArray(obj1);
1117
+ const isArray2 = Array.isArray(obj2);
1118
+ if (isArray1 !== isArray2) {
1119
+ return false;
1120
+ }
1121
+ if (isArray1) {
1122
+ const len = obj1.length;
1123
+ if (len !== obj2.length) {
1124
+ return false;
1125
+ }
1126
+ for (let i = 0; i < len; i++) {
1127
+ if (!deepEqual(obj1[i], obj2[i])) {
1128
+ return false;
1129
+ }
1130
+ }
1131
+ return true;
1132
+ }
1133
+ const hasOwn = Object.prototype.hasOwnProperty;
1134
+ const obj1Keys = getComparableKeys(obj1);
1135
+ const obj2Keys = getComparableKeys(obj2);
1136
+ if (obj1Keys.length !== obj2Keys.length) {
1137
+ return false;
1138
+ }
1139
+ for (const key of obj1Keys) {
1140
+ if (!hasOwn.call(obj2, key) || !deepEqual(obj1[key], obj2[key])) {
1141
+ return false;
1142
+ }
1143
+ }
1144
+ return true;
1145
+ }
1146
+ /**
1147
+ * Checks if a value is a plain object (not an array, null, or other type).
1148
+ *
1149
+ * @param value - The value to check
1150
+ * @returns True if the value is a plain object, false otherwise
1151
+ */
1152
+ export function isPlainObject(value) {
1153
+ return !!value && typeof value === "object" && !Array.isArray(value);
1154
+ }
1155
+ /**
1156
+ * Deeply merges the source object into the target object.
1157
+ *
1158
+ * @param target - The target object to merge into
1159
+ * @param source - The source object to merge from
1160
+ * @returns boolean indicating whether changes were made
1161
+ */
1162
+ export function deepMerge(target, source) {
1163
+ const hasOwn = Object.prototype.hasOwnProperty;
1164
+ let hasChanges = false;
1165
+ for (const key in source) {
1166
+ if (!hasOwn.call(source, key)) {
1167
+ continue;
1168
+ }
1169
+ const sourceValue = source[key];
1170
+ if (sourceValue === void 0) {
1171
+ continue;
1172
+ }
1173
+ const targetValue = target[key];
1174
+ if (deepEqual(targetValue, sourceValue)) {
1175
+ continue;
1176
+ }
1177
+ hasChanges = true;
1178
+ if (Array.isArray(sourceValue)) {
1179
+ const isTargetArray = Array.isArray(targetValue);
1180
+ const clonedItems = sourceValue.map((item) => isPlainObject(item) ? { ...item } : item);
1181
+ if (isTargetArray) {
1182
+ // Use splice to maintain observable array tracking
1183
+ targetValue.splice(0, targetValue.length, ...clonedItems);
1184
+ }
1185
+ else {
1186
+ // Target isn't an array, replace it
1187
+ target[key] = clonedItems;
1188
+ }
1189
+ continue;
1190
+ }
1191
+ if (isPlainObject(sourceValue)) {
1192
+ const targetIsObject = isPlainObject(targetValue);
1193
+ const nextTarget = targetIsObject ? { ...targetValue } : {};
1194
+ const nestedChanged = deepMerge(nextTarget, sourceValue);
1195
+ if (!targetIsObject) {
1196
+ target[key] = nextTarget;
1197
+ continue;
1198
+ }
1199
+ if (nestedChanged) {
1200
+ target[key] = nextTarget;
1201
+ }
1202
+ continue;
1203
+ }
1204
+ target[key] = sourceValue;
1205
+ }
1206
+ return hasChanges;
1207
+ }