@adaas/are-html 0.0.19 → 0.0.21

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 (74) hide show
  1. package/dist/browser/index.d.mts +161 -5
  2. package/dist/browser/index.mjs +357 -55
  3. package/dist/browser/index.mjs.map +1 -1
  4. package/dist/node/directives/AreDirectiveFor.directive.d.mts +7 -0
  5. package/dist/node/directives/AreDirectiveFor.directive.d.ts +7 -0
  6. package/dist/node/directives/AreDirectiveFor.directive.js +17 -2
  7. package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
  8. package/dist/node/directives/AreDirectiveFor.directive.mjs +17 -2
  9. package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
  10. package/dist/node/directives/AreDirectiveShow.directive.d.mts +32 -0
  11. package/dist/node/directives/AreDirectiveShow.directive.d.ts +32 -0
  12. package/dist/node/directives/AreDirectiveShow.directive.js +81 -0
  13. package/dist/node/directives/AreDirectiveShow.directive.js.map +1 -0
  14. package/dist/node/directives/AreDirectiveShow.directive.mjs +71 -0
  15. package/dist/node/directives/AreDirectiveShow.directive.mjs.map +1 -0
  16. package/dist/node/engine/AreHTML.engine.d.mts +2 -1
  17. package/dist/node/engine/AreHTML.engine.d.ts +2 -1
  18. package/dist/node/engine/AreHTML.engine.js +8 -2
  19. package/dist/node/engine/AreHTML.engine.js.map +1 -1
  20. package/dist/node/engine/AreHTML.engine.mjs +8 -2
  21. package/dist/node/engine/AreHTML.engine.mjs.map +1 -1
  22. package/dist/node/engine/AreHTML.interpreter.d.mts +3 -0
  23. package/dist/node/engine/AreHTML.interpreter.d.ts +3 -0
  24. package/dist/node/engine/AreHTML.interpreter.js +29 -0
  25. package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
  26. package/dist/node/engine/AreHTML.interpreter.mjs +29 -0
  27. package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
  28. package/dist/node/index.d.mts +4 -1
  29. package/dist/node/index.d.ts +4 -1
  30. package/dist/node/index.js +21 -0
  31. package/dist/node/index.mjs +3 -0
  32. package/dist/node/instructions/AreHTML.instructions.constants.d.mts +1 -0
  33. package/dist/node/instructions/AreHTML.instructions.constants.d.ts +1 -0
  34. package/dist/node/instructions/AreHTML.instructions.constants.js +2 -1
  35. package/dist/node/instructions/AreHTML.instructions.constants.js.map +1 -1
  36. package/dist/node/instructions/AreHTML.instructions.constants.mjs +2 -1
  37. package/dist/node/instructions/AreHTML.instructions.constants.mjs.map +1 -1
  38. package/dist/node/instructions/AreHTML.instructions.types.d.mts +9 -1
  39. package/dist/node/instructions/AreHTML.instructions.types.d.ts +9 -1
  40. package/dist/node/instructions/HideElement.instruction.d.mts +13 -0
  41. package/dist/node/instructions/HideElement.instruction.d.ts +13 -0
  42. package/dist/node/instructions/HideElement.instruction.js +31 -0
  43. package/dist/node/instructions/HideElement.instruction.js.map +1 -0
  44. package/dist/node/instructions/HideElement.instruction.mjs +24 -0
  45. package/dist/node/instructions/HideElement.instruction.mjs.map +1 -0
  46. package/dist/node/lib/AreRoot/AreRoot.component.d.mts +57 -3
  47. package/dist/node/lib/AreRoot/AreRoot.component.d.ts +57 -3
  48. package/dist/node/lib/AreRoot/AreRoot.component.js +137 -48
  49. package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
  50. package/dist/node/lib/AreRoot/AreRoot.component.mjs +139 -50
  51. package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
  52. package/dist/node/lib/AreRoot/AreRootCache.context.d.mts +58 -0
  53. package/dist/node/lib/AreRoot/AreRootCache.context.d.ts +58 -0
  54. package/dist/node/lib/AreRoot/AreRootCache.context.js +106 -0
  55. package/dist/node/lib/AreRoot/AreRootCache.context.js.map +1 -0
  56. package/dist/node/lib/AreRoot/AreRootCache.context.mjs +99 -0
  57. package/dist/node/lib/AreRoot/AreRootCache.context.mjs.map +1 -0
  58. package/examples/jumpstart/dist/index.html +1 -1
  59. package/examples/jumpstart/dist/{mq1a0fv0-ccgtz6.js → mq7hqrxy-4kus50.js} +629 -433
  60. package/examples/signal-routing/dist/index.html +1 -1
  61. package/examples/signal-routing/dist/{mq1bzrik-4lec86.js → mq7k53th-qiwy4x.js} +903 -486
  62. package/examples/signal-routing/src/components/SettingsPage.component.ts +39 -0
  63. package/examples/signal-routing/src/concept.ts +2 -0
  64. package/package.json +9 -9
  65. package/src/directives/AreDirectiveFor.directive.ts +44 -2
  66. package/src/directives/AreDirectiveShow.directive.ts +127 -0
  67. package/src/engine/AreHTML.engine.ts +11 -1
  68. package/src/engine/AreHTML.interpreter.ts +50 -0
  69. package/src/index.ts +3 -0
  70. package/src/instructions/AreHTML.instructions.constants.ts +1 -0
  71. package/src/instructions/AreHTML.instructions.types.ts +9 -0
  72. package/src/instructions/HideElement.instruction.ts +29 -0
  73. package/src/lib/AreRoot/AreRoot.component.ts +201 -71
  74. package/src/lib/AreRoot/AreRootCache.context.ts +133 -0
