@adaas/are-html 0.0.3 → 0.0.5
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/README.md +4 -4
- package/dist/browser/index.d.mts +12 -4
- package/dist/browser/index.mjs +47 -20
- package/dist/browser/index.mjs.map +1 -1
- package/dist/node/engine/AreHTML.compiler.js +10 -0
- package/dist/node/engine/AreHTML.compiler.js.map +1 -1
- package/dist/node/engine/AreHTML.compiler.mjs +10 -0
- package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
- package/dist/node/engine/AreHTML.constants.d.mts +10 -1
- package/dist/node/engine/AreHTML.constants.d.ts +10 -1
- package/dist/node/engine/AreHTML.constants.js +21 -0
- package/dist/node/engine/AreHTML.constants.js.map +1 -1
- package/dist/node/engine/AreHTML.constants.mjs +20 -1
- package/dist/node/engine/AreHTML.constants.mjs.map +1 -1
- package/dist/node/engine/AreHTML.engine.js +8 -0
- package/dist/node/engine/AreHTML.engine.js.map +1 -1
- package/dist/node/engine/AreHTML.engine.mjs +8 -0
- package/dist/node/engine/AreHTML.engine.mjs.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.js +1 -0
- package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.mjs +1 -0
- package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
- package/dist/node/engine/AreHTML.lifecycle.d.mts +1 -2
- package/dist/node/engine/AreHTML.lifecycle.d.ts +1 -2
- package/dist/node/engine/AreHTML.lifecycle.js +2 -11
- package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
- package/dist/node/engine/AreHTML.lifecycle.mjs +2 -11
- package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
- package/dist/node/index.d.mts +2 -2
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +3 -3
- package/dist/node/index.mjs +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.js +2 -3
- package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.mjs +2 -3
- package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
- package/dist/node/lib/{AreWatcher/AreWatcher.component.d.mts → AreRouteWatcher/AreRouteWatcher.component.d.mts} +2 -2
- package/dist/node/lib/{AreWatcher/AreWatcher.component.d.ts → AreRouteWatcher/AreRouteWatcher.component.d.ts} +2 -2
- package/dist/node/lib/{AreWatcher/AreWatcher.component.js → AreRouteWatcher/AreRouteWatcher.component.js} +6 -6
- package/dist/node/lib/AreRouteWatcher/AreRouteWatcher.component.js.map +1 -0
- package/dist/node/lib/{AreWatcher/AreWatcher.component.mjs → AreRouteWatcher/AreRouteWatcher.component.mjs} +7 -7
- package/dist/node/lib/AreRouteWatcher/AreRouteWatcher.component.mjs.map +1 -0
- package/dist/node/signals/AreRoute.signal.js +1 -1
- package/dist/node/signals/AreRoute.signal.js.map +1 -1
- package/dist/node/signals/AreRoute.signal.mjs +1 -1
- package/dist/node/signals/AreRoute.signal.mjs.map +1 -1
- package/examples/dashboard/dist/index.html +1 -1
- package/examples/dashboard/dist/{mpioi5ab-8c3oa9.js → mpm5e2oi-ghokyu.js} +2 -2
- package/examples/jumpstart/dist/mor90p6y-0plg7g.js +8 -8
- package/examples/jumpstart/dist/mor90p7p-1898bz.js +8 -8
- package/jest.config.ts +1 -1
- package/package.json +1 -1
- package/src/engine/AreHTML.compiler.ts +19 -1
- package/src/engine/AreHTML.constants.ts +16 -0
- package/src/engine/AreHTML.engine.ts +10 -0
- package/src/engine/AreHTML.interpreter.ts +3 -0
- package/src/engine/AreHTML.lifecycle.ts +13 -12
- package/src/index.ts +2 -2
- package/src/lib/AreRoot/AreRoot.component.ts +5 -6
- package/src/lib/{AreWatcher/AreWatcher.component.ts → AreRouteWatcher/AreRouteWatcher.component.ts} +2 -2
- package/src/signals/AreRoute.signal.ts +1 -1
- package/dist/node/lib/AreWatcher/AreWatcher.component.js.map +0 -1
- package/dist/node/lib/AreWatcher/AreWatcher.component.mjs.map +0 -1
|
@@ -101,8 +101,26 @@ export class AreHTMLCompiler extends AreCompiler {
|
|
|
101
101
|
title: 'Scene Host Not Found',
|
|
102
102
|
description: `No host found for the scene with id: ${scene.id}. Please ensure that the scene is properly initialized and has a host before compiling binding attributes.`
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
const content = attribute.content;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* If the attribute value contains {{ }} interpolations, transform them into
|
|
109
|
+
* a JS string-concatenation expression so the interpreter can evaluate them.
|
|
110
|
+
* e.g. "color:{{expr}}" → '"color:"+(expr)+""'
|
|
111
|
+
*/
|
|
112
|
+
if (content.includes('{{')) {
|
|
113
|
+
const transformed = '"' + content.replace(/\{\{([^}]+)\}\}/g, '"+($1)+"') + '"';
|
|
114
|
+
scene.plan(new AddAttributeInstruction(scene.host, {
|
|
115
|
+
name: attribute.name,
|
|
116
|
+
content: transformed,
|
|
117
|
+
evaluate: true,
|
|
118
|
+
}));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
/**
|
|
105
|
-
* Default case
|
|
123
|
+
* Default case: regular static attribute rendered as-is.
|
|
106
124
|
*/
|
|
107
125
|
scene.plan(new AddAttributeInstruction(scene.host, {
|
|
108
126
|
name: attribute.name,
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Void HTML elements that cannot have children and must not have a closing tag.
|
|
3
|
+
* Per the HTML5 spec these are treated as self-closing even when written as
|
|
4
|
+
* `<input>` (without the trailing slash `/>`).
|
|
5
|
+
*
|
|
6
|
+
* Reference: https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
7
|
+
*/
|
|
8
|
+
export const VOID_ELEMENTS = new Set<string>([
|
|
9
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
10
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function isVoidElement(tagName: string): boolean {
|
|
14
|
+
return VOID_ELEMENTS.has(tagName.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
/**
|
|
2
18
|
* Boolean HTML attributes whose presence (regardless of value) implies "true",
|
|
3
19
|
* and whose absence implies "false". Setting these via `setAttribute(name, value)`
|
|
@@ -13,6 +13,7 @@ import { AreRootNode } from "@adaas/are-html/nodes/AreRoot";
|
|
|
13
13
|
import { AreHTMLLifecycle } from "@adaas/are-html/lifecycle";
|
|
14
14
|
import { AreHTMLTransformer } from "@adaas/are-html/transformer";
|
|
15
15
|
import { AreHTMLCompiler } from "./AreHTML.compiler";
|
|
16
|
+
import { isVoidElement } from "./AreHTML.constants";
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
|
|
@@ -152,6 +153,15 @@ export class AreHTMLEngine extends AreEngine {
|
|
|
152
153
|
return match
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
// HTML5 void elements: <input>, <br>, <img>, etc. — treat as self-closing
|
|
157
|
+
if (isVoidElement(tagName)) {
|
|
158
|
+
const raw = source.slice(tagStart, openingTagEnd + 1)
|
|
159
|
+
const content = source.slice(tagStart + tagNameMatch[0].length, openingTagEnd)
|
|
160
|
+
const match = build(raw, content, tagStart, '>')
|
|
161
|
+
match.payload = { entity: tagName, selfClose: true, id }
|
|
162
|
+
return match
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
// find matching closing tag respecting nesting
|
|
156
166
|
const closingTag = `</${tagName}>`
|
|
157
167
|
let level = 0
|
|
@@ -337,6 +337,9 @@ export class AreHTMLInterpreter extends AreInterpreter {
|
|
|
337
337
|
event.set('args', effectiveArgs);
|
|
338
338
|
event.set('element', element);
|
|
339
339
|
event.set('instruction', mutation);
|
|
340
|
+
// Expose the raw DOM event under the conventional 'native' key so that
|
|
341
|
+
// event handlers can do: event.get('native')?.target as HTMLInputElement
|
|
342
|
+
if (liveEvent) event.set('native', liveEvent as any);
|
|
340
343
|
mutation.owner.emit(event);
|
|
341
344
|
};
|
|
342
345
|
handlerScope[`$${handler}`] = handlerFn;
|
|
@@ -19,19 +19,8 @@ import { A_Frame } from "@adaas/a-frame/core";
|
|
|
19
19
|
export class AreHTMLLifecycle extends AreLifecycle {
|
|
20
20
|
|
|
21
21
|
@AreLifecycle.Init(AreComponentNode)
|
|
22
|
-
initComponent(
|
|
23
|
-
@A_Inject(A_Caller) node: AreHTMLNode,
|
|
24
|
-
@A_Inject(A_Scope) scope: A_Scope,
|
|
25
|
-
@A_Inject(AreHTMLEngineContext) context: AreHTMLEngineContext,
|
|
26
|
-
@A_Inject(A_Logger) logger?: A_Logger,
|
|
27
|
-
...args: any[]
|
|
28
|
-
): void {
|
|
29
|
-
super.init(node, scope, context, logger, ...args);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
22
|
@AreLifecycle.Init(AreRootNode)
|
|
34
|
-
|
|
23
|
+
initComponent(
|
|
35
24
|
@A_Inject(A_Caller) node: AreHTMLNode,
|
|
36
25
|
@A_Inject(A_Scope) scope: A_Scope,
|
|
37
26
|
@A_Inject(AreHTMLEngineContext) context: AreHTMLEngineContext,
|
|
@@ -44,6 +33,18 @@ export class AreHTMLLifecycle extends AreLifecycle {
|
|
|
44
33
|
}
|
|
45
34
|
|
|
46
35
|
|
|
36
|
+
// initRoot(
|
|
37
|
+
// @A_Inject(A_Caller) node: AreHTMLNode,
|
|
38
|
+
// @A_Inject(A_Scope) scope: A_Scope,
|
|
39
|
+
// @A_Inject(AreHTMLEngineContext) context: AreHTMLEngineContext,
|
|
40
|
+
// @A_Inject(AreSignalsContext) signalsContext?: AreSignalsContext,
|
|
41
|
+
// @A_Inject(A_Logger) logger?: A_Logger,
|
|
42
|
+
// ...args: any[]
|
|
43
|
+
// ): void {
|
|
44
|
+
// super.init(node, scope, context, logger, ...args);
|
|
45
|
+
// }
|
|
46
|
+
|
|
47
|
+
|
|
47
48
|
@AreLifecycle.Init(AreText)
|
|
48
49
|
initText(
|
|
49
50
|
@A_Inject(A_Caller) node: AreHTMLNode,
|
package/src/index.ts
CHANGED
|
@@ -75,6 +75,6 @@ export * from './lib/AreStyle/AreStyle.context';
|
|
|
75
75
|
export * from './lib/AreStyle/AreStyle.types';
|
|
76
76
|
|
|
77
77
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
|
-
// ── Lib /
|
|
78
|
+
// ── Lib / AreRouteWatcher ─────────────────────────────────────────────────────────
|
|
79
79
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
-
export * from './lib/
|
|
80
|
+
export * from './lib/AreRouteWatcher/AreRouteWatcher.component';
|
|
@@ -54,11 +54,12 @@ export class AreRoot extends Are {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// 3. Fall back to the 'default' attribute on the node directly
|
|
58
|
-
//
|
|
57
|
+
// 3. Fall back to the 'default' attribute on the node directly.
|
|
58
|
+
// Note: root.attributes is NOT populated at this stage because tokenize()
|
|
59
|
+
// runs after template() in the lifecycle. Read from raw markup instead.
|
|
59
60
|
if (!componentName) {
|
|
60
|
-
const
|
|
61
|
-
componentName =
|
|
61
|
+
const defaultMatch = root.markup?.match(/\bdefault=["']([^"']*)["']/);
|
|
62
|
+
componentName = defaultMatch?.[1];
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (!componentName) {
|
|
@@ -78,8 +79,6 @@ export class AreRoot extends Are {
|
|
|
78
79
|
@A_Inject(A_Logger) logger: A_Logger,
|
|
79
80
|
@A_Inject(AreSignalsContext) signalsContext?: AreSignalsContext,
|
|
80
81
|
) {
|
|
81
|
-
console.log('Received signal vector in AreRoot:', root, vector);
|
|
82
|
-
|
|
83
82
|
const rootId = root.id;
|
|
84
83
|
// No routing config for this root — signals do not affect its content
|
|
85
84
|
if (signalsContext && !signalsContext.hasRoot(rootId)) {
|
package/src/lib/{AreWatcher/AreWatcher.component.ts → AreRouteWatcher/AreRouteWatcher.component.ts}
RENAMED
|
@@ -3,9 +3,9 @@ import { A_Frame } from "@adaas/a-frame/core";
|
|
|
3
3
|
|
|
4
4
|
@A_Frame.Define({
|
|
5
5
|
namespace: 'a-are-html',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'AreRouteWatcher is a component that observes browser navigation events (history pushState, replaceState, and popstate) and notifies registered handlers when the URL changes, enabling client-side routing and reactive route-based rendering within the ARE framework.'
|
|
7
7
|
})
|
|
8
|
-
export class
|
|
8
|
+
export class AreRouteWatcher extends A_Component {
|
|
9
9
|
|
|
10
10
|
private readonly handlers: Set<(url: URL) => void> = new Set();
|
|
11
11
|
private current: URL = new URL(window.location.href);
|
|
@@ -7,7 +7,7 @@ import { A_Frame } from "@adaas/a-frame/core";
|
|
|
7
7
|
|
|
8
8
|
@A_Frame.Define({
|
|
9
9
|
namespace: 'a-are-html',
|
|
10
|
-
description: 'ARE signal that carries an A_Route value. Dispatched by
|
|
10
|
+
description: 'ARE signal that carries an A_Route value. Dispatched by AreRouteWatcher on client-side navigation events (pushState, replaceState, popstate). The signal bus delivers it to all subscribed root nodes, triggering route-based conditional rendering across the component tree.'
|
|
11
11
|
})
|
|
12
12
|
export class AreRoute extends AreSignal<A_Route> {
|
|
13
13
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/AreWatcher/AreWatcher.component.ts"],"names":["AreWatcher","A_Component","A_Frame"],"mappings":";;;;;;;;;;;;;AAOaA,kBAAA,GAAN,yBAAyBC,oBAAA,CAAY;AAAA,EAKxC,WAAA,GAAc;AACV,IAAA,KAAA,EAAM;AAJV,IAAA,IAAA,CAAiB,QAAA,uBAAwC,GAAA,EAAI;AAC7D,IAAA,IAAA,CAAQ,OAAA,GAAe,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AA4BnD;AAAA,IAAA,IAAA,CAAQ,aAAa,MAAY;AAC7B,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA;AAEA,IAAA,IAAA,CAAQ,eAAe,MAAY;AAC/B,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA;AAEA,IAAA,IAAA,CAAQ,cAAc,MAAY;AAC9B,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA;AAlCI,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,eAAA,EAAgB;AAAA,EACzB;AAAA;AAAA,EAIA,SAAS,OAAA,EAAyC;AAC9C,IAAA,IAAA,CAAK,QAAA,CAAS,IAAI,OAAO,CAAA;AACzB,IAAA,OAAO,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,OAAO,CAAA;AAAA,EAC7C;AAAA,EAEA,IAAI,GAAA,GAAW;AACX,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EAChB;AAAA,EAEA,OAAA,GAAgB;AACZ,IAAA,MAAA,CAAO,mBAAA,CAAoB,UAAA,EAAY,IAAA,CAAK,UAAU,CAAA;AACtD,IAAA,MAAA,CAAO,mBAAA,CAAoB,YAAA,EAAc,IAAA,CAAK,YAAY,CAAA;AAC1D,IAAA,MAAA,CAAO,mBAAA,CAAoB,WAAA,EAAa,IAAA,CAAK,WAAW,CAAA;AACxD,IAAA,IAAA,CAAK,SAAS,KAAA,EAAM;AAAA,EACxB;AAAA,EAgBQ,eAAA,GAAwB;AAC5B,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAA,EAAY,IAAA,CAAK,UAAU,CAAA;AACnD,IAAA,MAAA,CAAO,gBAAA,CAAiB,YAAA,EAAc,IAAA,CAAK,YAAY,CAAA;AACvD,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAA,EAAa,IAAA,CAAK,WAAW,CAAA;AAAA,EACzD;AAAA;AAAA,EAIQ,YAAA,GAAqB;AACzB,IAAA,MAAM,KAAA,GAAQ,CAAC,QAAA,KACX,SAAA,GAA4B,IAAA,EAA4C;AACpE,MAAA,QAAA,CAAS,KAAA,CAAM,MAAM,IAAI,CAAA;AACzB,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,KAAA,CAAM,WAAW,CAAC,CAAA;AAAA,IAC/C,CAAA;AAEJ,IAAA,OAAA,CAAQ,SAAA,GAAY,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA;AAC3C,IAAA,OAAA,CAAQ,YAAA,GAAe,KAAA,CAAM,OAAA,CAAQ,YAAY,CAAA;AAAA,EACrD;AAAA;AAAA,EAIQ,MAAA,GAAe;AACnB,IAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AAEzC,IAAA,IAAI,IAAA,CAAK,IAAA,KAAS,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM;AAErC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAEf,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,QAAA,EAAU;AACjC,MAAA,OAAA,CAAQ,KAAK,OAAO,CAAA;AAAA,IACxB;AAAA,EACJ;AACJ;AA3EaD,kBAAA,GAAN,eAAA,CAAA;AAAA,EAJNE,aAAQ,MAAA,CAAO;AAAA,IACZ,SAAA,EAAW,YAAA;AAAA,IACX,WAAA,EAAa;AAAA,GAChB;AAAA,CAAA,EACYF,kBAAA,CAAA","file":"AreWatcher.component.js","sourcesContent":["import { A_Component } from \"@adaas/a-concept\";\nimport { A_Frame } from \"@adaas/a-frame/core\";\n\n@A_Frame.Define({\n namespace: 'a-are-html',\n description: 'AreWatcher is a component that observes browser navigation events (history pushState, replaceState, and popstate) and notifies registered handlers when the URL changes, enabling client-side routing and reactive route-based rendering within the ARE framework.'\n})\nexport class AreWatcher extends A_Component {\n\n private readonly handlers: Set<(url: URL) => void> = new Set();\n private current: URL = new URL(window.location.href);\n\n constructor() {\n super();\n this.patchHistory();\n this.attachListeners();\n }\n\n // ── Public ────────────────────────────────────────────────────────────────\n\n onChange(handler: (url: URL) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler); // returns unsubscribe fn\n }\n\n get url(): URL {\n return this.current;\n }\n\n destroy(): void {\n window.removeEventListener('popstate', this.onPopState);\n window.removeEventListener('hashchange', this.onHashChange);\n window.removeEventListener('urlchange', this.onURLChange);\n this.handlers.clear();\n }\n\n // ── Listeners ─────────────────────────────────────────────────────────────\n\n private onPopState = (): void => {\n this.notify();\n }\n\n private onHashChange = (): void => {\n this.notify();\n }\n\n private onURLChange = (): void => {\n this.notify();\n }\n\n private attachListeners(): void {\n window.addEventListener('popstate', this.onPopState);\n window.addEventListener('hashchange', this.onHashChange);\n window.addEventListener('urlchange', this.onURLChange); // custom event from patch\n }\n\n // ── Patch pushState / replaceState ────────────────────────────────────────\n\n private patchHistory(): void {\n const patch = (original: typeof history.pushState) =>\n function (this: History, ...args: Parameters<typeof history.pushState>) {\n original.apply(this, args);\n window.dispatchEvent(new Event('urlchange'));\n };\n\n history.pushState = patch(history.pushState);\n history.replaceState = patch(history.replaceState);\n }\n\n // ── Notify ────────────────────────────────────────────────────────────────\n\n private notify(): void {\n const next = new URL(window.location.href);\n\n if (next.href === this.current.href) return; // no actual change\n\n this.current = next;\n\n for (const handler of this.handlers) {\n handler(this.current);\n }\n }\n}"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/AreWatcher/AreWatcher.component.ts"],"names":[],"mappings":";;;;AAOO,IAAM,UAAA,GAAN,cAAyB,WAAA,CAAY;AAAA,EAKxC,WAAA,GAAc;AACV,IAAA,KAAA,EAAM;AAJV,IAAA,IAAA,CAAiB,QAAA,uBAAwC,GAAA,EAAI;AAC7D,IAAA,IAAA,CAAQ,OAAA,GAAe,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AA4BnD;AAAA,IAAA,IAAA,CAAQ,aAAa,MAAY;AAC7B,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA;AAEA,IAAA,IAAA,CAAQ,eAAe,MAAY;AAC/B,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA;AAEA,IAAA,IAAA,CAAQ,cAAc,MAAY;AAC9B,MAAA,IAAA,CAAK,MAAA,EAAO;AAAA,IAChB,CAAA;AAlCI,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,eAAA,EAAgB;AAAA,EACzB;AAAA;AAAA,EAIA,SAAS,OAAA,EAAyC;AAC9C,IAAA,IAAA,CAAK,QAAA,CAAS,IAAI,OAAO,CAAA;AACzB,IAAA,OAAO,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,OAAO,CAAA;AAAA,EAC7C;AAAA,EAEA,IAAI,GAAA,GAAW;AACX,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EAChB;AAAA,EAEA,OAAA,GAAgB;AACZ,IAAA,MAAA,CAAO,mBAAA,CAAoB,UAAA,EAAY,IAAA,CAAK,UAAU,CAAA;AACtD,IAAA,MAAA,CAAO,mBAAA,CAAoB,YAAA,EAAc,IAAA,CAAK,YAAY,CAAA;AAC1D,IAAA,MAAA,CAAO,mBAAA,CAAoB,WAAA,EAAa,IAAA,CAAK,WAAW,CAAA;AACxD,IAAA,IAAA,CAAK,SAAS,KAAA,EAAM;AAAA,EACxB;AAAA,EAgBQ,eAAA,GAAwB;AAC5B,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAA,EAAY,IAAA,CAAK,UAAU,CAAA;AACnD,IAAA,MAAA,CAAO,gBAAA,CAAiB,YAAA,EAAc,IAAA,CAAK,YAAY,CAAA;AACvD,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAA,EAAa,IAAA,CAAK,WAAW,CAAA;AAAA,EACzD;AAAA;AAAA,EAIQ,YAAA,GAAqB;AACzB,IAAA,MAAM,KAAA,GAAQ,CAAC,QAAA,KACX,SAAA,GAA4B,IAAA,EAA4C;AACpE,MAAA,QAAA,CAAS,KAAA,CAAM,MAAM,IAAI,CAAA;AACzB,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,KAAA,CAAM,WAAW,CAAC,CAAA;AAAA,IAC/C,CAAA;AAEJ,IAAA,OAAA,CAAQ,SAAA,GAAY,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA;AAC3C,IAAA,OAAA,CAAQ,YAAA,GAAe,KAAA,CAAM,OAAA,CAAQ,YAAY,CAAA;AAAA,EACrD;AAAA;AAAA,EAIQ,MAAA,GAAe;AACnB,IAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AAEzC,IAAA,IAAI,IAAA,CAAK,IAAA,KAAS,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM;AAErC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAEf,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,QAAA,EAAU;AACjC,MAAA,OAAA,CAAQ,KAAK,OAAO,CAAA;AAAA,IACxB;AAAA,EACJ;AACJ;AA3Ea,UAAA,GAAN,eAAA,CAAA;AAAA,EAJN,QAAQ,MAAA,CAAO;AAAA,IACZ,SAAA,EAAW,YAAA;AAAA,IACX,WAAA,EAAa;AAAA,GAChB;AAAA,CAAA,EACY,UAAA,CAAA","file":"AreWatcher.component.mjs","sourcesContent":["import { A_Component } from \"@adaas/a-concept\";\nimport { A_Frame } from \"@adaas/a-frame/core\";\n\n@A_Frame.Define({\n namespace: 'a-are-html',\n description: 'AreWatcher is a component that observes browser navigation events (history pushState, replaceState, and popstate) and notifies registered handlers when the URL changes, enabling client-side routing and reactive route-based rendering within the ARE framework.'\n})\nexport class AreWatcher extends A_Component {\n\n private readonly handlers: Set<(url: URL) => void> = new Set();\n private current: URL = new URL(window.location.href);\n\n constructor() {\n super();\n this.patchHistory();\n this.attachListeners();\n }\n\n // ── Public ────────────────────────────────────────────────────────────────\n\n onChange(handler: (url: URL) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler); // returns unsubscribe fn\n }\n\n get url(): URL {\n return this.current;\n }\n\n destroy(): void {\n window.removeEventListener('popstate', this.onPopState);\n window.removeEventListener('hashchange', this.onHashChange);\n window.removeEventListener('urlchange', this.onURLChange);\n this.handlers.clear();\n }\n\n // ── Listeners ─────────────────────────────────────────────────────────────\n\n private onPopState = (): void => {\n this.notify();\n }\n\n private onHashChange = (): void => {\n this.notify();\n }\n\n private onURLChange = (): void => {\n this.notify();\n }\n\n private attachListeners(): void {\n window.addEventListener('popstate', this.onPopState);\n window.addEventListener('hashchange', this.onHashChange);\n window.addEventListener('urlchange', this.onURLChange); // custom event from patch\n }\n\n // ── Patch pushState / replaceState ────────────────────────────────────────\n\n private patchHistory(): void {\n const patch = (original: typeof history.pushState) =>\n function (this: History, ...args: Parameters<typeof history.pushState>) {\n original.apply(this, args);\n window.dispatchEvent(new Event('urlchange'));\n };\n\n history.pushState = patch(history.pushState);\n history.replaceState = patch(history.replaceState);\n }\n\n // ── Notify ────────────────────────────────────────────────────────────────\n\n private notify(): void {\n const next = new URL(window.location.href);\n\n if (next.href === this.current.href) return; // no actual change\n\n this.current = next;\n\n for (const handler of this.handlers) {\n handler(this.current);\n }\n }\n}"]}
|