@supersoniks/concorde 4.0.0 → 4.2.0
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/build-infos.json +1 -1
- package/concorde-core.bundle.js +93 -93
- package/concorde-core.es.js +590 -552
- package/dist/concorde-core.bundle.js +93 -93
- package/dist/concorde-core.es.js +590 -552
- package/docs/assets/{index-B669R8JF.css → index-BBv9CZqo.css} +1 -1
- package/docs/assets/{index-BTo6ly4d.js → index-BbnRiebQ.js} +393 -321
- package/docs/index.html +2 -2
- package/docs/src/docs/_misc/on-assign.md +5 -5
- package/docs/src/docs/_misc/wait-for-ancestors.md +160 -0
- package/docs/src/docs/search/docs-search.json +280 -0
- package/docs/src/tsconfig.json +3 -9
- package/package.json +2 -1
- package/src/core/components/ui/icon/icons.ts +5 -1
- package/src/core/decorators/lifecycle.ts +79 -0
- package/src/core/utils/HTML.ts +62 -0
- package/src/core/utils/PublisherProxy.ts +8 -6
- package/src/core/utils/route.ts +21 -3
- package/src/decorators.ts +5 -0
- package/src/docs/_misc/wait-for-ancestors.md +160 -0
- package/src/docs/example/decorators-demo.ts +265 -0
- package/src/docs/navigation/navigation.ts +4 -1
- package/src/docs/search/docs-search.json +210 -0
- package/src/tsconfig.json +3 -0
- package/src/tsconfig.tsbuildinfo +1 -1
package/src/core/utils/HTML.ts
CHANGED
|
@@ -147,6 +147,68 @@ class HTML {
|
|
|
147
147
|
static getClosestForm(node: SearchableDomElement) {
|
|
148
148
|
return HTML.getClosestElement(node, "form");
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Parcourt les ancêtres (parentNode / shadow host) et collecte ceux dont le tagName
|
|
153
|
+
* correspond à l'un des noms fournis (comparaison insensible à la casse).
|
|
154
|
+
* @param node Élément de départ
|
|
155
|
+
* @param tagNames Noms de balises à rechercher (ex: ['sonic-subscriber', 'sonic-sdui'])
|
|
156
|
+
* @returns Tableau des ancêtres correspondants
|
|
157
|
+
*/
|
|
158
|
+
static getAncestorsByTagNames(
|
|
159
|
+
node: SearchableDomElement,
|
|
160
|
+
tagNames: string[]
|
|
161
|
+
): Element[] {
|
|
162
|
+
const normalized = new Set(tagNames.map((t) => t.toLowerCase()));
|
|
163
|
+
const result: Element[] = [];
|
|
164
|
+
let current = (node.parentNode ||
|
|
165
|
+
(node as ShadowRoot).host) as SearchableDomElement;
|
|
166
|
+
while (current) {
|
|
167
|
+
if (
|
|
168
|
+
current instanceof Element &&
|
|
169
|
+
normalized.has(current.tagName.toLowerCase())
|
|
170
|
+
) {
|
|
171
|
+
result.push(current);
|
|
172
|
+
}
|
|
173
|
+
current = (current.parentNode ||
|
|
174
|
+
(current as ShadowRoot).host) as SearchableDomElement;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parcourt les ancêtres (parentNode / shadow host) et collecte ceux qui matchent
|
|
181
|
+
* l'un des sélecteurs CSS fournis (element.matches(selector)).
|
|
182
|
+
* @param node Élément de départ
|
|
183
|
+
* @param selectors Sélecteurs CSS (ex: ['sonic-subscriber', 'sonic-sdui', 'div.container'])
|
|
184
|
+
* @returns Tableau des ancêtres correspondants
|
|
185
|
+
*/
|
|
186
|
+
static getAncestorsBySelectors(
|
|
187
|
+
node: SearchableDomElement,
|
|
188
|
+
selectors: string[]
|
|
189
|
+
): Element[] {
|
|
190
|
+
const result: Element[] = [];
|
|
191
|
+
let current = (node.parentNode ||
|
|
192
|
+
(node as ShadowRoot).host) as SearchableDomElement;
|
|
193
|
+
while (current) {
|
|
194
|
+
if (current instanceof Element) {
|
|
195
|
+
for (const selector of selectors) {
|
|
196
|
+
try {
|
|
197
|
+
if (current.matches(selector)) {
|
|
198
|
+
result.push(current);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// Invalid selector, skip
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
current = (current.parentNode ||
|
|
207
|
+
(current as ShadowRoot).host) as SearchableDomElement;
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
/**
|
|
151
213
|
* Lance le chargement d'un js et retourne une promise qui resoud à true lorsque le chargement à réussi et à false, sinon.
|
|
152
214
|
* */
|
|
@@ -31,7 +31,7 @@ function isLeaf(value: any) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function isComplex(value: any) {
|
|
34
|
-
return typeof value === "object" && value
|
|
34
|
+
return typeof value === "object" && value !== null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
declare const __BUILD_DATE__: string;
|
|
@@ -315,11 +315,13 @@ export class PublisherProxy<T = any> {
|
|
|
315
315
|
* et qu'elle est primitive
|
|
316
316
|
*/
|
|
317
317
|
if (
|
|
318
|
-
this._value_ &&
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
318
|
+
(isComplex(this._value_) &&
|
|
319
|
+
isComplex(newValue) &&
|
|
320
|
+
newValue &&
|
|
321
|
+
isLeaf(this._value_) &&
|
|
322
|
+
isLeaf(newValue) &&
|
|
323
|
+
(this._value_ as any).__value === (newValue as any).__value) ||
|
|
324
|
+
(!isComplex(newValue) && newValue === (this._value_ as any).__value)
|
|
323
325
|
) {
|
|
324
326
|
return true;
|
|
325
327
|
}
|
package/src/core/utils/route.ts
CHANGED
|
@@ -30,10 +30,28 @@ export class Route<RoutesData = any> {
|
|
|
30
30
|
matchesLocation() {
|
|
31
31
|
return this.matchesCurrentPath();
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
matchesCurrentPath() {
|
|
34
35
|
return this.extract() !== null;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
removeQuery() {
|
|
39
|
+
const url = new URL(this.route, window.location.origin);
|
|
40
|
+
url.search = "";
|
|
41
|
+
this.route = url.href.replace(window.location.origin, "");
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
withoutQuery() {
|
|
46
|
+
return this.clone().removeQuery();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
clone() {
|
|
50
|
+
const clonedRoute = new Route();
|
|
51
|
+
clonedRoute.route = this.route;
|
|
52
|
+
return clonedRoute;
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
fill(properties: Object) {
|
|
38
56
|
this.route = new URLPattern(this.route).stringify(properties);
|
|
39
57
|
return this;
|
|
@@ -98,7 +116,7 @@ export class Routes {
|
|
|
98
116
|
static register<RouteData = any>(
|
|
99
117
|
routes: Partial<RouteData>,
|
|
100
118
|
baseUrlOrOpt: { baseUrl?: string; prefix?: string } | string = "",
|
|
101
|
-
prefix = ""
|
|
119
|
+
prefix = "",
|
|
102
120
|
) {
|
|
103
121
|
const newRoutes: Partial<RouteData> = {};
|
|
104
122
|
Object.keys(routes).forEach((key) => {
|
|
@@ -125,7 +143,7 @@ export class Routes {
|
|
|
125
143
|
}
|
|
126
144
|
static get<RouteData extends Record<keyof RouteData, string | undefined>>(
|
|
127
145
|
name: keyof RouteData,
|
|
128
|
-
args?: RouteArgs
|
|
146
|
+
args?: RouteArgs,
|
|
129
147
|
) {
|
|
130
148
|
const routes = Routes.routes as Partial<RouteData>;
|
|
131
149
|
let route = routes[name] || "";
|
|
@@ -134,7 +152,7 @@ export class Routes {
|
|
|
134
152
|
}
|
|
135
153
|
const url = new URL(
|
|
136
154
|
route,
|
|
137
|
-
route.startsWith("#") ? window.location.href : window.location.origin
|
|
155
|
+
route.startsWith("#") ? window.location.href : window.location.origin,
|
|
138
156
|
);
|
|
139
157
|
if (args?.query) {
|
|
140
158
|
Object.entries(args.query).forEach(([key, value]) => {
|
package/src/decorators.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as mySubscriber from "@supersoniks/concorde/core/decorators/Subscriber";
|
|
2
|
+
import * as lifecycle from "@supersoniks/concorde/core/decorators/lifecycle";
|
|
3
|
+
|
|
2
4
|
export const bind = mySubscriber.bind;
|
|
3
5
|
export const onAssign = mySubscriber.onAssign;
|
|
4
6
|
export const ancestorAttribute = mySubscriber.ancestorAttribute;
|
|
5
7
|
export const autoSubscribe = mySubscriber.autoSubscribe;
|
|
6
8
|
export const autoFill = mySubscriber.autoFill;
|
|
9
|
+
export const awaitConnectedAncestors = lifecycle.awaitConnectedAncestors;
|
|
10
|
+
export const dispatchConnectedEvent = lifecycle.dispatchConnectedEvent;
|
|
11
|
+
export const CONNECTED = lifecycle.CONNECTED;
|
|
7
12
|
|
|
8
13
|
import { ConcordeWindow } from "./core/_types/types";
|
|
9
14
|
declare const window: ConcordeWindow;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# @awaitConnectedAncestors and @dispatchConnectedEvent
|
|
2
|
+
|
|
3
|
+
The `@awaitConnectedAncestors` and `@dispatchConnectedEvent` decorators delay a web component's initialization until its matching ancestors have executed their `connectedCallback`. This is when contextual elements (publisher, dataProvider, etc.) are configured.
|
|
4
|
+
|
|
5
|
+
## Principle
|
|
6
|
+
|
|
7
|
+
When a child component attaches to the DOM, its ancestors may not yet be initialized (especially if custom element definitions are loaded asynchronously). The `@awaitConnectedAncestors` decorator delays the component's `connectedCallback` until all ancestors matching the provided CSS selectors have executed their `connectedCallback`.
|
|
8
|
+
|
|
9
|
+
The `@dispatchConnectedEvent` decorator allows ancestors to signal they are ready by dispatching the `sonic-connected` event at the end of their `connectedCallback`. The event bubbles, so it can be listened to from anywhere (e.g. `document.addEventListener(CONNECTED, handler)`).
|
|
10
|
+
|
|
11
|
+
Ancestors that are not web components (no hyphen in tag name) are considered connected by default and do not need to emit the event.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Import
|
|
16
|
+
|
|
17
|
+
<sonic-code language="typescript">
|
|
18
|
+
<template>
|
|
19
|
+
import { awaitConnectedAncestors, dispatchConnectedEvent, ancestorAttribute } from "@supersoniks/concorde/decorators";
|
|
20
|
+
</template>
|
|
21
|
+
</sonic-code>
|
|
22
|
+
|
|
23
|
+
### Basic example
|
|
24
|
+
|
|
25
|
+
An ancestor container decorated with `@dispatchConnectedEvent()` signals when it is ready. A child component decorated with `@awaitConnectedAncestors("demo-wait-ancestor-container[dataProvider]")` waits for this container to be initialized before initializing itself. Parameters are CSS selectors (`element.matches()`).
|
|
26
|
+
|
|
27
|
+
The parent is registered via `customElements.define()` (vanilla JS) rather than `@customElement`, so it can be defined later—e.g. when the user clicks a button. This demonstrates the child waiting until the parent exists.
|
|
28
|
+
|
|
29
|
+
<sonic-code language="typescript">
|
|
30
|
+
<template>
|
|
31
|
+
import { html, LitElement } from "lit";
|
|
32
|
+
import { customElement, state } from "lit/decorators.js";
|
|
33
|
+
//
|
|
34
|
+
// Parent: registered later via customElements.define(), not @customElement
|
|
35
|
+
@dispatchConnectedEvent()
|
|
36
|
+
export class DemoWaitAncestorContainer extends LitElement {
|
|
37
|
+
render() {
|
|
38
|
+
return html`<slot></slot>`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//
|
|
42
|
+
// Child: waits for parent before initializing
|
|
43
|
+
@customElement("demo-wait-ancestor-value")
|
|
44
|
+
@awaitConnectedAncestors("demo-wait-ancestor-container[dataProvider]")
|
|
45
|
+
export class DemoWaitAncestorValue extends LitElement {
|
|
46
|
+
@ancestorAttribute("dataProvider")
|
|
47
|
+
dataProvider: string | null = null;
|
|
48
|
+
//
|
|
49
|
+
@state() initializedAt: string = "";
|
|
50
|
+
//
|
|
51
|
+
connectedCallback() {
|
|
52
|
+
super.connectedCallback();
|
|
53
|
+
this.initializedAt = new Date().toISOString();
|
|
54
|
+
}
|
|
55
|
+
//
|
|
56
|
+
render() {
|
|
57
|
+
return html`
|
|
58
|
+
<p>DataProvider from ancestor: <strong>${this.dataProvider || "—"}</strong></p>
|
|
59
|
+
<p>Initialized at: ${this.initializedAt || "(waiting for parent…)"}</p>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//
|
|
64
|
+
// Demo section: register parent via customElements.define() when user clicks
|
|
65
|
+
@customElement("demo-wait-ancestors-section")
|
|
66
|
+
export class DemoWaitAncestorsSection extends LitElement {
|
|
67
|
+
registerParent() {
|
|
68
|
+
if (!customElements.get("demo-wait-ancestor-container")) {
|
|
69
|
+
customElements.define("demo-wait-ancestor-container", DemoWaitAncestorContainer);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
render() {
|
|
73
|
+
return html`
|
|
74
|
+
<sonic-button @click=${this.registerParent}>Register parent component</sonic-button>
|
|
75
|
+
<demo-wait-ancestor-container dataProvider="waitAncestorDemo">
|
|
76
|
+
<demo-wait-ancestor-value></demo-wait-ancestor-value>
|
|
77
|
+
</demo-wait-ancestor-container>
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</template>
|
|
82
|
+
</sonic-code>
|
|
83
|
+
|
|
84
|
+
<sonic-code>
|
|
85
|
+
<template>
|
|
86
|
+
<demo-wait-ancestors-section></demo-wait-ancestors-section>
|
|
87
|
+
</template>
|
|
88
|
+
</sonic-code>
|
|
89
|
+
|
|
90
|
+
### Multiple ancestors
|
|
91
|
+
|
|
92
|
+
The child waits for all specified ancestors. Register outer first, then inner — the child initializes only when both are ready.
|
|
93
|
+
|
|
94
|
+
<sonic-code>
|
|
95
|
+
<template>
|
|
96
|
+
<demo-wait-ancestors-multi-section></demo-wait-ancestors-multi-section>
|
|
97
|
+
</template>
|
|
98
|
+
</sonic-code>
|
|
99
|
+
|
|
100
|
+
### Ancestors already connected
|
|
101
|
+
|
|
102
|
+
When the parent is defined at load and already in the DOM, the child initializes immediately (no delay).
|
|
103
|
+
|
|
104
|
+
**Static (both in DOM from start):**
|
|
105
|
+
|
|
106
|
+
<sonic-code>
|
|
107
|
+
<template>
|
|
108
|
+
<demo-wait-ancestors-static-section></demo-wait-ancestors-static-section>
|
|
109
|
+
</template>
|
|
110
|
+
</sonic-code>
|
|
111
|
+
|
|
112
|
+
**Dynamic (child added on button click):**
|
|
113
|
+
|
|
114
|
+
<sonic-code>
|
|
115
|
+
<template>
|
|
116
|
+
<demo-wait-ancestors-ready-section></demo-wait-ancestors-ready-section>
|
|
117
|
+
</template>
|
|
118
|
+
</sonic-code>
|
|
119
|
+
|
|
120
|
+
## CSS selector support
|
|
121
|
+
|
|
122
|
+
Parameters are CSS selectors matched via `element.matches()` — e.g. `"sonic-subscriber"`, `"sonic-subscriber[dataProvider]"`, `".my-container"`, or multiple: `"sonic-subscriber", "sonic-sdui"`.
|
|
123
|
+
|
|
124
|
+
## Behavior
|
|
125
|
+
|
|
126
|
+
- Ancestor search uses CSS selectors (`element.matches(selector)`) — supports tag names, classes, attributes, combinators, etc.
|
|
127
|
+
- Traversal includes shadow roots (parentNode / host)
|
|
128
|
+
- Non-web components (no hyphen in tag name) are considered connected by default
|
|
129
|
+
- For web component ancestors, it waits for `customElements.whenDefined(tagName)` and the `sonic-connected` event (or a timeout as fallback)
|
|
130
|
+
- If no matching ancestor is found, the original `connectedCallback` is called immediately
|
|
131
|
+
- Ancestors that do not emit `sonic-connected` trigger the fallback after the timeout (compatibility with existing components)
|
|
132
|
+
|
|
133
|
+
## Use cases
|
|
134
|
+
|
|
135
|
+
These decorators are particularly useful for:
|
|
136
|
+
|
|
137
|
+
- **sonic-value** inside a **sonic-subscriber**: the value waits for the subscriber to configure its publisher
|
|
138
|
+
- **Components inside sonic-sdui**: wait for the SDUI to load and configure its context
|
|
139
|
+
- **Any component** depending on context provided by an ancestor custom element
|
|
140
|
+
|
|
141
|
+
## Listening to the connected event
|
|
142
|
+
|
|
143
|
+
The `sonic-connected` event bubbles, so you can listen to it from anywhere:
|
|
144
|
+
|
|
145
|
+
<sonic-code language="typescript">
|
|
146
|
+
<template>
|
|
147
|
+
import { CONNECTED } from "@supersoniks/concorde/decorators";
|
|
148
|
+
//
|
|
149
|
+
someConnectable.addEventListener(CONNECTED, (e) => {
|
|
150
|
+
console.log("Component connected:", e.target);
|
|
151
|
+
});
|
|
152
|
+
</template>
|
|
153
|
+
</sonic-code>
|
|
154
|
+
|
|
155
|
+
## Notes
|
|
156
|
+
|
|
157
|
+
- These decorators apply only to web components (classes extending `HTMLElement`)
|
|
158
|
+
- The fallback timeout ensures compatibility with components that do not yet use `@dispatchConnectedEvent`
|
|
159
|
+
- Traversal includes shadow roots
|
|
160
|
+
- For a guarantee without relying on the timeout, decorate ancestors (Subscriber, Fetcher, etc.) with `@dispatchConnectedEvent`
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
ancestorAttribute,
|
|
6
6
|
onAssign,
|
|
7
7
|
autoSubscribe,
|
|
8
|
+
awaitConnectedAncestors,
|
|
9
|
+
dispatchConnectedEvent,
|
|
8
10
|
} from "@supersoniks/concorde/decorators";
|
|
9
11
|
import { sub } from "@supersoniks/concorde/directives";
|
|
10
12
|
import {
|
|
@@ -56,6 +58,7 @@ const initializeDecoratorsDemoData = () => {
|
|
|
56
58
|
|
|
57
59
|
ensurePublisherValue("autoValue1", 10);
|
|
58
60
|
ensurePublisherValue("autoValue2", 20);
|
|
61
|
+
ensurePublisherValue("waitAncestorDemo", { message: "Context from ancestor" });
|
|
59
62
|
|
|
60
63
|
ensurePublisherValue("combinedData", { title: "Combined Title" });
|
|
61
64
|
ensurePublisherValue("combinedUser", { name: "Combined User" });
|
|
@@ -656,3 +659,265 @@ export class DemoAutoSubscribe extends LitElement {
|
|
|
656
659
|
value.set(Math.floor(Math.random() * 100));
|
|
657
660
|
}
|
|
658
661
|
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Parent component for awaitConnectedAncestors demo.
|
|
665
|
+
* Registered via customElements.define() on button click (not @customElement).
|
|
666
|
+
*/
|
|
667
|
+
@dispatchConnectedEvent()
|
|
668
|
+
export class DemoWaitAncestorContainer extends LitElement {
|
|
669
|
+
render() {
|
|
670
|
+
return html`<slot></slot>`;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Demo component showcasing @awaitConnectedAncestors decorator (child).
|
|
676
|
+
* Uses CSS selector: tag + attribute.
|
|
677
|
+
*/
|
|
678
|
+
@customElement("demo-wait-ancestor-value")
|
|
679
|
+
@awaitConnectedAncestors("demo-wait-ancestor-container[dataProvider]")
|
|
680
|
+
export class DemoWaitAncestorValue extends LitElement {
|
|
681
|
+
@ancestorAttribute("dataProvider")
|
|
682
|
+
dataProvider: string | null = null;
|
|
683
|
+
|
|
684
|
+
@state() initializedAt = "";
|
|
685
|
+
|
|
686
|
+
connectedCallback() {
|
|
687
|
+
super.connectedCallback();
|
|
688
|
+
this.initializedAt = new Date().toISOString();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
render() {
|
|
692
|
+
return html`
|
|
693
|
+
<p>
|
|
694
|
+
DataProvider from ancestor:
|
|
695
|
+
<strong>${this.dataProvider || "—"}</strong>
|
|
696
|
+
</p>
|
|
697
|
+
<p>Initialized at: ${this.initializedAt || "(waiting for parent…)"}</p>
|
|
698
|
+
`;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Demo section with button to register parent component on demand.
|
|
704
|
+
* Demonstrates that the child waits until the parent is defined.
|
|
705
|
+
*/
|
|
706
|
+
@customElement("demo-wait-ancestors-section")
|
|
707
|
+
export class DemoWaitAncestorsSection extends LitElement {
|
|
708
|
+
static styles = [tailwind];
|
|
709
|
+
|
|
710
|
+
@state() parentRegistered = false;
|
|
711
|
+
|
|
712
|
+
registerParent() {
|
|
713
|
+
if (customElements.get("demo-wait-ancestor-container")) {
|
|
714
|
+
this.parentRegistered = true;
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
customElements.define(
|
|
718
|
+
"demo-wait-ancestor-container",
|
|
719
|
+
DemoWaitAncestorContainer
|
|
720
|
+
);
|
|
721
|
+
this.parentRegistered = true;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
render() {
|
|
725
|
+
return html`
|
|
726
|
+
<div class="space-y-4">
|
|
727
|
+
<sonic-button
|
|
728
|
+
?disabled=${this.parentRegistered}
|
|
729
|
+
@click=${this.registerParent}
|
|
730
|
+
>
|
|
731
|
+
${this.parentRegistered
|
|
732
|
+
? "Parent already registered"
|
|
733
|
+
: "Register parent component"}
|
|
734
|
+
</sonic-button>
|
|
735
|
+
<demo-wait-ancestor-container dataProvider="waitAncestorDemo">
|
|
736
|
+
<demo-wait-ancestor-value></demo-wait-ancestor-value>
|
|
737
|
+
</demo-wait-ancestor-container>
|
|
738
|
+
</div>
|
|
739
|
+
`;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// --- Multiple ancestors demo ---
|
|
744
|
+
|
|
745
|
+
@dispatchConnectedEvent()
|
|
746
|
+
export class DemoWaitAncestorOuter extends LitElement {
|
|
747
|
+
render() {
|
|
748
|
+
return html`<slot></slot>`;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
@dispatchConnectedEvent()
|
|
753
|
+
export class DemoWaitAncestorInner extends LitElement {
|
|
754
|
+
render() {
|
|
755
|
+
return html`<slot></slot>`;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
@customElement("demo-wait-ancestor-value-multi")
|
|
760
|
+
@awaitConnectedAncestors("demo-wait-ancestor-outer", "demo-wait-ancestor-inner")
|
|
761
|
+
export class DemoWaitAncestorValueMulti extends LitElement {
|
|
762
|
+
@ancestorAttribute("dataProvider")
|
|
763
|
+
dataProvider: string | null = null;
|
|
764
|
+
|
|
765
|
+
@state() initializedAt = "";
|
|
766
|
+
|
|
767
|
+
connectedCallback() {
|
|
768
|
+
super.connectedCallback();
|
|
769
|
+
this.initializedAt = new Date().toISOString();
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
render() {
|
|
773
|
+
return html`
|
|
774
|
+
<p>
|
|
775
|
+
DataProvider from ancestor:
|
|
776
|
+
<strong>${this.dataProvider || "—"}</strong>
|
|
777
|
+
</p>
|
|
778
|
+
<p>Initialized at: ${this.initializedAt || "(waiting for both ancestors…)"}</p>
|
|
779
|
+
`;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Demo: child waits for multiple ancestors.
|
|
785
|
+
* Register outer first, then inner — child initializes only when both are ready.
|
|
786
|
+
*/
|
|
787
|
+
@customElement("demo-wait-ancestors-multi-section")
|
|
788
|
+
export class DemoWaitAncestorsMultiSection extends LitElement {
|
|
789
|
+
static styles = [tailwind];
|
|
790
|
+
|
|
791
|
+
@state() outerRegistered = false;
|
|
792
|
+
@state() innerRegistered = false;
|
|
793
|
+
|
|
794
|
+
registerOuter() {
|
|
795
|
+
if (!customElements.get("demo-wait-ancestor-outer")) {
|
|
796
|
+
customElements.define("demo-wait-ancestor-outer", DemoWaitAncestorOuter);
|
|
797
|
+
}
|
|
798
|
+
this.outerRegistered = true;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
registerInner() {
|
|
802
|
+
if (!customElements.get("demo-wait-ancestor-inner")) {
|
|
803
|
+
customElements.define("demo-wait-ancestor-inner", DemoWaitAncestorInner);
|
|
804
|
+
}
|
|
805
|
+
this.innerRegistered = true;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
render() {
|
|
809
|
+
return html`
|
|
810
|
+
<div class="space-y-4">
|
|
811
|
+
<div class="flex gap-2 flex-wrap">
|
|
812
|
+
<sonic-button
|
|
813
|
+
?disabled=${this.outerRegistered}
|
|
814
|
+
@click=${this.registerOuter}
|
|
815
|
+
>
|
|
816
|
+
${this.outerRegistered ? "Outer registered" : "Register outer"}
|
|
817
|
+
</sonic-button>
|
|
818
|
+
<sonic-button
|
|
819
|
+
?disabled=${this.innerRegistered}
|
|
820
|
+
@click=${this.registerInner}
|
|
821
|
+
>
|
|
822
|
+
${this.innerRegistered ? "Inner registered" : "Register inner"}
|
|
823
|
+
</sonic-button>
|
|
824
|
+
</div>
|
|
825
|
+
<demo-wait-ancestor-outer>
|
|
826
|
+
<demo-wait-ancestor-inner dataProvider="waitAncestorDemo">
|
|
827
|
+
<demo-wait-ancestor-value-multi></demo-wait-ancestor-value-multi>
|
|
828
|
+
</demo-wait-ancestor-inner>
|
|
829
|
+
</demo-wait-ancestor-outer>
|
|
830
|
+
</div>
|
|
831
|
+
`;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// --- Ancestors already connected demo ---
|
|
836
|
+
|
|
837
|
+
@customElement("demo-wait-ancestor-ready")
|
|
838
|
+
@dispatchConnectedEvent()
|
|
839
|
+
export class DemoWaitAncestorReady extends LitElement {
|
|
840
|
+
render() {
|
|
841
|
+
return html`<slot></slot>`;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
@customElement("demo-wait-ancestor-value-ready")
|
|
846
|
+
@awaitConnectedAncestors("demo-wait-ancestor-ready")
|
|
847
|
+
export class DemoWaitAncestorValueReady extends LitElement {
|
|
848
|
+
@ancestorAttribute("dataProvider")
|
|
849
|
+
dataProvider: string | null = null;
|
|
850
|
+
|
|
851
|
+
@state() initializedAt = "";
|
|
852
|
+
|
|
853
|
+
connectedCallback() {
|
|
854
|
+
super.connectedCallback();
|
|
855
|
+
this.initializedAt = new Date().toISOString();
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
render() {
|
|
859
|
+
return html`
|
|
860
|
+
<p>
|
|
861
|
+
DataProvider: <strong>${this.dataProvider || "—"}</strong>
|
|
862
|
+
</p>
|
|
863
|
+
<p>Initialized at: ${this.initializedAt}</p>
|
|
864
|
+
`;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Demo: ancestors already connected at load.
|
|
870
|
+
* Parent is defined with @customElement. Child is added dynamically — it
|
|
871
|
+
* initializes immediately because the ancestor is already ready.
|
|
872
|
+
*/
|
|
873
|
+
@customElement("demo-wait-ancestors-ready-section")
|
|
874
|
+
export class DemoWaitAncestorsReadySection extends LitElement {
|
|
875
|
+
static styles = [tailwind];
|
|
876
|
+
|
|
877
|
+
@state() childInDom = false;
|
|
878
|
+
|
|
879
|
+
addChild() {
|
|
880
|
+
this.childInDom = true;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
render() {
|
|
884
|
+
return html`
|
|
885
|
+
<div class="space-y-4">
|
|
886
|
+
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
887
|
+
Parent is defined at load. Click to add child dynamically — it
|
|
888
|
+
initializes immediately because the ancestor is already ready.
|
|
889
|
+
</p>
|
|
890
|
+
<sonic-button ?disabled=${this.childInDom} @click=${this.addChild}>
|
|
891
|
+
${this.childInDom ? "Child added" : "Add child dynamically"}
|
|
892
|
+
</sonic-button>
|
|
893
|
+
<demo-wait-ancestor-ready dataProvider="waitAncestorDemo">
|
|
894
|
+
${this.childInDom
|
|
895
|
+
? html`<demo-wait-ancestor-value-ready></demo-wait-ancestor-value-ready>`
|
|
896
|
+
: html``}
|
|
897
|
+
</demo-wait-ancestor-ready>
|
|
898
|
+
</div>
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Demo: parent and child both in DOM from start, parent defined at load.
|
|
905
|
+
* Child initializes immediately (no delay) because ancestor is already ready.
|
|
906
|
+
*/
|
|
907
|
+
@customElement("demo-wait-ancestors-static-section")
|
|
908
|
+
export class DemoWaitAncestorsStaticSection extends LitElement {
|
|
909
|
+
static styles = [tailwind];
|
|
910
|
+
|
|
911
|
+
render() {
|
|
912
|
+
return html`
|
|
913
|
+
<p class="text-sm text-neutral-600 dark:text-neutral-400 mb-4">
|
|
914
|
+
Parent and child both in DOM from load. Child initializes immediately
|
|
915
|
+
because the ancestor is already defined and connected.
|
|
916
|
+
</p>
|
|
917
|
+
<demo-wait-ancestor-ready dataProvider="waitAncestorDemo">
|
|
918
|
+
<demo-wait-ancestor-value-ready></demo-wait-ancestor-value-ready>
|
|
919
|
+
</demo-wait-ancestor-ready>
|
|
920
|
+
`;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
@@ -146,7 +146,10 @@ export class DocsNavigation extends LitElement {
|
|
|
146
146
|
label: "@autoSubscribe",
|
|
147
147
|
href: "#docs/_misc/auto-subscribe.md/auto-subscribe",
|
|
148
148
|
},
|
|
149
|
-
|
|
149
|
+
{
|
|
150
|
+
label: "@awaitConnectedAncestors",
|
|
151
|
+
href: "#docs/_misc/wait-for-ancestors.md/wait-for-ancestors",
|
|
152
|
+
},
|
|
150
153
|
{
|
|
151
154
|
label: "Templates Demo",
|
|
152
155
|
href: "#docs/_misc/templates-demo.md/templates-demo",
|