@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.
- package/dist/browser/index.d.mts +161 -5
- package/dist/browser/index.mjs +357 -55
- package/dist/browser/index.mjs.map +1 -1
- package/dist/node/directives/AreDirectiveFor.directive.d.mts +7 -0
- package/dist/node/directives/AreDirectiveFor.directive.d.ts +7 -0
- package/dist/node/directives/AreDirectiveFor.directive.js +17 -2
- package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
- package/dist/node/directives/AreDirectiveFor.directive.mjs +17 -2
- package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
- package/dist/node/directives/AreDirectiveShow.directive.d.mts +32 -0
- package/dist/node/directives/AreDirectiveShow.directive.d.ts +32 -0
- package/dist/node/directives/AreDirectiveShow.directive.js +81 -0
- package/dist/node/directives/AreDirectiveShow.directive.js.map +1 -0
- package/dist/node/directives/AreDirectiveShow.directive.mjs +71 -0
- package/dist/node/directives/AreDirectiveShow.directive.mjs.map +1 -0
- package/dist/node/engine/AreHTML.engine.d.mts +2 -1
- package/dist/node/engine/AreHTML.engine.d.ts +2 -1
- package/dist/node/engine/AreHTML.engine.js +8 -2
- package/dist/node/engine/AreHTML.engine.js.map +1 -1
- package/dist/node/engine/AreHTML.engine.mjs +8 -2
- package/dist/node/engine/AreHTML.engine.mjs.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.d.mts +3 -0
- package/dist/node/engine/AreHTML.interpreter.d.ts +3 -0
- package/dist/node/engine/AreHTML.interpreter.js +29 -0
- package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.mjs +29 -0
- package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
- package/dist/node/index.d.mts +4 -1
- package/dist/node/index.d.ts +4 -1
- package/dist/node/index.js +21 -0
- package/dist/node/index.mjs +3 -0
- package/dist/node/instructions/AreHTML.instructions.constants.d.mts +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.d.ts +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.js +2 -1
- package/dist/node/instructions/AreHTML.instructions.constants.js.map +1 -1
- package/dist/node/instructions/AreHTML.instructions.constants.mjs +2 -1
- package/dist/node/instructions/AreHTML.instructions.constants.mjs.map +1 -1
- package/dist/node/instructions/AreHTML.instructions.types.d.mts +9 -1
- package/dist/node/instructions/AreHTML.instructions.types.d.ts +9 -1
- package/dist/node/instructions/HideElement.instruction.d.mts +13 -0
- package/dist/node/instructions/HideElement.instruction.d.ts +13 -0
- package/dist/node/instructions/HideElement.instruction.js +31 -0
- package/dist/node/instructions/HideElement.instruction.js.map +1 -0
- package/dist/node/instructions/HideElement.instruction.mjs +24 -0
- package/dist/node/instructions/HideElement.instruction.mjs.map +1 -0
- package/dist/node/lib/AreRoot/AreRoot.component.d.mts +57 -3
- package/dist/node/lib/AreRoot/AreRoot.component.d.ts +57 -3
- package/dist/node/lib/AreRoot/AreRoot.component.js +137 -48
- package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.mjs +139 -50
- package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
- package/dist/node/lib/AreRoot/AreRootCache.context.d.mts +58 -0
- package/dist/node/lib/AreRoot/AreRootCache.context.d.ts +58 -0
- package/dist/node/lib/AreRoot/AreRootCache.context.js +106 -0
- package/dist/node/lib/AreRoot/AreRootCache.context.js.map +1 -0
- package/dist/node/lib/AreRoot/AreRootCache.context.mjs +99 -0
- package/dist/node/lib/AreRoot/AreRootCache.context.mjs.map +1 -0
- package/examples/jumpstart/dist/index.html +1 -1
- package/examples/jumpstart/dist/{mq1a0fv0-ccgtz6.js → mq7hqrxy-4kus50.js} +629 -433
- package/examples/signal-routing/dist/index.html +1 -1
- package/examples/signal-routing/dist/{mq1bzrik-4lec86.js → mq7k53th-qiwy4x.js} +903 -486
- package/examples/signal-routing/src/components/SettingsPage.component.ts +39 -0
- package/examples/signal-routing/src/concept.ts +2 -0
- package/package.json +9 -9
- package/src/directives/AreDirectiveFor.directive.ts +44 -2
- package/src/directives/AreDirectiveShow.directive.ts +127 -0
- package/src/engine/AreHTML.engine.ts +11 -1
- package/src/engine/AreHTML.interpreter.ts +50 -0
- package/src/index.ts +3 -0
- package/src/instructions/AreHTML.instructions.constants.ts +1 -0
- package/src/instructions/AreHTML.instructions.types.ts +9 -0
- package/src/instructions/HideElement.instruction.ts +29 -0
- package/src/lib/AreRoot/AreRoot.component.ts +201 -71
- 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.
|
|
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.
|
|
86
|
-
"@adaas/a-frame": "^0.1.
|
|
87
|
-
"@adaas/a-utils": "^0.3.
|
|
88
|
-
"@adaas/are": "^0.0.
|
|
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.
|
|
92
|
-
"@adaas/a-frame": "^0.1.
|
|
93
|
-
"@adaas/a-utils": "^0.3.
|
|
94
|
-
"@adaas/are": "^0.0.
|
|
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
|
-
|
|
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
|
-
|
|
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 ───────────────────────────────────────────────────────────
|
|
@@ -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
|
+
}
|