@adaas/are-html 0.0.22 → 0.0.23

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 (129) hide show
  1. package/dist/browser/index.d.mts +176 -8
  2. package/dist/browser/index.mjs +661 -235
  3. package/dist/browser/index.mjs.map +1 -1
  4. package/dist/node/{AreBinding.attribute-doUvtOjc.d.mts → AreBinding.attribute-BWzEIw6H.d.mts} +45 -0
  5. package/dist/node/{AreBinding.attribute-Bm5LlOyE.d.ts → AreBinding.attribute-GpT-5Qmf.d.ts} +45 -0
  6. package/dist/node/attributes/AreBinding.attribute.d.mts +1 -1
  7. package/dist/node/attributes/AreBinding.attribute.d.ts +1 -1
  8. package/dist/node/attributes/AreDirective.attribute.d.mts +1 -1
  9. package/dist/node/attributes/AreDirective.attribute.d.ts +1 -1
  10. package/dist/node/attributes/AreEvent.attribute.d.mts +1 -1
  11. package/dist/node/attributes/AreEvent.attribute.d.ts +1 -1
  12. package/dist/node/attributes/AreStatic.attribute.d.mts +1 -1
  13. package/dist/node/attributes/AreStatic.attribute.d.ts +1 -1
  14. package/dist/node/directives/AreDirectiveFor.directive.d.mts +18 -1
  15. package/dist/node/directives/AreDirectiveFor.directive.d.ts +18 -1
  16. package/dist/node/directives/AreDirectiveFor.directive.js +57 -9
  17. package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
  18. package/dist/node/directives/AreDirectiveFor.directive.mjs +57 -9
  19. package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
  20. package/dist/node/directives/AreDirectiveIf.directive.d.mts +1 -1
  21. package/dist/node/directives/AreDirectiveIf.directive.d.ts +1 -1
  22. package/dist/node/directives/AreDirectiveShow.directive.d.mts +1 -1
  23. package/dist/node/directives/AreDirectiveShow.directive.d.ts +1 -1
  24. package/dist/node/engine/AreHTML.compiler.d.mts +1 -1
  25. package/dist/node/engine/AreHTML.compiler.d.ts +1 -1
  26. package/dist/node/engine/AreHTML.compiler.js +4 -0
  27. package/dist/node/engine/AreHTML.compiler.js.map +1 -1
  28. package/dist/node/engine/AreHTML.compiler.mjs +4 -0
  29. package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
  30. package/dist/node/engine/AreHTML.constants.d.mts +33 -1
  31. package/dist/node/engine/AreHTML.constants.d.ts +33 -1
  32. package/dist/node/engine/AreHTML.constants.js +166 -0
  33. package/dist/node/engine/AreHTML.constants.js.map +1 -1
  34. package/dist/node/engine/AreHTML.constants.mjs +165 -1
  35. package/dist/node/engine/AreHTML.constants.mjs.map +1 -1
  36. package/dist/node/engine/AreHTML.context.d.mts +66 -0
  37. package/dist/node/engine/AreHTML.context.d.ts +66 -0
  38. package/dist/node/engine/AreHTML.context.js +98 -0
  39. package/dist/node/engine/AreHTML.context.js.map +1 -1
  40. package/dist/node/engine/AreHTML.context.mjs +98 -0
  41. package/dist/node/engine/AreHTML.context.mjs.map +1 -1
  42. package/dist/node/engine/AreHTML.interpreter.d.mts +3 -0
  43. package/dist/node/engine/AreHTML.interpreter.d.ts +3 -0
  44. package/dist/node/engine/AreHTML.interpreter.js +66 -10
  45. package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
  46. package/dist/node/engine/AreHTML.interpreter.mjs +66 -10
  47. package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
  48. package/dist/node/engine/AreHTML.lifecycle.d.mts +1 -8
  49. package/dist/node/engine/AreHTML.lifecycle.d.ts +1 -8
  50. package/dist/node/engine/AreHTML.lifecycle.js +29 -44
  51. package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
  52. package/dist/node/engine/AreHTML.lifecycle.mjs +29 -44
  53. package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
  54. package/dist/node/engine/AreHTML.tokenizer.d.mts +1 -1
  55. package/dist/node/engine/AreHTML.tokenizer.d.ts +1 -1
  56. package/dist/node/engine/AreHTML.tokenizer.js +7 -1
  57. package/dist/node/engine/AreHTML.tokenizer.js.map +1 -1
  58. package/dist/node/engine/AreHTML.tokenizer.mjs +7 -1
  59. package/dist/node/engine/AreHTML.tokenizer.mjs.map +1 -1
  60. package/dist/node/engine/AreHTML.transformer.d.mts +1 -1
  61. package/dist/node/engine/AreHTML.transformer.d.ts +1 -1
  62. package/dist/node/index.d.mts +4 -3
  63. package/dist/node/index.d.ts +4 -3
  64. package/dist/node/index.js +7 -0
  65. package/dist/node/index.mjs +1 -0
  66. package/dist/node/instructions/AddStaticHTML.instruction.d.mts +8 -0
  67. package/dist/node/instructions/AddStaticHTML.instruction.d.ts +8 -0
  68. package/dist/node/instructions/AddStaticHTML.instruction.js +31 -0
  69. package/dist/node/instructions/AddStaticHTML.instruction.js.map +1 -0
  70. package/dist/node/instructions/AddStaticHTML.instruction.mjs +24 -0
  71. package/dist/node/instructions/AddStaticHTML.instruction.mjs.map +1 -0
  72. package/dist/node/instructions/AreHTML.instructions.constants.d.mts +1 -0
  73. package/dist/node/instructions/AreHTML.instructions.constants.d.ts +1 -0
  74. package/dist/node/instructions/AreHTML.instructions.constants.js +1 -0
  75. package/dist/node/instructions/AreHTML.instructions.constants.js.map +1 -1
  76. package/dist/node/instructions/AreHTML.instructions.constants.mjs +1 -0
  77. package/dist/node/instructions/AreHTML.instructions.constants.mjs.map +1 -1
  78. package/dist/node/instructions/AreHTML.instructions.types.d.mts +9 -1
  79. package/dist/node/instructions/AreHTML.instructions.types.d.ts +9 -1
  80. package/dist/node/lib/AreDirective/AreDirective.component.d.mts +1 -1
  81. package/dist/node/lib/AreDirective/AreDirective.component.d.ts +1 -1
  82. package/dist/node/lib/AreDirective/AreDirective.types.d.mts +1 -1
  83. package/dist/node/lib/AreDirective/AreDirective.types.d.ts +1 -1
  84. package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.mts +1 -1
  85. package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.ts +1 -1
  86. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.mts +1 -1
  87. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.ts +1 -1
  88. package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.mts +1 -1
  89. package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.ts +1 -1
  90. package/dist/node/lib/AreHTMLNode/AreHTMLNode.js +51 -0
  91. package/dist/node/lib/AreHTMLNode/AreHTMLNode.js.map +1 -1
  92. package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs +51 -0
  93. package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs.map +1 -1
  94. package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
  95. package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
  96. package/dist/node/nodes/AreComment.d.mts +1 -1
  97. package/dist/node/nodes/AreComment.d.ts +1 -1
  98. package/dist/node/nodes/AreComponent.d.mts +1 -1
  99. package/dist/node/nodes/AreComponent.d.ts +1 -1
  100. package/dist/node/nodes/AreInterpolation.d.mts +1 -1
  101. package/dist/node/nodes/AreInterpolation.d.ts +1 -1
  102. package/dist/node/nodes/AreRoot.d.mts +1 -1
  103. package/dist/node/nodes/AreRoot.d.ts +1 -1
  104. package/dist/node/nodes/AreText.d.mts +1 -1
  105. package/dist/node/nodes/AreText.d.ts +1 -1
  106. package/examples/dashboard/concept.ts +1 -1
  107. package/examples/dashboard/dist/index.html +1 -1
  108. package/examples/dashboard/dist/{mqh9ryml-xat335.js → mqiw5sqa-ypckmj.js} +403 -57
  109. package/examples/for-perf/dist/index.html +1 -1
  110. package/examples/for-perf/dist/{mqh9ryfo-6a8d0o.js → mqj1mpf2-z4aokv.js} +558 -117
  111. package/examples/for-perf/dist/{mqh9ryfq-4pf5cv.js → mqj1mpff-4fr7mw.js} +558 -117
  112. package/examples/signal-routing/dist/index.html +1 -1
  113. package/examples/signal-routing/dist/{mqh9ryc9-dkcbkx.js → mqiwo23h-bhcolu.js} +413 -60
  114. package/package.json +5 -5
  115. package/src/directives/AreDirectiveFor.directive.ts +99 -16
  116. package/src/engine/AreHTML.compiler.ts +13 -0
  117. package/src/engine/AreHTML.constants.ts +142 -0
  118. package/src/engine/AreHTML.context.ts +112 -0
  119. package/src/engine/AreHTML.interpreter.ts +114 -13
  120. package/src/engine/AreHTML.lifecycle.ts +81 -74
  121. package/src/engine/AreHTML.tokenizer.ts +30 -1
  122. package/src/index.ts +1 -0
  123. package/src/instructions/AddStaticHTML.instruction.ts +23 -0
  124. package/src/instructions/AreHTML.instructions.constants.ts +1 -0
  125. package/src/instructions/AreHTML.instructions.types.ts +9 -0
  126. package/src/lib/AreHTMLNode/AreHTMLNode.ts +74 -0
  127. package/src/lib/AreRoot/AreRoot.component.ts +3 -3
  128. package/tests/StaticIsland.test.ts +115 -0
  129. package/examples/for-perf/dist/mqh9ryde-m243t8.js +0 -15223