@@ -30,6 +30,33 @@ export class SettingsPage extends Are {
30
30
  </label>
31
31
  </div>
32
32
 
33
+ <div class="settings-group">
34
+ <h2>Directive demo · <code>$show</code> vs <code>$if</code></h2>
35
+ <p class="hint">
36
+ Type something into both boxes below, then toggle <strong>Compact layout</strong>
37
+ twice. Both panels react to the same <code>compact</code> store flag, but:
38
+ </p>
39
+
40
+ <div class="demo-panel demo-show" $show="!compact">
41
+ <span class="demo-tag">$show</span>
42
+ <p>
43
+ Toggled with <code>$show</code> — I stay <strong>mounted</strong> and only my
44
+ inline <code>display</code> flips. Your text below <strong>survives</strong>
45
+ the toggle because the DOM node is never destroyed.
46
+ </p>
47
+ <input type="text" placeholder="Scratch text (survives toggle)…" />
48
+ </div>
49
+
50
+ <div class="demo-panel demo-if" $if="!compact">
51
+ <span class="demo-tag">$if</span>
52
+ <p>
53
+ Toggled with <code>$if</code> — I am <strong>unmounted</strong> and rebuilt
54
+ each time I reappear. Your text below is <strong>wiped</strong> on every toggle.
55
+ </p>
56
+ <input type="text" placeholder="Scratch text (lost on toggle)…" />
57
+ </div>
58
+ </div>
59
+
33
60
  <div class="settings-group">
34
61
  <h2>Display name</h2>
35
62
  <div class="input-row">
@@ -75,6 +102,18 @@ export class SettingsPage extends Are {
75
102
  .preview strong { color: #a78bfa; }
76
103
  .hint { font-size: 13px; color: #71717a; line-height: 1.7; }
77
104
  .hint code { background: #27272a; padding: 1px 6px; border-radius: 4px; color: #a78bfa; font-size: 12px; }
105
+ .settings-group h2 code { background: #27272a; padding: 1px 6px; border-radius: 4px; color: #a78bfa; font-size: 12px; font-weight: 600; }
106
+ .demo-panel { position: relative; background: #1c1c1f; border: 1px solid #27272a; border-radius: 10px; padding: 18px 18px 18px 20px; margin-top: 14px; }
107
+ .demo-panel p { font-size: 13px; color: #a1a1aa; line-height: 1.7; margin: 0 0 12px; }
108
+ .demo-panel p code { background: #27272a; padding: 1px 5px; border-radius: 3px; color: #a78bfa; font-size: 12px; }
109
+ .demo-panel strong { color: #e4e4e7; }
110
+ .demo-panel input[type="text"] { background: #131316; border: 1px solid #3f3f46; border-radius: 8px; color: #f4f4f5; padding: 8px 14px; font-size: 13px; outline: none; width: 100%; box-sizing: border-box; }
111
+ .demo-panel input[type="text"]:focus { border-color: #a78bfa; }
112
+ .demo-tag { display: inline-block; font-family: monospace; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 6px; margin-bottom: 10px; }
113
+ .demo-show { border-left: 3px solid #34d399; }
114
+ .demo-show .demo-tag { background: rgba(52, 211, 153, 0.12); color: #34d399; }
115
+ .demo-if { border-left: 3px solid #f59e0b; }
116
+ .demo-if .demo-tag { background: rgba(245, 158, 11, 0.12); color: #f59e0b; }
78
117
  `);
79
118
  }
80
119
 
@@ -14,6 +14,7 @@ import {
14
14
  AreHTMLEngineContext,
15
15
  AreDirectiveIf,
16
16
  AreDirectiveFor,
17
+ AreDirectiveShow,
17
18
  AreRouteWatcher,
18
19
  } from "src";
19
20
 
@@ -64,6 +65,7 @@ import { AreRoute as AreRouteSignal } from "src/signals/AreRoute.signal";
64
65
  // ── Directives ───────────────────────────────────────────
65
66
  AreDirectiveIf,
66
67
  AreDirectiveFor,
68
+ AreDirectiveShow,
67
69
  // ── Engine ───────────────────────────────────────────────
68
70
  A_SignalBus,
69
71
  AreRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaas/are-html",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "A-Concept Rendering Engine (ARE) is a powerful rendering engine designed to work seamlessly with the A-Concept framework. This library provides an HTML engine implementation of ARE, enabling developers to create dynamic and interactive user interfaces for web applications using standard HTML syntax.",
5
5
  "keywords": [
6
6
  "a-concept",
@@ -82,16 +82,16 @@
82
82
  "build": "tsup --config tsup.config.ts"
83
83
  },
84
84
  "peerDependencies": {
85
- "@adaas/a-concept": "^0.3.25",
86
- "@adaas/a-frame": "^0.1.8",
87
- "@adaas/a-utils": "^0.3.29",
88
- "@adaas/are": "^0.0.19"
85
+ "@adaas/a-concept": "^0.3.27",
86
+ "@adaas/a-frame": "^0.1.14",
87
+ "@adaas/a-utils": "^0.3.32",
88
+ "@adaas/are": "^0.0.21"
89
89
  },
90
90
  "devDependencies": {
91
- "@adaas/a-concept": "^0.3.25",
92
- "@adaas/a-frame": "^0.1.8",
93
- "@adaas/a-utils": "^0.3.29",
94
- "@adaas/are": "^0.0.19",
91
+ "@adaas/a-concept": "^0.3.27",
92
+ "@adaas/a-frame": "^0.1.14",
93
+ "@adaas/a-utils": "^0.3.32",
94
+ "@adaas/are": "^0.0.21",
95
95
  "@types/chai": "^4.3.14",
96
96
  "@types/jest": "^29.5.12",
97
97
  "@types/mocha": "^10.0.6",
@@ -134,6 +134,25 @@ export class AreDirectiveFor extends AreDirective {
134
134
 
135
135
  attribute.value = newArray;
136
136
 
137
+ /**
138
+ * Is this `$for`'s subtree currently rendered into the DOM?
139
+ *
140
+ * A `$for` can update while its subtree is detached — e.g. it lives
141
+ * inside a `$if` whose condition is currently false (the documented
142
+ * `<div $if><x $for></div>` nesting). The directive still receives the
143
+ * store change and re-diffs, but it must NOT mount/unmount item nodes
144
+ * directly while detached: the `$for` anchor (and its ancestors) are
145
+ * not in the DOM, so the interpreter's mount-point walk would fall
146
+ * through to the nearest *mounted* ancestor (the `$if` comment in the
147
+ * grandparent) and HOIST the items out of their intended container.
148
+ * When the ancestor `$if` later activates, its mount cascade applies
149
+ * the already-compiled item instructions in the correct place.
150
+ *
151
+ * Detached === any ancestor scene is inactive (regular nodes default
152
+ * to an active scene; only structural directives deactivate one).
153
+ */
154
+ const attached = this.isAttached(owner);
155
+
137
156
  const computeKey = this.makeKeyFn(key, index, trackExpr);
138
157
 
139
158
  // ── 1. Index existing children by stable key ────────────────────────
@@ -180,7 +199,10 @@ export class AreDirectiveFor extends AreDirective {
180
199
 
181
200
  // ── 3. Unmount + detach removed children ─────────────────────────────
182
201
  for (const child of remaining) {
183
- child.unmount();
202
+ // Only revert DOM if the subtree is live; a detached subtree's item
203
+ // nodes were never mounted (see `attached` rationale above), so
204
+ // unmounting them is a no-op at best and risks reverting stale state.
205
+ if (attached) child.unmount();
184
206
  owner.removeChild(child);
185
207
  }
186
208
 
@@ -188,8 +210,28 @@ export class AreDirectiveFor extends AreDirective {
188
210
  for (const child of newOnes) {
189
211
  child.transform();
190
212
  child.compile();
191
- child.mount();
213
+ // While detached, stop after compile: the item's instructions are
214
+ // planned and the ancestor `$if`'s mount cascade will apply them in
215
+ // the correct container once the condition becomes truthy. Mounting
216
+ // here would hoist the item to the nearest mounted ancestor.
217
+ if (attached) child.mount();
218
+ }
219
+ }
220
+
221
+
222
+ /**
223
+ * Walks the node's ancestor chain (inclusive) and reports whether the
224
+ * whole path is currently active — i.e. the subtree is actually rendered
225
+ * into the DOM. A single inactive ancestor scene (e.g. a `$if` whose
226
+ * condition is false) means the subtree is detached.
227
+ */
228
+ private isAttached(node: AreHTMLNode): boolean {
229
+ let current: AreHTMLNode | undefined = node;
230
+ while (current) {
231
+ if (current.scene?.isInactive) return false;
232
+ current = current.parent as AreHTMLNode | undefined;
192
233
  }
234
+ return true;
193
235
  }
194
236
 
195
237
 
@@ -0,0 +1,127 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { A_Logger } from "@adaas/a-utils/a-logger";
3
+ import { AreDirectiveAttribute } from "@adaas/are-html/attributes/AreDirective.attribute";
4
+ import { AreScene, AreStore, AreSyntax } from "@adaas/are";
5
+ import { AreDirective } from "@adaas/are-html/directive/AreDirective.component";
6
+ import { AreDirectiveContext } from "@adaas/are-html/directive/AreDirective.context";
7
+ import { HideElementInstruction } from "@adaas/are-html/instructions/HideElement.instruction";
8
+ import { A_Frame } from "@adaas/a-frame/core";
9
+
10
+
11
+
12
+ /**
13
+ * `$show` directive — conditionally toggles an element's visibility.
14
+ *
15
+ * Unlike `$if`, `$show` keeps the element fully mounted at all times and only
16
+ * flips its inline `display` (Vue `v-show` semantics). The element's subtree,
17
+ * event listeners and scene state are preserved across toggles, which makes it
18
+ * far cheaper than `$if` for things that flip on/off frequently. Use `$if` when
19
+ * the hidden branch is expensive and rarely shown; use `$show` when it toggles
20
+ * often.
21
+ *
22
+ * ⚠️ Known limitations:
23
+ * - Do NOT combine `$show` with `$if`/`$for` on the SAME element — they share
24
+ * an owner node and would fight over its host instruction. Wrap one in a
25
+ * parent element instead.
26
+ * - `$show` forces inline `display:none`, which beats stylesheet rules but will
27
+ * NOT override the element's own inline `:style="display:..."` binding.
28
+ */
29
+ @A_Frame.Define({
30
+ namespace: 'a-are-html',
31
+ description: 'Built-in $show directive. Toggles an element\'s visibility by flipping its inline display value based on a store expression, keeping the element mounted (subtree, listeners and scene state preserved) instead of unmounting it like $if.'
32
+ })
33
+ @AreDirective.Priority(3)
34
+ export class AreDirectiveShow extends AreDirective {
35
+
36
+
37
+ @AreDirective.Transform
38
+ transform(
39
+ @A_Inject(A_Caller) attribute: AreDirectiveAttribute,
40
+ @A_Inject(A_Logger) logger: A_Logger,
41
+ ...args: any[]
42
+ ) {
43
+ // $show makes no structural change to the tree — the element stays in
44
+ // place and is only hidden/shown at interpret time. Nothing to do here
45
+ // beyond overriding the base directive's default transform warning.
46
+ logger.debug(`[Transform] directive $SHOW for <${attribute.owner.aseid.toString()}> (no structural change)`)
47
+ }
48
+
49
+
50
+ @AreDirective.Compile
51
+ compile(
52
+ @A_Inject(A_Caller) attribute: AreDirectiveAttribute,
53
+ @A_Inject(AreStore) store: AreStore,
54
+ @A_Inject(AreScene) scene: AreScene,
55
+ @A_Inject(AreSyntax) syntax: AreSyntax,
56
+
57
+ @A_Inject(AreDirectiveContext) directiveContext?: AreDirectiveContext,
58
+ ...args: any[]
59
+ ): void {
60
+ /**
61
+ * 1. Evaluate the expression to determine the initial visibility.
62
+ */
63
+ const visible = !!syntax.evaluate(attribute.content, store, {
64
+ ...(directiveContext?.scope || {}),
65
+ });
66
+
67
+ attribute.value = visible;
68
+
69
+ /**
70
+ * 2. Create a single reusable HideElement mutation parented to the
71
+ * element's host instruction, and cache it on the attribute so
72
+ * update() can plan/unplan the exact same instance.
73
+ */
74
+ const hide = new HideElementInstruction(scene.host!, {});
75
+ attribute.cache = hide;
76
+
77
+ /**
78
+ * 3. When initially hidden, plan the mutation so the first interpret
79
+ * applies `display:none`. When visible, leave it unplanned.
80
+ */
81
+ if (!visible)
82
+ scene.plan(hide);
83
+ }
84
+
85
+
86
+ @AreDirective.Update
87
+ update(
88
+ @A_Inject(A_Caller) attribute: AreDirectiveAttribute,
89
+ @A_Inject(AreStore) store: AreStore,
90
+ @A_Inject(AreScene) scene: AreScene,
91
+ @A_Inject(AreSyntax) syntax: AreSyntax,
92
+
93
+ @A_Inject(AreDirectiveContext) directiveContext?: AreDirectiveContext,
94
+ ...args: any[]
95
+ ): void {
96
+ /**
97
+ * 1. Re-evaluate the expression, forwarding the directive context scope
98
+ * (e.g. a `$for` loop variable) so `$show` reacts correctly inside
99
+ * loops.
100
+ */
101
+ const previous = !!attribute.value;
102
+ const next = !!syntax.evaluate(attribute.content, store, {
103
+ ...(directiveContext?.scope || {}),
104
+ });
105
+
106
+ attribute.value = next;
107
+
108
+ // Skip when visibility has not changed — avoids redundant DOM writes.
109
+ if (previous === next) return;
110
+
111
+ const hide = attribute.cache as HideElementInstruction | undefined;
112
+
113
+ if (!hide) return;
114
+
115
+ /**
116
+ * 2. Toggle the cached mutation: unplan to reveal, plan to hide. Then
117
+ * re-interpret the owner so the scene diff applies/reverts it.
118
+ */
119
+ if (next)
120
+ scene.unPlan(hide);
121
+ else
122
+ scene.plan(hide);
123
+
124
+ attribute.owner.interpret();
125
+ }
126
+
127
+ }
@@ -14,6 +14,7 @@ import { AreHTMLLifecycle } from "@adaas/are-html/lifecycle";
14
14
  import { AreHTMLTransformer } from "@adaas/are-html/transformer";
15
15
  import { AreHTMLCompiler } from "./AreHTML.compiler";
16
16
  import { isVoidElement } from "./AreHTML.constants";
17
+ import { AreRootCache } from "../lib/AreRoot/AreRootCache.context";
17
18
 
18
19
 
19
20
 
@@ -81,7 +82,8 @@ export class AreHTMLEngine extends AreEngine {
81
82
  })
82
83
  async init(
83
84
  @A_Inject(A_Scope) scope: A_Scope,
84
- @A_Inject(AreSignalsContext) signalContext?: AreSignalsContext
85
+ @A_Inject(AreSignalsContext) signalContext?: AreSignalsContext,
86
+ @A_Inject(AreRootCache) rootCache?: AreRootCache
85
87
  ) {
86
88
  this.package(scope, {
87
89
  context: new AreHTMLEngineContext({}),
@@ -97,6 +99,14 @@ export class AreHTMLEngine extends AreEngine {
97
99
  signalContext = new AreSignalsContext();
98
100
  scope.register(signalContext);
99
101
  }
102
+
103
+ // Default per-root subtree cache used by AreRoot for fast route-back
104
+ // re-injection. Apps may register their own (e.g. with a custom limit)
105
+ // before load to override this.
106
+ if (!rootCache) {
107
+ rootCache = new AreRootCache();
108
+ scope.register(rootCache);
109
+ }
100
110
  }
101
111
 
102
112
 
@@ -15,6 +15,7 @@ import { AddElementInstruction } from "@adaas/are-html/instructions/AddElement.i
15
15
  import { AddListenerInstruction } from "@adaas/are-html/instructions/AddListener.instruction";
16
16
  import { AddTextInstruction } from "@adaas/are-html/instructions/AddText.instruction";
17
17
  import { AddStyleInstruction } from "@adaas/are-html/instructions/AddStyle.instruction";
18
+ import { HideElementInstruction } from "@adaas/are-html/instructions/HideElement.instruction";
18
19
  import { AreDirectiveContext } from "@adaas/are-html/directive/AreDirective.context";
19
20
  import { AreHTMLNode } from "../lib/AreHTMLNode/AreHTMLNode";
20
21
  import { AreHTMLEngineContext } from "./AreHTML.context";
@@ -302,6 +303,55 @@ export class AreHTMLInterpreter extends AreInterpreter {
302
303
  }
303
304
 
304
305
 
306
+ // ─────────────────────────────────────────────────────────────────────────────
307
+ // ── HideElement — Apply / Revert ─────────────────────────────────────────────
308
+ // ─────────────────────────────────────────────────────────────────────────────
309
+ // Drives the `$show` directive. Apply hides the element by forcing its inline
310
+ // `display:none` (which beats stylesheet rules and, unlike rewriting the whole
311
+ // `style` attribute, does NOT clobber other inline styles or `:style`
312
+ // bindings). The element stays mounted — its subtree, listeners and scene
313
+ // state are preserved — so toggling visibility is far cheaper than $if's
314
+ // mount/unmount cycle. Revert restores the element's previous inline display.
315
+ @A_Frame.Define({
316
+ description: 'Hide an element by setting inline display:none, caching its previous inline display value for restoration on revert.'
317
+ })
318
+ @AreInterpreter.Apply(AreHTMLInstructions.HideElement)
319
+ hideElement(
320
+ @A_Inject(A_Caller) mutation: HideElementInstruction,
321
+ @A_Inject(AreHTMLEngineContext) context: AreHTMLEngineContext,
322
+ ): void {
323
+ const element = context.getElementByInstruction(mutation.parent!);
324
+
325
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
326
+
327
+ const el = element as HTMLElement;
328
+
329
+ // Remember the element's own inline display so it can be restored exactly.
330
+ mutation.cache = el.style.display;
331
+ el.style.display = 'none';
332
+ }
333
+
334
+ @A_Frame.Define({
335
+ description: 'Restore an element hidden by a HideElement instruction back to its previous inline display value.'
336
+ })
337
+ @AreInterpreter.Revert(AreHTMLInstructions.HideElement)
338
+ showElement(
339
+ @A_Inject(A_Caller) mutation: HideElementInstruction,
340
+ @A_Inject(AreHTMLEngineContext) context: AreHTMLEngineContext,
341
+ ): void {
342
+ const element = context.getElementByInstruction(mutation.parent!);
343
+
344
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
345
+
346
+ const el = element as HTMLElement;
347
+
348
+ // Restore the cached inline display. An explicit payload display, when
349
+ // provided, takes precedence; otherwise fall back to the cached value
350
+ // (empty string clears the inline rule and reverts to the CSS default).
351
+ el.style.display = mutation.payload?.display ?? mutation.cache ?? '';
352
+ }
353
+
354
+
305
355
  // ─────────────────────────────────────────────────────────────────────────────
306
356
  // ── addEventListener — Apply / Revert ────────────────────────────────────────
307
357
  // ─────────────────────────────────────────────────────────────────────────────
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export * from './attributes/AreStatic.attribute';
12
12
  export * from './directives/AreComponent.directive';
13
13
  export * from './directives/AreDirectiveFor.directive';
14
14
  export * from './directives/AreDirectiveIf.directive';
15
+ export * from './directives/AreDirectiveShow.directive';
15
16
 
16
17
  // ─────────────────────────────────────────────────────────────────────────────
17
18
  // ── Instructions ─────────────────────────────────────────────────────────────
@@ -22,6 +23,7 @@ export * from './instructions/AddInterpolation.instruction';
22
23
  export * from './instructions/AddListener.instruction';
23
24
  export * from './instructions/AddStyle.instruction';
24
25
  export * from './instructions/AddText.instruction';
26
+ export * from './instructions/HideElement.instruction';
25
27
  export * from './instructions/AreHTML.instructions.constants';
26
28
  export * from './instructions/AreHTML.instructions.types';
27
29
 
@@ -67,6 +69,7 @@ export * from './lib/AreDirective/AreDirective.types';
67
69
  // ── Lib / AreRoot ────────────────────────────────────────────────────────────
68
70
  // ─────────────────────────────────────────────────────────────────────────────
69
71
  export * from './lib/AreRoot/AreRoot.component';
72
+ export * from './lib/AreRoot/AreRootCache.context';
70
73
 
71
74
  // ─────────────────────────────────────────────────────────────────────────────
72
75
  // ── Lib / AreStyle ───────────────────────────────────────────────────────────
@@ -8,4 +8,5 @@ export const AreHTMLInstructions = {
8
8
  AddListener: '_AreHTML_AddListener',
9
9
  AddInterpolation: '_AreHTML_AddInterpolation',
10
10
  AddComment: '_AreHTML_AddComment',
11
+ HideElement: '_AreHTML_HideElement',
11
12
  } as const
@@ -34,6 +34,15 @@ export type AreHtmlAddStyleInstructionPayload = {
34
34
  styles: string;
35
35
  }
36
36
 
37
+ export type AreHtmlHideInstructionPayload = {
38
+ /**
39
+ * Optional explicit display value to restore when the element becomes
40
+ * visible again. When omitted, the interpreter caches and restores the
41
+ * element's own prior inline `display` value (Vue `v-show` semantics).
42
+ */
43
+ display?: string;
44
+ }
45
+
37
46
  export type AreHtmlAddListenerInstructionPayload = {
38
47
  /** DOM event name (e.g. "click", "input", "submit") */
39
48
  name: string;
@@ -0,0 +1,29 @@
1
+ import { A_Frame } from "@adaas/a-frame/core"
2
+ import { AreDeclaration, AreMutation, AreInstructionSerialized } from "@adaas/are";
3
+ import { AreHtmlHideInstructionPayload } from "./AreHTML.instructions.types";
4
+ import { AreHTMLInstructions } from "./AreHTML.instructions.constants";
5
+
6
+
7
+ @A_Frame.Define({
8
+ namespace: 'a-are-html',
9
+ description: 'Toggles the visibility of an existing element by setting its inline display to "none" on apply and restoring the previous inline display on revert. Used by the $show directive to hide/show an element without unmounting it, preserving its subtree, listeners and scene state.'
10
+ })
11
+ export class HideElementInstruction extends AreMutation<AreHtmlHideInstructionPayload> {
12
+
13
+ /**
14
+ * Caches the element's inline `display` value captured at apply time so it
15
+ * can be restored verbatim on revert (mirrors Vue `v-show`).
16
+ */
17
+ cache?: string;
18
+
19
+ constructor(
20
+ parent: AreDeclaration,
21
+ props: AreHtmlHideInstructionPayload | AreInstructionSerialized<AreHtmlHideInstructionPayload>
22
+ ) {
23
+ if ('aseid' in props) {
24
+ super(props as AreInstructionSerialized<AreHtmlHideInstructionPayload>);
25
+ } else {
26
+ super(AreHTMLInstructions.HideElement, parent, props);
27
+ }
28
+ }
29
+ }