@@ -208,6 +208,7 @@ var AreHTMLInstructions = {
208
208
  AddListener: "_AreHTML_AddListener",
209
209
  AddInterpolation: "_AreHTML_AddInterpolation",
210
210
  AddComment: "_AreHTML_AddComment",
211
+ AddStaticHTML: "_AreHTML_AddStaticHTML",
211
212
  HideElement: "_AreHTML_HideElement"
212
213
  };
213
214
 
@@ -272,6 +273,279 @@ var AreSchedulerHelper = class {
272
273
  };
273
274
  /** FIFO queue of callbacks waiting for their posted macrotask to fire. */
274
275
  AreSchedulerHelper._queue = [];
276
+ var AreHTMLEngineContext = class extends AreContext {
277
+ constructor(props) {
278
+ super(props.container?.body.innerHTML || props.source || "");
279
+ /**
280
+ * Index structure mapping:
281
+ *
282
+ * Node -> Group ID -> Element
283
+ * -----------------------------------------------------------------------------------
284
+ * | - Attribute | group: string | Node
285
+ * | - Directive (e.g. for) | | Node
286
+ */
287
+ this.index = {
288
+ /**
289
+ * 1 AreNode = 1 Dom Node
290
+ *
291
+ * uses ASEID
292
+ */
293
+ nodeToHostElements: /* @__PURE__ */ new Map(),
294
+ /**
295
+ * 1 Group Instruction = MANY Dom Nodes (e.g. for loop)
296
+ *
297
+ * uses ASEID
298
+ */
299
+ groupToElements: /* @__PURE__ */ new Map(),
300
+ /**
301
+ * 1 Dom Node = 1 Instruction
302
+ *
303
+ * uses ASEID
304
+ */
305
+ elementToInstruction: /* @__PURE__ */ new WeakMap(),
306
+ /**
307
+ * 1 Instruction = 1 Dom Node (for CreateElement instructions, for example)
308
+ *
309
+ * uses ASEID
310
+ */
311
+ instructionToElement: /* @__PURE__ */ new Map(),
312
+ /**
313
+ * Event listeners attached to elements, used for proper cleanup when reverting instructions. Maps a DOM element to a map of event names and their corresponding listeners, allowing the engine to track which listeners are attached to which elements and remove them when necessary (e.g., when an instruction is reverted).
314
+ */
315
+ elementListeners: /* @__PURE__ */ new WeakMap()
316
+ };
317
+ /**
318
+ * Parsed-fragment cache for static islands (see AddStaticHTMLInstruction).
319
+ *
320
+ * Keyed by `hostTag\u0000markup`, each entry holds a `DocumentFragment` whose
321
+ * children were parsed by the browser exactly once — in the *correct element
322
+ * context* (the host tag), so table fragments (`<tr>`, `<td>`, …) and other
323
+ * context-sensitive content parse correctly. Repeated static islands with
324
+ * identical markup (e.g. list rows, reused components) clone the pre-parsed
325
+ * fragment instead of re-parsing the HTML string on every mount — turning an
326
+ * O(parse) operation into an O(clone) one.
327
+ */
328
+ this._staticFragmentCache = /* @__PURE__ */ new Map();
329
+ /**
330
+ * Live-DOM attachments deferred while a mount pass is batching.
331
+ *
332
+ * A freshly-mounted subtree is built inside a *detached* root element, so
333
+ * every descendant `appendChild`/`insertBefore` happens off-document and
334
+ * triggers zero layout/paint invalidation. The single mutation that actually
335
+ * connects the built subtree to the live document is deferred and collected
336
+ * here, then flushed once when the batch closes — collapsing O(nodes) reflows
337
+ * into O(1) per mount root.
338
+ */
339
+ this._pendingAttachments = [];
340
+ /**
341
+ * Depth of the currently open batching scopes. Re-entrant so that nested
342
+ * `beginBatch`/`endBatch` pairs flush exactly once, when the outermost scope
343
+ * closes.
344
+ */
345
+ this._batchDepth = 0;
346
+ this._container = props.container;
347
+ }
348
+ get container() {
349
+ return this._container;
350
+ }
351
+ /**
352
+ * `true` while a synchronous mount pass is batching live-DOM attachments.
353
+ * Interpreter handlers consult this to decide whether to attach an element
354
+ * immediately or hand the attachment to {@link deferAttach}.
355
+ */
356
+ get isBatching() {
357
+ return this._batchDepth > 0;
358
+ }
359
+ /**
360
+ * Opens a batching scope. Re-entrant: only the outermost matching
361
+ * {@link endBatch} flushes the deferred attachments, so a single mount pass
362
+ * connects its built subtree to the live DOM exactly once.
363
+ */
364
+ beginBatch() {
365
+ this._batchDepth++;
366
+ }
367
+ /**
368
+ * Registers a live-DOM attachment to run when the current batch flushes. If
369
+ * no batch is active the attachment runs immediately, preserving the original
370
+ * synchronous behaviour for updates that mount outside a batch.
371
+ *
372
+ * @param attach the DOM mutation that connects a built subtree to the document
373
+ */
374
+ deferAttach(attach) {
375
+ if (this._batchDepth > 0) {
376
+ this._pendingAttachments.push(attach);
377
+ } else {
378
+ attach();
379
+ }
380
+ }
381
+ /**
382
+ * Closes a batching scope. When the outermost scope closes, every deferred
383
+ * attachment runs in registration (document) order, connecting the built
384
+ * subtrees to the live DOM in a single pass.
385
+ */
386
+ endBatch() {
387
+ if (this._batchDepth === 0) return;
388
+ this._batchDepth--;
389
+ if (this._batchDepth > 0) return;
390
+ const pending = this._pendingAttachments;
391
+ this._pendingAttachments = [];
392
+ for (let i = 0; i < pending.length; i++) {
393
+ pending[i]();
394
+ }
395
+ }
396
+ /**
397
+ * Returns a `DocumentFragment` containing the parsed form of `html`, parsed
398
+ * once in the context of `hostTag` (so context-sensitive content such as
399
+ * table rows/cells parses correctly) and cached thereafter. Callers should
400
+ * `cloneNode(true)` the returned fragment rather than mutating it, so the
401
+ * cache stays reusable.
402
+ *
403
+ * @param hostTag the tag name of the element the markup will be injected into
404
+ * @param html verbatim static-island inner markup
405
+ */
406
+ getStaticFragment(hostTag, html) {
407
+ const key = `${hostTag}\0${html}`;
408
+ let fragment = this._staticFragmentCache.get(key);
409
+ if (!fragment) {
410
+ const container = this._container.createElement(hostTag);
411
+ container.innerHTML = html;
412
+ fragment = this._container.createDocumentFragment();
413
+ while (container.firstChild) {
414
+ fragment.appendChild(container.firstChild);
415
+ }
416
+ this._staticFragmentCache.set(key, fragment);
417
+ }
418
+ return fragment;
419
+ }
420
+ getNodeElement(node) {
421
+ if (typeof node === "string") {
422
+ return this.index.nodeToHostElements.get(node);
423
+ } else {
424
+ return this.index.nodeToHostElements.get(node.aseid.toString());
425
+ }
426
+ }
427
+ /**
428
+ * Associates a DOM element with a given instruction and its owner node. This method updates the context's index to map the instruction's ASEID to the provided DOM element, and also maps the element back to the instruction's ASEID for reverse lookup. If the instruction has an owner node, it also maps the node's ASEID to the element. Additionally, if the instruction belongs to a group, it adds the element to the set of elements associated with that group. This indexing allows the engine to efficiently manage and update DOM elements based on instructions and their corresponding nodes, enabling dynamic rendering and interaction in response to application state changes.
429
+ *
430
+ * @param instruction
431
+ * @param element
432
+ */
433
+ setInstructionElement(instruction, element) {
434
+ const node = instruction.owner;
435
+ this.index.instructionToElement.set(instruction.aseid.toString(), element);
436
+ this.index.elementToInstruction.set(element, instruction.aseid.toString());
437
+ if (node && instruction instanceof AreDeclaration) {
438
+ this.index.nodeToHostElements.set(node.aseid.toString(), element);
439
+ }
440
+ if (instruction.group) {
441
+ const groupId = instruction.group;
442
+ if (!this.index.groupToElements.has(groupId)) {
443
+ this.index.groupToElements.set(groupId, /* @__PURE__ */ new Set());
444
+ }
445
+ this.index.groupToElements.get(groupId).add(element);
446
+ }
447
+ }
448
+ getElementByInstruction(instruction) {
449
+ if (typeof instruction === "string") {
450
+ return this.index.instructionToElement.get(instruction);
451
+ } else {
452
+ return this.index.instructionToElement.get(instruction.aseid.toString());
453
+ }
454
+ }
455
+ /**
456
+ * Removes the association between a given instruction and its corresponding DOM element. This method looks up the instruction's ASEID to find the associated DOM element, and if found, it deletes the mapping from both instructionToElement and elementToInstruction. If the instruction has an owner node, it also removes the mapping from nodeToHostElements. Additionally, if the instruction belongs to a group, it removes the element from the set of elements associated with that group, and if the group has no more elements, it deletes the group from the index. This cleanup is essential for maintaining an accurate and efficient mapping of instructions to DOM elements, especially when instructions are reverted or when nodes are removed from the DOM.
457
+ *
458
+ * @param instruction
459
+ */
460
+ removeInstructionElement(instruction) {
461
+ const element = this.index.instructionToElement.get(instruction.aseid.toString());
462
+ if (element) {
463
+ this.index.instructionToElement.delete(instruction.aseid.toString());
464
+ this.index.elementToInstruction.delete(element);
465
+ const node = instruction.owner;
466
+ if (node && instruction instanceof AreDeclaration) {
467
+ this.index.nodeToHostElements.delete(node.aseid.toString());
468
+ }
469
+ if (instruction.group) {
470
+ const groupId = instruction.group;
471
+ const groupElements = this.index.groupToElements.get(groupId);
472
+ if (groupElements) {
473
+ groupElements.delete(element);
474
+ if (groupElements.size === 0) {
475
+ this.index.groupToElements.delete(groupId);
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }
481
+ getElementsByGroup(instruction) {
482
+ if (typeof instruction === "string") {
483
+ return this.index.groupToElements.get(instruction);
484
+ } else {
485
+ return this.index.groupToElements.get(instruction.aseid.toString());
486
+ }
487
+ }
488
+ /**
489
+ * Adds an event listener to a specific DOM element and keeps track of it in the context's index for proper cleanup later. This method takes a DOM element, an event name, and a listener function or object, and stores this information in the elementListeners map. This allows the engine to efficiently manage event listeners attached to dynamically created elements, ensuring that they can be removed when the associated instructions are reverted or when nodes are removed from the DOM, preventing memory leaks and unintended behavior.
490
+ *
491
+ * @param element
492
+ * @param eventName
493
+ * @param listener
494
+ */
495
+ addListener(element, eventName, listener) {
496
+ if (!this.index.elementListeners.has(element)) {
497
+ this.index.elementListeners.set(element, /* @__PURE__ */ new Map());
498
+ }
499
+ const byEvent = this.index.elementListeners.get(element);
500
+ if (!byEvent.has(eventName)) {
501
+ byEvent.set(eventName, /* @__PURE__ */ new Set());
502
+ }
503
+ byEvent.get(eventName).add(listener);
504
+ }
505
+ /**
506
+ * Retrieves the event listener associated with a specific DOM element and event name from the context's index. This method looks up the element in the elementListeners map and then retrieves the listener for the specified event name. If no listener is found for the given element and event, it returns undefined. This allows the engine to efficiently access and manage event listeners that have been attached to dynamically created elements, enabling proper cleanup when instructions are reverted or when nodes are removed from the DOM.
507
+ *
508
+ * @param element
509
+ * @param eventName
510
+ * @returns
511
+ */
512
+ getListener(element, eventName) {
513
+ const set = this.index.elementListeners.get(element)?.get(eventName);
514
+ if (!set || set.size === 0) return void 0;
515
+ return set.values().next().value;
516
+ }
517
+ /**
518
+ * Returns all listeners registered for a given element + event name.
519
+ */
520
+ getListeners(element, eventName) {
521
+ return this.index.elementListeners.get(element)?.get(eventName);
522
+ }
523
+ /**
524
+ * Removes an event listener from a specific DOM element and updates the context's index accordingly. This method looks up the element in the elementListeners map and deletes the listener for the specified event name. This is typically called when an instruction is reverted or when a node is removed from the DOM, ensuring that any attached event listeners are properly cleaned up to prevent memory leaks and unintended behavior.
525
+ *
526
+ * @param element
527
+ * @param eventName
528
+ */
529
+ removeListener(element, eventName, listener) {
530
+ const byEvent = this.index.elementListeners.get(element);
531
+ if (!byEvent) return;
532
+ if (listener) {
533
+ const set = byEvent.get(eventName);
534
+ if (set) {
535
+ set.delete(listener);
536
+ if (set.size === 0) byEvent.delete(eventName);
537
+ }
538
+ } else {
539
+ byEvent.delete(eventName);
540
+ }
541
+ }
542
+ };
543
+ AreHTMLEngineContext = __decorateClass([
544
+ A_Frame.Define({
545
+ namespace: "a-are-html",
546
+ description: "Runtime index for the HTML rendering engine. Maps each AreNode and instruction ASEID to its corresponding DOM element so that apply and revert handlers on interpreter instructions can look up their DOM node in O(1). Tracks root-element mounts and maintains the group-level index used by structural directives."
547
+ })
548
+ ], AreHTMLEngineContext);
275
549
 
276
550
  // src/directives/AreDirectiveFor.directive.ts
277
551
  var AreDirectiveFor = class extends AreDirective {
@@ -287,7 +561,8 @@ var AreDirectiveFor = class extends AreDirective {
287
561
  node.init();
288
562
  attribute.template = forTemplate;
289
563
  const { key, index, arrayExpr } = this.parseExpression(attribute.content);
290
- const array = this.resolveArray(store, arrayExpr, attribute.content);
564
+ const contextScope = attribute.owner.scope.resolve(AreDirectiveContext)?.scope || {};
565
+ const array = this.resolveArray(store, arrayExpr, attribute.content, contextScope);
291
566
  attribute.value = array;
292
567
  for (let i = 0; i < array.length; i++) {
293
568
  this.spawnItemNode(attribute.template, attribute.owner, key, index, array[i], i);
@@ -321,8 +596,9 @@ var AreDirectiveFor = class extends AreDirective {
321
596
  */
322
597
  performUpdate(attribute, store, scene, state) {
323
598
  const { key, index, arrayExpr, trackExpr } = this.parseExpression(attribute.content);
324
- const newArray = this.resolveArray(store, arrayExpr, attribute.content);
325
599
  const owner = attribute.owner;
600
+ const contextScope = owner.scope.resolve(AreDirectiveContext)?.scope || {};
601
+ const newArray = this.resolveArray(store, arrayExpr, attribute.content, contextScope);
326
602
  const currentChildren = [...owner.children];
327
603
  attribute.value = newArray;
328
604
  const attached = this.isAttached(owner);
@@ -337,12 +613,16 @@ var AreDirectiveFor = class extends AreDirective {
337
613
  remaining.add(child);
338
614
  }
339
615
  const toCreate = [];
616
+ const finalByKey = /* @__PURE__ */ new Map();
617
+ const orderedKeys = new Array(newArray.length);
340
618
  for (let i = 0; i < newArray.length; i++) {
341
619
  const item = newArray[i];
342
620
  const k = computeKey(item, i);
621
+ orderedKeys[i] = k;
343
622
  const existing = childByKey.get(k);
344
623
  if (existing) {
345
624
  remaining.delete(existing);
625
+ finalByKey.set(k, existing);
346
626
  let directiveContext = existing.scope.resolveFlat(AreDirectiveContext);
347
627
  if (!directiveContext) {
348
628
  directiveContext = new AreDirectiveContext(existing.aseid);
@@ -354,7 +634,7 @@ var AreDirectiveFor = class extends AreDirective {
354
634
  [index || "index"]: i
355
635
  };
356
636
  } else {
357
- toCreate.push({ item, idx: i });
637
+ toCreate.push({ item, idx: i, key: k });
358
638
  }
359
639
  }
360
640
  for (const child of remaining) {
@@ -363,12 +643,14 @@ var AreDirectiveFor = class extends AreDirective {
363
643
  }
364
644
  const createItem = (desc) => {
365
645
  const child = this.spawnItemNode(attribute.template, owner, key, index, desc.item, desc.idx);
646
+ finalByKey.set(desc.key, child);
366
647
  child.transform();
367
648
  child.compile();
368
649
  if (attached) child.mount();
369
650
  };
370
651
  if (toCreate.length <= AreDirectiveFor.SYNC_THRESHOLD) {
371
652
  for (const desc of toCreate) createItem(desc);
653
+ if (attached) this.reconcileOrder(owner, orderedKeys, finalByKey);
372
654
  return this.finishUpdate(attribute, store, scene, state);
373
655
  }
374
656
  state.running = true;
@@ -391,10 +673,39 @@ var AreDirectiveFor = class extends AreDirective {
391
673
  AreSchedulerHelper.scheduleMacrotask(() => resolve(processChunk()));
392
674
  });
393
675
  }
676
+ if (attached) this.reconcileOrder(owner, orderedKeys, finalByKey);
394
677
  return this.finishUpdate(attribute, store, scene, state);
395
678
  };
396
679
  return processChunk();
397
680
  }
681
+ /**
682
+ * Repositions the item nodes' DOM elements so the rendered order matches the
683
+ * source array order. The keyed diff (steps 1–4) reuses existing nodes in
684
+ * place and mounts new ones at the end; without this pass a `prepend` or
685
+ * `shuffle` would leave reused rows where they were and pile new rows at the
686
+ * bottom. We walk the desired order RIGHT-TO-LEFT, keeping a `ref` pointer to
687
+ * the element each item must precede (starting at the `$for` anchor comment),
688
+ * and only call `insertBefore` when an element is not already in position —
689
+ * so a plain `append` (already-correct order) performs ZERO DOM moves.
690
+ */
691
+ reconcileOrder(owner, orderedKeys, finalByKey) {
692
+ const context = owner.scope.resolve(AreHTMLEngineContext);
693
+ if (!context) return;
694
+ const anchor = context.getNodeElement(owner);
695
+ if (!anchor || !anchor.parentNode) return;
696
+ const parent = anchor.parentNode;
697
+ let ref = anchor;
698
+ for (let i = orderedKeys.length - 1; i >= 0; i--) {
699
+ const node = finalByKey.get(orderedKeys[i]);
700
+ if (!node) continue;
701
+ const element = context.getNodeElement(node);
702
+ if (!element || element.parentNode !== parent) continue;
703
+ if (element.nextSibling !== ref) {
704
+ parent.insertBefore(element, ref);
705
+ }
706
+ ref = element;
707
+ }
708
+ }
398
709
  /**
399
710
  * Completes an update pass. If another update() arrived while a chunked
400
711
  * render was streaming, run exactly one more pass now from the latest store
@@ -485,13 +796,23 @@ var AreDirectiveFor = class extends AreDirective {
485
796
  * Supports both plain key lookups and function-call expressions:
486
797
  * items → store.get('items')
487
798
  * filter(items) → store.get('filter')(store.get('items'))
799
+ *
800
+ * `contextScope` carries item-scoped variables introduced by an enclosing
801
+ * directive (e.g. the `row` of an outer `$for`). It is consulted BEFORE the
802
+ * store so a nested `$for="cell in row.cells"` resolves `row` from the
803
+ * parent iteration instead of looking for a (non-existent) top-level store
804
+ * key. Leading identifiers not present in the context fall back to the store.
488
805
  */
489
- resolveArray(store, arrayExpr, fullContent) {
806
+ resolveArray(store, arrayExpr, fullContent, contextScope = {}) {
807
+ const getRoot = (rawKey) => {
808
+ const k = rawKey.replace(/\?$/, "");
809
+ return k in contextScope ? contextScope[k] : store.get(k);
810
+ };
490
811
  let result;
491
812
  const callMatch = arrayExpr.match(/^([^(]+)\((.+)\)$/);
492
813
  if (callMatch) {
493
814
  const fnName = callMatch[1].trim();
494
- const fn = store.get(fnName);
815
+ const fn = getRoot(fnName);
495
816
  if (typeof fn !== "function")
496
817
  throw new AreCompilerError({
497
818
  title: 'Invalid "for" Directive Function',
@@ -505,25 +826,25 @@ var AreDirectiveFor = class extends AreDirective {
505
826
  const stripped = arg.replace(/\?$/, "");
506
827
  if (stripped.includes(".")) {
507
828
  const parts = stripped.split(".").map((p) => p.replace(/\?$/, ""));
508
- let val = store.get(parts[0]);
829
+ let val = getRoot(parts[0]);
509
830
  for (let j = 1; j < parts.length; j++) {
510
831
  if (val == null) return void 0;
511
832
  val = val[parts[j]];
512
833
  }
513
834
  return val ?? void 0;
514
835
  }
515
- return store.get(stripped);
836
+ return getRoot(stripped);
516
837
  });
517
838
  result = fn(...resolvedArgs);
518
839
  } else if (arrayExpr.includes(".")) {
519
840
  const parts = arrayExpr.split(".").map((p) => p.replace(/\?$/, ""));
520
- result = store.get(parts[0]);
841
+ result = getRoot(parts[0]);
521
842
  for (let i = 1; i < parts.length; i++) {
522
843
  if (result == null) break;
523
844
  result = result[parts[i]];
524
845
  }
525
846
  } else {
526
- result = store.get(arrayExpr.replace(/\?$/, ""));
847
+ result = getRoot(arrayExpr);
527
848
  }
528
849
  if (result == null) return [];
529
850
  if (!Array.isArray(result))
@@ -823,6 +1144,21 @@ AddListenerInstruction = __decorateClass([
823
1144
  description: "Attaches a DOM event listener to an element. Apply calls addEventListener; revert calls removeEventListener."
824
1145
  })
825
1146
  ], AddListenerInstruction);
1147
+ var AddStaticHTMLInstruction = class extends AreMutation {
1148
+ constructor(parent, props) {
1149
+ if ("aseid" in props) {
1150
+ super(props);
1151
+ } else {
1152
+ super(AreHTMLInstructions.AddStaticHTML, parent, props);
1153
+ }
1154
+ }
1155
+ };
1156
+ AddStaticHTMLInstruction = __decorateClass([
1157
+ A_Frame.Define({
1158
+ namespace: "a-are-html",
1159
+ description: 'Materialises a fully static subtree (a "static island") onto its parent element in a single pass via browser-parsed innerHTML / a cached <template> clone. Apply injects the markup; revert clears it. Decodes HTML entities (e.g. &nbsp;) for free.'
1160
+ })
1161
+ ], AddStaticHTMLInstruction);
826
1162
  var AddStyleInstruction = class extends AreMutation {
827
1163
  constructor(parent, props) {
828
1164
  if ("aseid" in props) {
@@ -877,6 +1213,57 @@ var AreHTMLNode = class extends AreNode {
877
1213
  get tag() {
878
1214
  return this.aseid.entity;
879
1215
  }
1216
+ /**
1217
+ * The verbatim inner markup captured when this node was identified as a
1218
+ * static island, or `undefined` for ordinary (per-node) nodes.
1219
+ */
1220
+ get staticInnerHTML() {
1221
+ return this._staticInnerHTML;
1222
+ }
1223
+ /**
1224
+ * Whether this node is a static-island root (see `_staticInnerHTML`).
1225
+ */
1226
+ get isStaticIsland() {
1227
+ return this._staticInnerHTML !== void 0;
1228
+ }
1229
+ /**
1230
+ * Marks this node as a static-island root, capturing the verbatim inner
1231
+ * markup to be materialised in one shot by the interpreter. Called by the
1232
+ * tokenizer when the node's inner content is detected to be fully static.
1233
+ */
1234
+ markStatic(innerHTML) {
1235
+ this._staticInnerHTML = innerHTML;
1236
+ }
1237
+ /**
1238
+ * Deep-clone the node. Overridden to carry over the static-island marker
1239
+ * (`_staticInnerHTML`), which lives on AreHTMLNode and is therefore NOT
1240
+ * copied by the base AreNode.clone(). Without this, cloning a directive
1241
+ * template ($if/$for) that wraps a static island (e.g. `<span $if>★</span>`)
1242
+ * would drop the captured inner markup and render an empty element. The
1243
+ * base clone() recurses via each child's polymorphic clone(), so nested
1244
+ * island children are preserved automatically through this override.
1245
+ */
1246
+ clone() {
1247
+ const cloned = super.clone();
1248
+ const self = this;
1249
+ if (self._staticInnerHTML !== void 0)
1250
+ cloned.markStatic(self._staticInnerHTML);
1251
+ return cloned;
1252
+ }
1253
+ /**
1254
+ * Clone the node while transferring its existing scope to the clone (used by
1255
+ * the $if/$for directives to turn the original node into a lightweight group
1256
+ * container). Overridden for the same reason as `clone()`: the static-island
1257
+ * marker must survive so a directive applied to an island root keeps its
1258
+ * inner markup.
1259
+ */
1260
+ cloneWithScope() {
1261
+ const cloned = super.cloneWithScope();
1262
+ const self = this;
1263
+ if (self._staticInnerHTML !== void 0)
1264
+ cloned.markStatic(self._staticInnerHTML);
1265
+ return cloned;
1266
+ }
880
1267
  /**
881
1268
  * The static attributes defined for the node, which are typically used to represent static properties or characteristics of the node that do not change based on the context or state. These attributes are usually defined in the template and are not reactive.
882
1269
  *
@@ -1175,184 +1562,176 @@ function toDOMString(value) {
1175
1562
  return "";
1176
1563
  }
1177
1564
  }
1178
- var AreHTMLEngineContext = class extends AreContext {
1179
- constructor(props) {
1180
- super(props.container?.body.innerHTML || props.source || "");
1181
- /**
1182
- * Index structure mapping:
1183
- *
1184
- * Node -> Group ID -> Element
1185
- * -----------------------------------------------------------------------------------
1186
- * | - Attribute | group: string | Node
1187
- * | - Directive (e.g. for) | | Node
1188
- */
1189
- this.index = {
1190
- /**
1191
- * 1 AreNode = 1 Dom Node
1192
- *
1193
- * uses ASEID
1194
- */
1195
- nodeToHostElements: /* @__PURE__ */ new Map(),
1196
- /**
1197
- * 1 Group Instruction = MANY Dom Nodes (e.g. for loop)
1198
- *
1199
- * uses ASEID
1200
- */
1201
- groupToElements: /* @__PURE__ */ new Map(),
1202
- /**
1203
- * 1 Dom Node = 1 Instruction
1204
- *
1205
- * uses ASEID
1206
- */
1207
- elementToInstruction: /* @__PURE__ */ new WeakMap(),
1208
- /**
1209
- * 1 Instruction = 1 Dom Node (for CreateElement instructions, for example)
1210
- *
1211
- * uses ASEID
1212
- */
1213
- instructionToElement: /* @__PURE__ */ new Map(),
1214
- /**
1215
- * Event listeners attached to elements, used for proper cleanup when reverting instructions. Maps a DOM element to a map of event names and their corresponding listeners, allowing the engine to track which listeners are attached to which elements and remove them when necessary (e.g., when an instruction is reverted).
1216
- */
1217
- elementListeners: /* @__PURE__ */ new WeakMap()
1218
- };
1219
- this._container = props.container;
1220
- }
1221
- get container() {
1222
- return this._container;
1223
- }
1224
- getNodeElement(node) {
1225
- if (typeof node === "string") {
1226
- return this.index.nodeToHostElements.get(node);
1227
- } else {
1228
- return this.index.nodeToHostElements.get(node.aseid.toString());
1229
- }
1230
- }
1231
- /**
1232
- * Associates a DOM element with a given instruction and its owner node. This method updates the context's index to map the instruction's ASEID to the provided DOM element, and also maps the element back to the instruction's ASEID for reverse lookup. If the instruction has an owner node, it also maps the node's ASEID to the element. Additionally, if the instruction belongs to a group, it adds the element to the set of elements associated with that group. This indexing allows the engine to efficiently manage and update DOM elements based on instructions and their corresponding nodes, enabling dynamic rendering and interaction in response to application state changes.
1233
- *
1234
- * @param instruction
1235
- * @param element
1236
- */
1237
- setInstructionElement(instruction, element) {
1238
- const node = instruction.owner;
1239
- this.index.instructionToElement.set(instruction.aseid.toString(), element);
1240
- this.index.elementToInstruction.set(element, instruction.aseid.toString());
1241
- if (node && instruction instanceof AreDeclaration) {
1242
- this.index.nodeToHostElements.set(node.aseid.toString(), element);
1243
- }
1244
- if (instruction.group) {
1245
- const groupId = instruction.group;
1246
- if (!this.index.groupToElements.has(groupId)) {
1247
- this.index.groupToElements.set(groupId, /* @__PURE__ */ new Set());
1248
- }
1249
- this.index.groupToElements.get(groupId).add(element);
1250
- }
1251
- }
1252
- getElementByInstruction(instruction) {
1253
- if (typeof instruction === "string") {
1254
- return this.index.instructionToElement.get(instruction);
1255
- } else {
1256
- return this.index.instructionToElement.get(instruction.aseid.toString());
1257
- }
1258
- }
1259
- /**
1260
- * Removes the association between a given instruction and its corresponding DOM element. This method looks up the instruction's ASEID to find the associated DOM element, and if found, it deletes the mapping from both instructionToElement and elementToInstruction. If the instruction has an owner node, it also removes the mapping from nodeToHostElements. Additionally, if the instruction belongs to a group, it removes the element from the set of elements associated with that group, and if the group has no more elements, it deletes the group from the index. This cleanup is essential for maintaining an accurate and efficient mapping of instructions to DOM elements, especially when instructions are reverted or when nodes are removed from the DOM.
1261
- *
1262
- * @param instruction
1263
- */
1264
- removeInstructionElement(instruction) {
1265
- const element = this.index.instructionToElement.get(instruction.aseid.toString());
1266
- if (element) {
1267
- this.index.instructionToElement.delete(instruction.aseid.toString());
1268
- this.index.elementToInstruction.delete(element);
1269
- const node = instruction.owner;
1270
- if (node && instruction instanceof AreDeclaration) {
1271
- this.index.nodeToHostElements.delete(node.aseid.toString());
1272
- }
1273
- if (instruction.group) {
1274
- const groupId = instruction.group;
1275
- const groupElements = this.index.groupToElements.get(groupId);
1276
- if (groupElements) {
1277
- groupElements.delete(element);
1278
- if (groupElements.size === 0) {
1279
- this.index.groupToElements.delete(groupId);
1280
- }
1565
+ var STANDARD_HTML_TAGS = /* @__PURE__ */ new Set([
1566
+ // root / sections
1567
+ "html",
1568
+ "body",
1569
+ "header",
1570
+ "footer",
1571
+ "main",
1572
+ "nav",
1573
+ "section",
1574
+ "article",
1575
+ "aside",
1576
+ "address",
1577
+ "hgroup",
1578
+ // headings
1579
+ "h1",
1580
+ "h2",
1581
+ "h3",
1582
+ "h4",
1583
+ "h5",
1584
+ "h6",
1585
+ // grouping
1586
+ "div",
1587
+ "p",
1588
+ "span",
1589
+ "pre",
1590
+ "blockquote",
1591
+ "figure",
1592
+ "figcaption",
1593
+ "hr",
1594
+ "br",
1595
+ "wbr",
1596
+ // lists
1597
+ "ul",
1598
+ "ol",
1599
+ "li",
1600
+ "dl",
1601
+ "dt",
1602
+ "dd",
1603
+ "menu",
1604
+ // text-level / phrasing
1605
+ "a",
1606
+ "b",
1607
+ "i",
1608
+ "u",
1609
+ "s",
1610
+ "em",
1611
+ "strong",
1612
+ "small",
1613
+ "mark",
1614
+ "abbr",
1615
+ "cite",
1616
+ "q",
1617
+ "code",
1618
+ "kbd",
1619
+ "samp",
1620
+ "var",
1621
+ "sub",
1622
+ "sup",
1623
+ "time",
1624
+ "data",
1625
+ "dfn",
1626
+ "bdi",
1627
+ "bdo",
1628
+ "ruby",
1629
+ "rt",
1630
+ "rp",
1631
+ "del",
1632
+ "ins",
1633
+ // media / embedded (no special namespace handling needed)
1634
+ "img",
1635
+ "picture",
1636
+ "source",
1637
+ "figure",
1638
+ "audio",
1639
+ "video",
1640
+ "track",
1641
+ // tables
1642
+ "table",
1643
+ "caption",
1644
+ "colgroup",
1645
+ "col",
1646
+ "thead",
1647
+ "tbody",
1648
+ "tfoot",
1649
+ "tr",
1650
+ "th",
1651
+ "td",
1652
+ // forms (display only — these still render fine from innerHTML)
1653
+ "label",
1654
+ "fieldset",
1655
+ "legend",
1656
+ "datalist",
1657
+ "option",
1658
+ "optgroup",
1659
+ "output",
1660
+ "progress",
1661
+ "meter",
1662
+ // interactive
1663
+ "details",
1664
+ "summary",
1665
+ "dialog"
1666
+ ]);
1667
+ function isStaticMarkup(inner) {
1668
+ if (!inner) return false;
1669
+ if (inner.indexOf("{{") !== -1) return false;
1670
+ const n = inner.length;
1671
+ let i = 0;
1672
+ while (i < n) {
1673
+ const lt = inner.indexOf("<", i);
1674
+ if (lt === -1) break;
1675
+ if (inner.startsWith("<!--", lt)) {
1676
+ const end = inner.indexOf("-->", lt + 4);
1677
+ if (end === -1) return false;
1678
+ i = end + 3;
1679
+ continue;
1680
+ }
1681
+ if (inner[lt + 1] === "/" || inner[lt + 1] === "!" || inner[lt + 1] === "?") {
1682
+ const gt = inner.indexOf(">", lt);
1683
+ if (gt === -1) return false;
1684
+ i = gt + 1;
1685
+ continue;
1686
+ }
1687
+ const nameMatch = /^<([a-zA-Z][a-zA-Z0-9-]*)/.exec(inner.slice(lt));
1688
+ if (!nameMatch) {
1689
+ i = lt + 1;
1690
+ continue;
1691
+ }
1692
+ const tag = nameMatch[1].toLowerCase();
1693
+ if (tag.indexOf("-") !== -1 || !STANDARD_HTML_TAGS.has(tag)) return false;
1694
+ let j = lt + nameMatch[0].length;
1695
+ let inSingle = false;
1696
+ let inDouble = false;
1697
+ let atNameBoundary = true;
1698
+ let tagEnd = -1;
1699
+ while (j < n) {
1700
+ const ch = inner[j];
1701
+ if (inDouble) {
1702
+ if (ch === '"') inDouble = false;
1703
+ } else if (inSingle) {
1704
+ if (ch === "'") inSingle = false;
1705
+ } else if (ch === '"') {
1706
+ inDouble = true;
1707
+ atNameBoundary = false;
1708
+ } else if (ch === "'") {
1709
+ inSingle = true;
1710
+ atNameBoundary = false;
1711
+ } else if (ch === ">") {
1712
+ tagEnd = j;
1713
+ break;
1714
+ } else if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "/") {
1715
+ atNameBoundary = true;
1716
+ } else {
1717
+ if (atNameBoundary && (ch === "$" || ch === ":" || ch === "@")) {
1718
+ return false;
1281
1719
  }
1720
+ atNameBoundary = false;
1282
1721
  }
1722
+ j++;
1283
1723
  }
1724
+ if (tagEnd === -1) return false;
1725
+ i = tagEnd + 1;
1284
1726
  }
1285
- getElementsByGroup(instruction) {
1286
- if (typeof instruction === "string") {
1287
- return this.index.groupToElements.get(instruction);
1288
- } else {
1289
- return this.index.groupToElements.get(instruction.aseid.toString());
1290
- }
1291
- }
1292
- /**
1293
- * Adds an event listener to a specific DOM element and keeps track of it in the context's index for proper cleanup later. This method takes a DOM element, an event name, and a listener function or object, and stores this information in the elementListeners map. This allows the engine to efficiently manage event listeners attached to dynamically created elements, ensuring that they can be removed when the associated instructions are reverted or when nodes are removed from the DOM, preventing memory leaks and unintended behavior.
1294
- *
1295
- * @param element
1296
- * @param eventName
1297
- * @param listener
1298
- */
1299
- addListener(element, eventName, listener) {
1300
- if (!this.index.elementListeners.has(element)) {
1301
- this.index.elementListeners.set(element, /* @__PURE__ */ new Map());
1302
- }
1303
- const byEvent = this.index.elementListeners.get(element);
1304
- if (!byEvent.has(eventName)) {
1305
- byEvent.set(eventName, /* @__PURE__ */ new Set());
1306
- }
1307
- byEvent.get(eventName).add(listener);
1308
- }
1309
- /**
1310
- * Retrieves the event listener associated with a specific DOM element and event name from the context's index. This method looks up the element in the elementListeners map and then retrieves the listener for the specified event name. If no listener is found for the given element and event, it returns undefined. This allows the engine to efficiently access and manage event listeners that have been attached to dynamically created elements, enabling proper cleanup when instructions are reverted or when nodes are removed from the DOM.
1311
- *
1312
- * @param element
1313
- * @param eventName
1314
- * @returns
1315
- */
1316
- getListener(element, eventName) {
1317
- const set = this.index.elementListeners.get(element)?.get(eventName);
1318
- if (!set || set.size === 0) return void 0;
1319
- return set.values().next().value;
1320
- }
1321
- /**
1322
- * Returns all listeners registered for a given element + event name.
1323
- */
1324
- getListeners(element, eventName) {
1325
- return this.index.elementListeners.get(element)?.get(eventName);
1326
- }
1327
- /**
1328
- * Removes an event listener from a specific DOM element and updates the context's index accordingly. This method looks up the element in the elementListeners map and deletes the listener for the specified event name. This is typically called when an instruction is reverted or when a node is removed from the DOM, ensuring that any attached event listeners are properly cleaned up to prevent memory leaks and unintended behavior.
1329
- *
1330
- * @param element
1331
- * @param eventName
1332
- */
1333
- removeListener(element, eventName, listener) {
1334
- const byEvent = this.index.elementListeners.get(element);
1335
- if (!byEvent) return;
1336
- if (listener) {
1337
- const set = byEvent.get(eventName);
1338
- if (set) {
1339
- set.delete(listener);
1340
- if (set.size === 0) byEvent.delete(eventName);
1341
- }
1342
- } else {
1343
- byEvent.delete(eventName);
1344
- }
1345
- }
1346
- };
1347
- AreHTMLEngineContext = __decorateClass([
1348
- A_Frame.Define({
1349
- namespace: "a-are-html",
1350
- description: "Runtime index for the HTML rendering engine. Maps each AreNode and instruction ASEID to its corresponding DOM element so that apply and revert handlers on interpreter instructions can look up their DOM node in O(1). Tracks root-element mounts and maintains the group-level index used by structural directives."
1351
- })
1352
- ], AreHTMLEngineContext);
1727
+ return true;
1728
+ }
1353
1729
  var AreHTMLCompiler = class extends AreCompiler {
1354
1730
  compileHTMLNode(node, scene, logger, ...args) {
1355
1731
  super.compile(node, scene, logger, ...args);
1732
+ if (node.isStaticIsland && scene.host) {
1733
+ scene.plan(new AddStaticHTMLInstruction(scene.host, { html: node.staticInnerHTML }));
1734
+ }
1356
1735
  if (node.styles?.styles) {
1357
1736
  const host = scene.host;
1358
1737
  if (host) {
@@ -1547,12 +1926,15 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1547
1926
  });
1548
1927
  }
1549
1928
  const element = isSVG ? context.container.createElementNS(SVG_NAMESPACE, tag) : context.container.createElement(tag);
1550
- if (mountPoint.nodeType === Node.ELEMENT_NODE) {
1551
- mountPoint.appendChild(element);
1552
- } else {
1929
+ context.setInstructionElement(declaration, element);
1930
+ const attach = mountPoint.nodeType === Node.ELEMENT_NODE ? () => mountPoint.appendChild(element) : () => {
1553
1931
  mountPoint.parentNode?.insertBefore(element, mountPoint);
1932
+ };
1933
+ if (context.isBatching && mountPoint.isConnected) {
1934
+ context.deferAttach(attach);
1935
+ } else {
1936
+ attach();
1554
1937
  }
1555
- context.setInstructionElement(declaration, element);
1556
1938
  } else {
1557
1939
  const mountPoint = context.container.getElementById(node.id);
1558
1940
  if (!mountPoint) {
@@ -1562,8 +1944,15 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1562
1944
  });
1563
1945
  }
1564
1946
  const element = isSVG ? context.container.createElementNS(SVG_NAMESPACE, tag) : context.container.createElement(tag);
1565
- mountPoint.parentNode?.replaceChild(element, mountPoint);
1566
1947
  context.setInstructionElement(declaration, element);
1948
+ const attach = () => {
1949
+ mountPoint.parentNode?.replaceChild(element, mountPoint);
1950
+ };
1951
+ if (context.isBatching && mountPoint.isConnected) {
1952
+ context.deferAttach(attach);
1953
+ } else {
1954
+ attach();
1955
+ }
1567
1956
  }
1568
1957
  logger?.debug("green", `Element ${node.aseid.toString()} added to Context:`);
1569
1958
  } catch (error) {
@@ -1573,7 +1962,7 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1573
1962
  }
1574
1963
  removeElement(declaration, context) {
1575
1964
  const element = context.getElementByInstruction(declaration);
1576
- if (element && element.parentNode) {
1965
+ if (element && element.parentNode && element.isConnected) {
1577
1966
  element.parentNode.removeChild(element);
1578
1967
  }
1579
1968
  context.removeInstructionElement(declaration);
@@ -1676,7 +2065,7 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1676
2065
  const element = context.getElementByInstruction(mutation.parent);
1677
2066
  if (!element) return;
1678
2067
  const { name } = mutation.payload;
1679
- if (name && element.nodeType === Node.ELEMENT_NODE) {
2068
+ if (name && element.nodeType === Node.ELEMENT_NODE && element.isConnected) {
1680
2069
  const colonIdx = name.indexOf(":");
1681
2070
  if (colonIdx > 0) {
1682
2071
  const ns = SVG_ATTRIBUTE_NS[name.slice(0, colonIdx)];
@@ -1703,6 +2092,7 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1703
2092
  showElement(mutation, context) {
1704
2093
  const element = context.getElementByInstruction(mutation.parent);
1705
2094
  if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
2095
+ if (!element.isConnected) return;
1706
2096
  const el = element;
1707
2097
  el.style.display = mutation.payload?.display ?? mutation.cache ?? "";
1708
2098
  }
@@ -1797,7 +2187,9 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1797
2187
  const { event: eventName } = parseEventName(name);
1798
2188
  const listener = mutation.payload._callback;
1799
2189
  if (listener) {
1800
- element.removeEventListener(eventName, listener);
2190
+ if (element.isConnected) {
2191
+ element.removeEventListener(eventName, listener);
2192
+ }
1801
2193
  context.removeListener(element, name, listener);
1802
2194
  mutation.payload._callback = void 0;
1803
2195
  }
@@ -1835,9 +2227,32 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1835
2227
  removeText(declaration, context) {
1836
2228
  const element = context.getElementByInstruction(declaration);
1837
2229
  if (!element) return;
1838
- element.parentNode?.removeChild(element);
2230
+ if (element.isConnected) {
2231
+ element.parentNode?.removeChild(element);
2232
+ }
1839
2233
  context.removeInstructionElement(declaration);
1840
2234
  }
2235
+ addStaticHTML(mutation, context, logger) {
2236
+ const element = context.getElementByInstruction(mutation.parent);
2237
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) {
2238
+ throw new AreInterpreterError({
2239
+ title: "Element Not Found",
2240
+ description: `Could not find a DOM element associated with the instruction ASEID "${mutation.parent}". Ensure the host element is rendered before materialising its static island.`
2241
+ });
2242
+ }
2243
+ const el = element;
2244
+ const { html } = mutation.payload;
2245
+ el.textContent = "";
2246
+ const fragment = context.getStaticFragment(el.tagName.toLowerCase(), html);
2247
+ el.appendChild(fragment.cloneNode(true));
2248
+ logger?.debug("green", `Static island materialised onto <${(mutation.owner.parent ?? mutation.owner)?.aseid?.toString?.()}>`);
2249
+ }
2250
+ removeStaticHTML(mutation, context) {
2251
+ const element = context.getElementByInstruction(mutation.parent);
2252
+ if (element && element.nodeType === Node.ELEMENT_NODE && element.isConnected) {
2253
+ element.textContent = "";
2254
+ }
2255
+ }
1841
2256
  addComment(declaration, context, store, syntax, directiveContext, logger) {
1842
2257
  const node = declaration.owner.parent;
1843
2258
  const { content, evaluate } = declaration.payload;
@@ -1871,7 +2286,9 @@ var AreHTMLInterpreter = class extends AreInterpreter {
1871
2286
  removeComment(declaration, context) {
1872
2287
  const element = context.getElementByInstruction(declaration);
1873
2288
  if (!element) return;
1874
- element.parentNode?.removeChild(element);
2289
+ if (element.isConnected) {
2290
+ element.parentNode?.removeChild(element);
2291
+ }
1875
2292
  context.removeInstructionElement(declaration);
1876
2293
  }
1877
2294
  addStyle(mutation, context, logger) {
@@ -2015,6 +2432,24 @@ __decorateClass([
2015
2432
  __decorateParam(0, A_Inject(A_Caller)),
2016
2433
  __decorateParam(1, A_Inject(AreHTMLEngineContext))
2017
2434
  ], AreHTMLInterpreter.prototype, "removeText", 1);
2435
+ __decorateClass([
2436
+ A_Frame.Define({
2437
+ description: "Inject a static island's inner markup onto its host element in one pass via a cached, browser-parsed <template> clone. Decodes HTML entities natively."
2438
+ }),
2439
+ AreInterpreter.Apply(AreHTMLInstructions.AddStaticHTML),
2440
+ AreInterpreter.Update(AreHTMLInstructions.AddStaticHTML),
2441
+ __decorateParam(0, A_Inject(A_Caller)),
2442
+ __decorateParam(1, A_Inject(AreHTMLEngineContext)),
2443
+ __decorateParam(2, A_Inject(A_Logger))
2444
+ ], AreHTMLInterpreter.prototype, "addStaticHTML", 1);
2445
+ __decorateClass([
2446
+ A_Frame.Define({
2447
+ description: "Clear a static island's injected markup from its host element on revert."
2448
+ }),
2449
+ AreInterpreter.Revert(AreHTMLInstructions.AddStaticHTML),
2450
+ __decorateParam(0, A_Inject(A_Caller)),
2451
+ __decorateParam(1, A_Inject(AreHTMLEngineContext))
2452
+ ], AreHTMLInterpreter.prototype, "removeStaticHTML", 1);
2018
2453
  __decorateClass([
2019
2454
  A_Frame.Define({
2020
2455
  description: "Add a comment node to the DOM based on the provided declaration instruction."
@@ -2066,7 +2501,12 @@ var AreHTMLTokenizer = class extends AreTokenizer {
2066
2501
  this.ATTR_PATTERN = /([$:@]?[\w.-]+(?::[\w.-]+)?)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>/"'=]+)))?/g;
2067
2502
  }
2068
2503
  tokenize(node, context, logger) {
2069
- super.tokenize(node, context, logger);
2504
+ const isStaticIsland = node instanceof AreComponentNode && !!node.content && isStaticMarkup(node.content);
2505
+ if (isStaticIsland) {
2506
+ node.markStatic(node.content);
2507
+ } else {
2508
+ super.tokenize(node, context, logger);
2509
+ }
2070
2510
  context.startPerformance("attributeExtraction");
2071
2511
  const attributes = this.extractAttributes(node.markup);
2072
2512
  for (const attr of attributes) {
@@ -2143,46 +2583,39 @@ var AreHTMLLifecycle = class extends AreLifecycle {
2143
2583
  mount(node, scene, logger, ...args) {
2144
2584
  logger?.debug(`[Mount] Component Trigger for <${node.aseid.entity}> with aseid :{${node.aseid.toString()}}`);
2145
2585
  if (scene.isInactive) return;
2146
- node.interpret();
2147
- const stack = [];
2148
- for (let i = node.children.length - 1; i >= 0; i--) {
2149
- stack.push({ node: node.children[i], entered: false });
2150
- }
2151
- const step = () => {
2152
- const frame = stack[stack.length - 1];
2153
- const current = frame.node;
2154
- if (frame.entered) {
2155
- stack.pop();
2156
- current.call(AreNodeFeatures.onAfterMount, current.scope);
2157
- return;
2158
- }
2159
- frame.entered = true;
2160
- current.call(AreNodeFeatures.onBeforeMount, current.scope);
2161
- if (!current.scene.isInactive) {
2162
- current.interpret();
2163
- for (let i = current.children.length - 1; i >= 0; i--) {
2164
- stack.push({ node: current.children[i], entered: false });
2165
- }
2586
+ const context = node.scope.resolve(AreHTMLEngineContext);
2587
+ context?.beginBatch();
2588
+ const afterMountQueue = [];
2589
+ try {
2590
+ node.interpret();
2591
+ const stack = [];
2592
+ for (let i = node.children.length - 1; i >= 0; i--) {
2593
+ stack.push({ node: node.children[i], entered: false });
2166
2594
  }
2167
- };
2168
- const drive = () => {
2169
- const start = AreSchedulerHelper.now();
2170
2595
  while (stack.length > 0) {
2171
- step();
2172
- if (stack.length > 0 && AreSchedulerHelper.now() - start >= AreHTMLLifecycle.MOUNT_BUDGET_MS) {
2173
- return new Promise((resolve, reject) => {
2174
- AreSchedulerHelper.scheduleMacrotask(() => {
2175
- try {
2176
- resolve(drive());
2177
- } catch (error) {
2178
- reject(error);
2179
- }
2180
- });
2181
- });
2596
+ const frame = stack[stack.length - 1];
2597
+ const current = frame.node;
2598
+ if (frame.entered) {
2599
+ stack.pop();
2600
+ afterMountQueue.push(current);
2601
+ continue;
2602
+ }
2603
+ frame.entered = true;
2604
+ current.call(AreNodeFeatures.onBeforeMount, current.scope);
2605
+ if (!current.scene.isInactive) {
2606
+ current.interpret();
2607
+ for (let i = current.children.length - 1; i >= 0; i--) {
2608
+ stack.push({ node: current.children[i], entered: false });
2609
+ }
2182
2610
  }
2183
2611
  }
2184
- };
2185
- return drive();
2612
+ } finally {
2613
+ context?.endBatch();
2614
+ }
2615
+ for (let i = 0; i < afterMountQueue.length; i++) {
2616
+ const mounted = afterMountQueue[i];
2617
+ mounted.call(AreNodeFeatures.onAfterMount, mounted.scope);
2618
+ }
2186
2619
  }
2187
2620
  updateDirectiveAttribute(directive, scope, feature, logger, ...args) {
2188
2621
  if (directive.component) {
@@ -2192,13 +2625,6 @@ var AreHTMLLifecycle = class extends AreLifecycle {
2192
2625
  }
2193
2626
  }
2194
2627
  };
2195
- /**
2196
- * Per-chunk time budget (ms) for the time-sliced initial mount walk. While
2197
- * mounting a large subtree we keep applying nodes until this much wall-clock
2198
- * time has elapsed, then yield to the browser so it can paint and process
2199
- * input before the next chunk. ~16ms targets a single animation frame.
2200
- */
2201
- AreHTMLLifecycle.MOUNT_BUDGET_MS = 16;
2202
2628
  __decorateClass([
2203
2629
  AreLifecycle.Init(AreComponentNode),
2204
2630
  __decorateParam(0, A_Inject(A_Caller)),
@@ -2834,6 +3260,6 @@ AreRouteWatcher = __decorateClass([
2834
3260
  })
2835
3261
  ], AreRouteWatcher);
2836
3262
 
2837
- export { AddAttributeInstruction, AddElementInstruction, AddInterpolationInstruction, AddListenerInstruction, AddStyleInstruction, AddTextInstruction, AreBindingAttribute, AreComment, AreComponentNode, AreDirective, AreDirectiveAttribute, AreDirectiveContext, AreDirectiveFeatures, AreDirectiveFor, AreDirectiveIf, AreDirectiveMeta, AreDirectiveShow, AreEventAttribute, AreHTMLAttribute, AreHTMLCompiler, AreHTMLEngine, AreHTMLEngineContext, AreHTMLInstructions, AreHTMLInterpreter, AreHTMLLifecycle, AreHTMLNode, AreHTMLTokenizer, AreHTMLTransformer, AreInterpolation, AreRoot, AreRootCache, AreRootNode, AreRoute, AreRouteWatcher, AreStaticAttribute, AreStyle, AreText, BOOLEAN_ATTRIBUTES, HideElementInstruction, IDL_FORM_PROPERTIES, LISTENER_OPTION_MODIFIERS, SVG_ATTRIBUTE_NS, SVG_NAMESPACE, VOID_ELEMENTS, isBooleanAttribute, isIDLFormProperty, isVoidElement, normalizeClassValue, normalizeStyleValue, parseEventName, toDOMString };
3263
+ export { AddAttributeInstruction, AddElementInstruction, AddInterpolationInstruction, AddListenerInstruction, AddStaticHTMLInstruction, AddStyleInstruction, AddTextInstruction, AreBindingAttribute, AreComment, AreComponentNode, AreDirective, AreDirectiveAttribute, AreDirectiveContext, AreDirectiveFeatures, AreDirectiveFor, AreDirectiveIf, AreDirectiveMeta, AreDirectiveShow, AreEventAttribute, AreHTMLAttribute, AreHTMLCompiler, AreHTMLEngine, AreHTMLEngineContext, AreHTMLInstructions, AreHTMLInterpreter, AreHTMLLifecycle, AreHTMLNode, AreHTMLTokenizer, AreHTMLTransformer, AreInterpolation, AreRoot, AreRootCache, AreRootNode, AreRoute, AreRouteWatcher, AreStaticAttribute, AreStyle, AreText, BOOLEAN_ATTRIBUTES, HideElementInstruction, IDL_FORM_PROPERTIES, LISTENER_OPTION_MODIFIERS, STANDARD_HTML_TAGS, SVG_ATTRIBUTE_NS, SVG_NAMESPACE, VOID_ELEMENTS, isBooleanAttribute, isIDLFormProperty, isStaticMarkup, isVoidElement, normalizeClassValue, normalizeStyleValue, parseEventName, toDOMString };
2838
3264
  //# sourceMappingURL=index.mjs.map
2839
3265
  //# sourceMappingURL=index.mjs.map