@supersoniks/concorde 4.6.0 → 4.7.3
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/.gitlab-ci.yml +23 -0
- package/README.md +106 -55
- package/ai/AGENTS.md +52 -0
- package/ai/README.md +30 -0
- package/ai/cursor/rules/concorde-menu.mdc +15 -0
- package/ai/cursor/rules/concorde-scope.mdc +14 -0
- package/ai/cursor/rules/concorde-theme.mdc +13 -0
- package/ai/cursor/rules/concorde.mdc +49 -0
- package/ai/jetbrains/rules/concorde.md +39 -0
- package/ai/skills/concorde/SKILL.md +273 -0
- package/ai/skills/concorde-get-set-dp/SKILL.md +194 -0
- package/ai/skills/concorde-imports/SKILL.md +78 -0
- package/ai/skills/concorde-menu/SKILL.md +74 -0
- package/ai/skills/concorde-scope/SKILL.md +70 -0
- package/ai/skills/concorde-theme/SKILL.md +46 -0
- package/build-infos.json +1 -1
- package/concorde-core.bundle.js +152 -152
- package/concorde-core.es.js +1853 -1689
- package/dist/altcha-widget.js +2662 -0
- package/dist/concorde-core.bundle.js +152 -152
- package/dist/concorde-core.es.js +1853 -1689
- package/dist/docs-mock-api-sw.js +589 -0
- package/dist/docs-mock-api-sw.js.map +7 -0
- package/docs/altcha-widget.js +2662 -0
- package/docs/assets/index-D9pxaQYK.js +7508 -0
- package/docs/assets/index-t0-i22oI.css +1 -0
- package/docs/docs-mock-api-sw.js +589 -0
- package/docs/docs-mock-api-sw.js.map +7 -0
- package/docs/index.html +2 -2
- package/docs/src/core/components/functional/fetch/fetch.md +13 -11
- package/docs/src/core/components/functional/if/if.md +4 -11
- package/docs/src/core/components/functional/list/list.md +60 -194
- package/docs/src/core/components/functional/queue/queue.md +70 -85
- package/docs/src/core/components/functional/router/router.md +62 -97
- package/docs/src/core/components/functional/states/states.md +2 -2
- package/docs/src/core/components/functional/submit/submit.md +86 -55
- package/docs/src/core/components/ui/captcha/captcha.md +2 -2
- package/docs/src/core/components/ui/card/card.md +1 -1
- package/docs/src/core/components/ui/form/checkbox/checkbox.md +5 -32
- package/docs/src/core/components/ui/form/input/input.md +5 -30
- package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +6 -4
- package/docs/src/core/components/ui/form/radio/radio.md +5 -32
- package/docs/src/core/components/ui/form/select/select.md +5 -31
- package/docs/src/core/components/ui/form/switch/switch.md +5 -32
- package/docs/src/core/components/ui/loader/loader.md +1 -13
- package/docs/src/core/components/ui/table/table.md +3 -3
- package/docs/src/docs/_core-concept/dataFlow.md +73 -0
- package/docs/src/docs/_core-concept/subscriber.md +9 -10
- package/docs/src/docs/_decorators/ancestor-attribute.md +4 -3
- package/docs/src/docs/_decorators/auto-subscribe.md +19 -16
- package/docs/src/docs/_decorators/bind.md +20 -17
- package/docs/src/docs/_decorators/get.md +7 -4
- package/docs/src/docs/_decorators/handle.md +171 -0
- package/docs/src/docs/_decorators/on-assign.md +99 -73
- package/docs/src/docs/_decorators/publish.md +2 -1
- package/docs/src/docs/_decorators/subscribe.md +70 -9
- package/docs/src/docs/_decorators/wait-for-ancestors.md +13 -10
- package/docs/src/docs/_directives/sub.md +91 -0
- package/docs/src/docs/_getting-started/ai-agents.md +56 -0
- package/docs/src/docs/_getting-started/concorde-manual-install.md +133 -0
- package/docs/src/docs/_getting-started/concorde-outside.md +13 -123
- package/docs/src/docs/_getting-started/create-a-component.md +2 -0
- package/docs/src/docs/_getting-started/my-first-component.md +236 -0
- package/docs/src/docs/_getting-started/my-first-subscriber.md +29 -83
- package/docs/src/docs/_getting-started/pubsub.md +21 -134
- package/docs/src/docs/_getting-started/start.md +26 -18
- package/docs/src/docs/_misc/api-configuration.md +79 -0
- package/docs/src/docs/_misc/dataProviderKey.md +38 -5
- package/docs/src/docs/_misc/docs-mock-api.md +60 -0
- package/docs/src/docs/_misc/endpoint.md +2 -1
- package/docs/src/docs/_misc/html-integration.md +13 -0
- package/docs/src/docs/search/docs-search.json +4163 -873
- package/docs/src/tsconfig.json +380 -317
- package/gitlab/job_tests.sh +55 -0
- package/package.json +34 -3
- package/public/altcha-widget.js +2662 -0
- package/public/docs-mock-api-sw.js +589 -0
- package/public/docs-mock-api-sw.js.map +7 -0
- package/scripts/ai-init.mjs +167 -0
- package/scripts/docs-mock-api-vite-plugin.ts +116 -0
- package/scripts/docs-open-in-editor-plugin.ts +130 -0
- package/scripts/pre-publish.mjs +2 -1
- package/src/core/components/functional/example/example.ts +1 -1
- package/src/core/components/functional/fetch/fetch.md +13 -11
- package/src/core/components/functional/if/if.md +4 -11
- package/src/core/components/functional/list/list.demo.ts +4 -4
- package/src/core/components/functional/list/list.md +60 -194
- package/src/core/components/functional/list/list.ts +8 -7
- package/src/core/components/functional/queue/queue.demo.ts +1 -1
- package/src/core/components/functional/queue/queue.md +70 -85
- package/src/core/components/functional/queue/queue.ts +4 -4
- package/src/core/components/functional/router/router.md +62 -97
- package/src/core/components/functional/router/router.ts +1 -1
- package/src/core/components/functional/states/states.md +2 -2
- package/src/core/components/functional/submit/submit.md +86 -55
- package/src/core/components/functional/submit/submit.ts +10 -3
- package/src/core/components/ui/captcha/captcha.md +2 -2
- package/src/core/components/ui/card/card.md +1 -1
- package/src/core/components/ui/form/checkbox/checkbox.md +5 -32
- package/src/core/components/ui/form/input/input.md +5 -30
- package/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +6 -4
- package/src/core/components/ui/form/radio/radio.md +5 -32
- package/src/core/components/ui/form/select/select.md +5 -31
- package/src/core/components/ui/form/switch/switch.md +5 -32
- package/src/core/components/ui/loader/loader.md +1 -13
- package/src/core/components/ui/table/table.md +3 -3
- package/src/core/decorators/api.spec.ts +8 -1
- package/src/core/decorators/api.ts +126 -15
- package/src/core/directives/DataProvider.sub.spec.ts +96 -0
- package/src/core/directives/DataProvider.ts +109 -40
- package/src/core/utils/HTML.ts +42 -10
- package/src/core/utils/PublisherProxy.ts +33 -18
- package/src/core/utils/dataProviderKey.ts +23 -0
- package/src/core/utils/publisherPathKey.spec.ts +58 -0
- package/src/docs/_core-concept/dataFlow.md +73 -0
- package/src/docs/_core-concept/subscriber.md +9 -10
- package/src/docs/_decorators/ancestor-attribute.md +4 -3
- package/src/docs/_decorators/auto-subscribe.md +19 -16
- package/src/docs/_decorators/bind.md +19 -16
- package/src/docs/_decorators/get.md +7 -4
- package/src/docs/_decorators/handle.md +15 -13
- package/src/docs/_decorators/on-assign.md +53 -53
- package/src/docs/_decorators/publish.md +2 -1
- package/src/docs/_decorators/subscribe.md +70 -9
- package/src/docs/_decorators/wait-for-ancestors.md +13 -10
- package/src/docs/_directives/sub.md +91 -0
- package/src/docs/_getting-started/ai-agents.md +56 -0
- package/src/docs/_getting-started/concorde-manual-install.md +133 -0
- package/src/docs/_getting-started/concorde-outside.md +13 -123
- package/src/docs/_getting-started/create-a-component.md +2 -0
- package/src/docs/_getting-started/my-first-component.md +236 -0
- package/src/docs/_getting-started/my-first-subscriber.md +29 -83
- package/src/docs/_getting-started/pubsub.md +21 -134
- package/src/docs/_getting-started/start.md +26 -18
- package/src/docs/_misc/api-configuration.md +79 -0
- package/src/docs/_misc/dataProviderKey.md +34 -1
- package/src/docs/_misc/docs-mock-api.md +60 -0
- package/src/docs/_misc/endpoint.md +2 -1
- package/src/docs/_misc/html-integration.md +13 -0
- package/src/docs/code.ts +58 -12
- package/src/docs/components/docs-demo-sources.ts +397 -0
- package/src/docs/components/docs-lit-demo-raw.ts +28 -0
- package/src/docs/components/docs-lit-demo.ts +166 -0
- package/src/docs/components/docs-source-link.ts +72 -0
- package/src/docs/docs-location.ts +54 -0
- package/src/docs/docs.ts +12 -0
- package/src/docs/example/decorators-demo-bind-demos.ts +41 -46
- package/src/docs/example/decorators-demo-geo.ts +16 -11
- package/src/docs/example/decorators-demo-init.ts +2 -228
- package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +54 -14
- package/src/docs/example/decorators-demo.ts +71 -70
- package/src/docs/example/docs-api-config-demos.ts +234 -0
- package/src/docs/example/docs-joke-demos.ts +297 -0
- package/src/docs/example/docs-list-demos.ts +179 -0
- package/src/docs/example/docs-provider-keys.ts +315 -0
- package/src/docs/example/docs-queue-demos.ts +114 -0
- package/src/docs/example/docs-router-demos.ts +89 -0
- package/src/docs/example/docs-submit-demos.ts +455 -0
- package/src/docs/example/docs-toggle-demos.ts +73 -0
- package/src/docs/example/docs-user-two-scopes.ts +37 -0
- package/src/docs/example/docs-users-list.ts +71 -0
- package/src/docs/example/users.ts +41 -24
- package/src/docs/mock-api/api-config-mock.ts +152 -0
- package/src/docs/mock-api/fixtures.ts +377 -0
- package/src/docs/mock-api/register.ts +25 -0
- package/src/docs/mock-api/router.ts +234 -0
- package/src/docs/mock-api/service-worker.ts +23 -0
- package/src/docs/mock-api/urls.ts +11 -0
- package/src/docs/navigation/navigation.ts +39 -7
- package/src/docs/search/docs-search.json +4021 -936
- package/src/docs/search/markdown-renderer.ts +7 -3
- package/src/docs/search/page.ts +11 -14
- package/src/docs/search/sonic-code-markdown.spec.ts +29 -0
- package/src/docs/search/sonic-code-markdown.ts +28 -0
- package/src/docs.ts +4 -0
- package/src/tsconfig.json +87 -0
- package/src/tsconfig.tsbuildinfo +1 -1
- package/vite.config.mts +8 -0
- package/docs/assets/index-CaysOMFz.js +0 -5046
- package/docs/assets/index-D8mGoXzF.css +0 -1
- package/docs/src/docs/_misc/templates-demo.md +0 -19
- package/src/docs/_misc/templates-demo.md +0 -19
|
@@ -12,6 +12,7 @@ By default the method is called on **every** assignment, even when the value is
|
|
|
12
12
|
<template>
|
|
13
13
|
import { handle, Skip } from "@supersoniks/concorde/decorators";
|
|
14
14
|
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
15
|
+
import { get, set } from "@supersoniks/concorde/utils";
|
|
15
16
|
</template>
|
|
16
17
|
</sonic-code>
|
|
17
18
|
|
|
@@ -21,24 +22,23 @@ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
|
21
22
|
<template>
|
|
22
23
|
type DemoCounterData = { count: number };
|
|
23
24
|
const demoDataKey = new DataProviderKey<DemoCounterData>("demoData");
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
@customElement("demo-handle")
|
|
26
27
|
export class DemoHandle extends LitElement {
|
|
27
28
|
@state() doubled = 0;
|
|
28
29
|
@state() lastUpdate = "";
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
@handle(demoDataKey.count)
|
|
31
32
|
onCountChange(count: number) {
|
|
32
33
|
this.doubled = count * 2;
|
|
33
34
|
this.lastUpdate = new Date().toLocaleTimeString();
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
incrementCount() {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
publisher.set({ ...data, count: data.count + 1 });
|
|
38
|
+
const data = get(demoDataKey);
|
|
39
|
+
set(demoDataKey, { ...data, count: data.count + 1 });
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
render() {
|
|
43
43
|
return html`
|
|
44
44
|
<p>Doubled count: ${this.doubled}</p>
|
|
@@ -52,6 +52,7 @@ export class DemoHandle extends LitElement {
|
|
|
52
52
|
|
|
53
53
|
<sonic-code>
|
|
54
54
|
<template>
|
|
55
|
+
<docs-demo-sources for="demo-handle"></docs-demo-sources>
|
|
55
56
|
<demo-handle></demo-handle>
|
|
56
57
|
</template>
|
|
57
58
|
</sonic-code>
|
|
@@ -63,21 +64,21 @@ Placeholders in `DataProviderKey` resolve from the host component’s properties
|
|
|
63
64
|
<sonic-code language="typescript">
|
|
64
65
|
<template>
|
|
65
66
|
type User = { firstName: string; lastName: string; email: string };
|
|
66
|
-
|
|
67
|
+
|
|
67
68
|
@customElement("demo-handle-dynamic")
|
|
68
69
|
export class DemoHandleDynamic extends LitElement {
|
|
69
70
|
@property({ type: Number })
|
|
70
71
|
userIndex = 0;
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
@state() displayName = "";
|
|
73
74
|
@state() lastUpdate = "";
|
|
74
|
-
|
|
75
|
+
|
|
75
76
|
@handle(new DataProviderKey<User, { userIndex: number }>("demoUsers.${userIndex}"))
|
|
76
77
|
onUserAssigned(user: User) {
|
|
77
78
|
this.displayName = `${user.firstName} ${user.lastName}`;
|
|
78
79
|
this.lastUpdate = new Date().toLocaleTimeString();
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
render() {
|
|
82
83
|
return html`...`;
|
|
83
84
|
}
|
|
@@ -87,6 +88,7 @@ export class DemoHandleDynamic extends LitElement {
|
|
|
87
88
|
|
|
88
89
|
<sonic-code>
|
|
89
90
|
<template>
|
|
91
|
+
<docs-demo-sources for="demo-handle-dynamic"></docs-demo-sources>
|
|
90
92
|
<demo-handle-dynamic></demo-handle-dynamic>
|
|
91
93
|
</template>
|
|
92
94
|
</sonic-code>
|
|
@@ -100,10 +102,10 @@ export class DemoHandleDynamic extends LitElement {
|
|
|
100
102
|
type QueueConfig = { onInactivity: { stillHere: { show: boolean } } };
|
|
101
103
|
const config = new DataProviderKey<QueueConfig>("sessionQueueConfig");
|
|
102
104
|
const idle = new DataProviderKey<{ isIdle: boolean }>("idleStatus");
|
|
103
|
-
|
|
105
|
+
|
|
104
106
|
@customElement("demo-handle-multi")
|
|
105
107
|
export class DemoHandleMulti extends LitElement {
|
|
106
|
-
|
|
108
|
+
|
|
107
109
|
// show: boolean, isIdle: boolean — fully typed from the keys
|
|
108
110
|
@handle(config.onInactivity.stillHere.show, idle.isIdle)
|
|
109
111
|
onInactivity(show: boolean, isIdle: boolean) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# @onAssign
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> `@onAssign` takes untyped **string** paths; `@handle` does the same with full typing via `DataProviderKey`, supports up to 3 keys, and exposes explicit options. `@onAssign` stays functional and unchanged during the migration. See the **Migrating to @handle** section below.
|
|
3
|
+
> **New apps:** use [@handle](#docs/_decorators/handle.md/handle) with `DataProviderKey` ([Data flow](#docs/_core-concept/dataFlow.md/dataFlow)). `@onAssign` uses untyped string paths; it remains documented for existing codebases — see **Migrating to @handle** below.
|
|
5
4
|
|
|
6
5
|
The `@onAssign` decorator allows you to execute a method when one or more publishers are updated. The method is called only when all specified publishers have been assigned values.
|
|
7
6
|
|
|
@@ -9,7 +8,7 @@ For a **typed** equivalent (recommended), use [@handle](#docs/_decorators/handle
|
|
|
9
8
|
|
|
10
9
|
## Principle
|
|
11
10
|
|
|
12
|
-
This decorator subscribes to one or more publishers
|
|
11
|
+
This decorator subscribes to one or more publishers by **string path** (legacy). When all specified publishers have been assigned values (via `set`), the decorated method is called with all the values as arguments. Prefer [@handle](#docs/_decorators/handle.md/handle) + `DataProviderKey` and `get` / `set` from [Data flow](#docs/_core-concept/dataFlow.md/dataFlow).
|
|
13
12
|
|
|
14
13
|
This is particularly useful when you need to wait for multiple data sources to be ready before executing logic.
|
|
15
14
|
|
|
@@ -32,11 +31,11 @@ import { onAssign } from "@supersoniks/concorde/decorators";
|
|
|
32
31
|
@customElement("demo-on-assign")
|
|
33
32
|
export class DemoOnAssign extends LitElement {
|
|
34
33
|
static styles = [tailwind];
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
@state() userWithSettings: any = null;
|
|
37
36
|
@state() isReady: boolean = false;
|
|
38
37
|
@state() lastUpdate: string = "";
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
@onAssign("demoUser", "demoUserSettings")
|
|
41
40
|
handleDataReady(user: any, settings: any) {
|
|
42
41
|
this.isReady = Object.keys(user).length > 0 && Object.keys(settings).length > 0;
|
|
@@ -44,12 +43,12 @@ export class DemoOnAssign extends LitElement {
|
|
|
44
43
|
this.lastUpdate = new Date().toLocaleTimeString();
|
|
45
44
|
this.requestUpdate();
|
|
46
45
|
}
|
|
47
|
-
|
|
46
|
+
|
|
48
47
|
render() {
|
|
49
48
|
const { name, email, theme, language } = this.userWithSettings;
|
|
50
49
|
return //...
|
|
51
50
|
}
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
updateData() {
|
|
54
53
|
const user = PublisherManager.get("demoUser");
|
|
55
54
|
const userSettings = PublisherManager.get("demoUserSettings");
|
|
@@ -58,7 +57,7 @@ export class DemoOnAssign extends LitElement {
|
|
|
58
57
|
name: `User n°${userNumber}`,
|
|
59
58
|
email: `user-${userNumber}@example.com`,
|
|
60
59
|
});
|
|
61
|
-
|
|
60
|
+
|
|
62
61
|
userSettings.set({
|
|
63
62
|
theme: ["light", "dark", "auto"][Math.floor(Math.random() * 3)],
|
|
64
63
|
language: ["en", "fr", "es"][Math.floor(Math.random() * 3)],
|
|
@@ -70,6 +69,7 @@ export class DemoOnAssign extends LitElement {
|
|
|
70
69
|
|
|
71
70
|
<sonic-code>
|
|
72
71
|
<template>
|
|
72
|
+
<docs-demo-sources for="demo-on-assign"></docs-demo-sources>
|
|
73
73
|
<demo-on-assign></demo-on-assign>
|
|
74
74
|
</template>
|
|
75
75
|
</sonic-code>
|
|
@@ -83,17 +83,17 @@ export class DemoOnAssign extends LitElement {
|
|
|
83
83
|
export class ProductView extends LitElement {
|
|
84
84
|
product: any = null;
|
|
85
85
|
inventory: any = null;
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
@onAssign("store.product", "store.inventory")
|
|
88
88
|
handleProductData(product: any, inventory: any) {
|
|
89
89
|
this.product = product;
|
|
90
90
|
this.inventory = inventory;
|
|
91
91
|
this.requestUpdate();
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
render() {
|
|
95
95
|
if (!this.product) return html`<div>Loading...</div>`;
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
const stock = this.inventory[this.product.id] || 0;
|
|
98
98
|
return html`
|
|
99
99
|
<div>
|
|
@@ -134,32 +134,32 @@ Each placeholder is replaced at runtime with the current value of the correspond
|
|
|
134
134
|
@customElement("demo-on-assign-dynamic")
|
|
135
135
|
export class DemoOnAssignDynamic extends LitElement {
|
|
136
136
|
static styles = [tailwind];
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
@property({ type: String })
|
|
139
139
|
dataProvider: "demoUsers" | "demoUsersAlt" = "demoUsers";
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
@property({ type: Number })
|
|
142
142
|
userIndex: number = 0;
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
@state() user: any = null;
|
|
145
145
|
@state() userSettings: any = null;
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
@onAssign("${dataProvider}.${userIndex}", "${dataProvider}Settings.${userIndex}")
|
|
148
148
|
handleUserDataReady(user: any, settings: any) {
|
|
149
149
|
this.user = user;
|
|
150
150
|
this.userSettings = settings;
|
|
151
151
|
}
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
updateUserIndex(e: Event) {
|
|
154
154
|
this.userIndex = parseInt((e.target as HTMLInputElement).value);
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
updateDataProvider(e: Event) {
|
|
158
158
|
this.dataProvider = (e.target as HTMLSelectElement).value as
|
|
159
159
|
| "demoUsers"
|
|
160
160
|
| "demoUsersAlt";
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
updateCurrentUserData() {
|
|
164
164
|
const usersPublisher = PublisherManager.get(this.dataProvider);
|
|
165
165
|
const settingsPublisher = PublisherManager.get(
|
|
@@ -173,7 +173,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
173
173
|
settingsPublisher,
|
|
174
174
|
[String(this.userIndex)]
|
|
175
175
|
) as PublisherProxy;
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
if (userPublisher && settingPublisher) {
|
|
178
178
|
// Générer de nouvelles données aléatoires
|
|
179
179
|
const randomNames = [
|
|
@@ -183,7 +183,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
183
183
|
];
|
|
184
184
|
const randomThemes = ["light", "dark", "auto"];
|
|
185
185
|
const randomLanguages = ["en", "fr", "es"];
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
const randomName =
|
|
188
188
|
randomNames[Math.floor(Math.random() * randomNames.length)];
|
|
189
189
|
const randomEmail = `${randomName.firstName.toLowerCase()}.${randomName.lastName.toLowerCase()}@example.com`;
|
|
@@ -191,7 +191,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
191
191
|
randomThemes[Math.floor(Math.random() * randomThemes.length)];
|
|
192
192
|
const randomLanguage =
|
|
193
193
|
randomLanguages[Math.floor(Math.random() * randomLanguages.length)];
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
// Mettre à jour l'utilisateur directement
|
|
196
196
|
const currentUser = userPublisher.get() || {};
|
|
197
197
|
userPublisher.set({
|
|
@@ -200,7 +200,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
200
200
|
lastName: randomName.lastName,
|
|
201
201
|
email: randomEmail,
|
|
202
202
|
});
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
// Mettre à jour les settings directement
|
|
205
205
|
settingPublisher.set({
|
|
206
206
|
theme: randomTheme,
|
|
@@ -208,15 +208,15 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
-
|
|
211
|
+
|
|
212
212
|
render() {
|
|
213
213
|
return html`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
214
|
+
<div class="flex flex-col gap-2">
|
|
215
|
+
<sonic-select label="Users set" @change=${this.updateDataProvider}>
|
|
216
|
+
<option value="demoUsers">First set of users</option>
|
|
217
|
+
<option value="demoUsersAlt">Second set of users</option>
|
|
218
|
+
</sonic-select>
|
|
219
|
+
<sonic-input
|
|
220
220
|
type="number"
|
|
221
221
|
.value=${this.userIndex}
|
|
222
222
|
@input=${this.updateUserIndex}
|
|
@@ -224,26 +224,25 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
224
224
|
max="9"
|
|
225
225
|
label="Index"
|
|
226
226
|
class="block"
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<sonic-icon name="user" library="heroicons"></sonic-icon>
|
|
227
|
+
></sonic-input>
|
|
228
|
+
<sonic-button @click=${this.updateCurrentUserData}>
|
|
229
|
+
Update current user data
|
|
230
|
+
</sonic-button>
|
|
231
|
+
<div class="flex flex-col gap-2 border p-2">
|
|
232
|
+
<div>
|
|
233
|
+
<sonic-icon name="user" library="heroicons"></sonic-icon>
|
|
235
234
|
${this.user?.firstName} ${this.user?.lastName}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
</div>
|
|
236
|
+
<div>
|
|
237
|
+
<sonic-icon name="envelope" library="heroicons"></sonic-icon>
|
|
239
238
|
${this.user?.email}
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
</div>
|
|
240
|
+
<div>
|
|
242
241
|
Theme: ${this.userSettings?.theme} | Language:
|
|
243
242
|
${this.userSettings?.language}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
247
246
|
`;
|
|
248
247
|
}
|
|
249
248
|
}
|
|
@@ -252,6 +251,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
252
251
|
|
|
253
252
|
<sonic-code>
|
|
254
253
|
<template>
|
|
254
|
+
<docs-demo-sources for="demo-on-assign-dynamic"></docs-demo-sources>
|
|
255
255
|
<demo-on-assign-dynamic></demo-on-assign-dynamic>
|
|
256
256
|
</template>
|
|
257
257
|
</sonic-code>
|
|
@@ -290,13 +290,13 @@ import { html, LitElement } from "lit";
|
|
|
290
290
|
import { customElement } from "lit/decorators.js";
|
|
291
291
|
import { onAssign } from "@supersoniks/concorde/decorators";
|
|
292
292
|
import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
@customElement("order-summary")
|
|
295
295
|
export class OrderSummary extends LitElement {
|
|
296
296
|
order: any = null;
|
|
297
297
|
customer: any = null;
|
|
298
298
|
shipping: any = null;
|
|
299
|
-
|
|
299
|
+
|
|
300
300
|
@onAssign("orderData", "customerData", "shippingData")
|
|
301
301
|
handleOrderReady(order: any, customer: any, shipping: any) {
|
|
302
302
|
this.order = order;
|
|
@@ -304,12 +304,12 @@ export class OrderSummary extends LitElement {
|
|
|
304
304
|
this.shipping = shipping;
|
|
305
305
|
this.requestUpdate();
|
|
306
306
|
}
|
|
307
|
-
|
|
307
|
+
|
|
308
308
|
render() {
|
|
309
309
|
if (!this.order || !this.customer || !this.shipping) {
|
|
310
310
|
return html`<div>Loading order details...</div>`;
|
|
311
311
|
}
|
|
312
|
-
|
|
312
|
+
|
|
313
313
|
return html`
|
|
314
314
|
<div class="order-summary">
|
|
315
315
|
<h2>Order #${this.order.id}</h2>
|
|
@@ -349,11 +349,11 @@ shippingPub.set({ address: "123 Main St" });
|
|
|
349
349
|
// Before
|
|
350
350
|
@onAssign("demoUser", "demoUserSettings")
|
|
351
351
|
handleDataReady(user: any, settings: any) { /* ... */ }
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
// After — same "wait for everything" behavior, but typed
|
|
354
354
|
const user = new DataProviderKey<User>("demoUser");
|
|
355
355
|
const settings = new DataProviderKey<Settings>("demoUserSettings");
|
|
356
|
-
|
|
356
|
+
|
|
357
357
|
@handle(user, settings, { waitForAllDefined: true })
|
|
358
358
|
handleDataReady(user: User, settings: Settings) { /* ... */ }
|
|
359
359
|
</template>
|
|
@@ -366,10 +366,10 @@ handleDataReady(user: User, settings: Settings) { /* ... */ }
|
|
|
366
366
|
// Before
|
|
367
367
|
@onAssign("settings.modules.logs_route.enabled")
|
|
368
368
|
onLogRoute(value: boolean) { /* ... */ }
|
|
369
|
-
|
|
369
|
+
|
|
370
370
|
// After
|
|
371
371
|
const settings = new DataProviderKey<AppSettings>("settings");
|
|
372
|
-
|
|
372
|
+
|
|
373
373
|
@handle(settings.modules.logs_route.enabled)
|
|
374
374
|
onLogRoute(value: boolean) { /* ... */ }
|
|
375
375
|
</template>
|
|
@@ -38,7 +38,7 @@ export class DemoPublish extends LitElement {
|
|
|
38
38
|
@input=${(e) => (this.email = (e.target as HTMLInputElement).value)}
|
|
39
39
|
label="Email"
|
|
40
40
|
></sonic-input>
|
|
41
|
-
<p>${sub(
|
|
41
|
+
<p>${sub(publishDemoKey.email)}</p>
|
|
42
42
|
`;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -47,6 +47,7 @@ export class DemoPublish extends LitElement {
|
|
|
47
47
|
|
|
48
48
|
<sonic-code>
|
|
49
49
|
<template>
|
|
50
|
+
<docs-demo-sources for="demo-publish"></docs-demo-sources>
|
|
50
51
|
<demo-publish></demo-publish>
|
|
51
52
|
</template>
|
|
52
53
|
</sonic-code>
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
# @subscribe
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Keeps a **Lit property** in sync with a **read-only** slice of the DataProvider store. You pass a [`DataProviderKey`](#docs/_misc/dataProviderKey.md/dataProviderKey); when that path changes, the property updates and the component re-renders.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Typical setup (same idea as [My first component](#docs/_getting-started/my-first-component.md/my-first-component)):
|
|
6
|
+
|
|
7
|
+
| Piece | Role |
|
|
8
|
+
|-------|------|
|
|
9
|
+
| **Type** `T` | Shape of the object at that path (`DocsUserData`, `{ count: number }`, …) |
|
|
10
|
+
| **Key** | `DataProviderKey<T, U>` — static path (`"cart"`) or dynamic (`"users.${userIndex}"`, `"${dataProvider}"`) |
|
|
11
|
+
| **Scope on the host** | Properties listed in `U` (e.g. `dataProvider`, `userIndex`) — often filled via [`@ancestorAttribute`](#docs/_decorators/ancestor-attribute.md/ancestor-attribute) |
|
|
12
|
+
| **`@subscribe(key)`** | Mirrors the store into `@state()` (or another property); read-only from the component side |
|
|
13
|
+
|
|
14
|
+
For **writing** back to the store from component state, use [@publish](#docs/_decorators/publish.md/publish). In templates, the same paths work with [sub()](#docs/_directives/sub.md/sub).
|
|
6
15
|
|
|
7
16
|
## Import
|
|
8
17
|
|
|
@@ -10,27 +19,79 @@ For bidirectional binding or string paths, use [@bind](#docs/_decorators/bind.md
|
|
|
10
19
|
<template>
|
|
11
20
|
import { subscribe } from "@supersoniks/concorde/decorators";
|
|
12
21
|
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
13
|
-
|
|
22
|
+
|
|
14
23
|
type Data = { count: number };
|
|
15
|
-
const dataKey = new DataProviderKey
|
|
16
|
-
|
|
24
|
+
const dataKey = new DataProviderKey<Data>("data");
|
|
25
|
+
|
|
17
26
|
@subscribe(dataKey.count)
|
|
18
27
|
@state()
|
|
19
28
|
count = 0;
|
|
20
29
|
</template>
|
|
21
30
|
</sonic-code>
|
|
22
31
|
|
|
23
|
-
##
|
|
32
|
+
## Static path
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
The key path is fixed. The property type must match `T` at that segment.
|
|
35
|
+
|
|
36
|
+
<sonic-code language="typescript">
|
|
37
|
+
<template>
|
|
38
|
+
const cartKey = new DataProviderKey<{ items: string[] }>("cart");
|
|
39
|
+
|
|
40
|
+
@subscribe(cartKey)
|
|
41
|
+
@state()
|
|
42
|
+
cart: { items: string[] } | null = null;
|
|
43
|
+
</template>
|
|
44
|
+
</sonic-code>
|
|
45
|
+
|
|
46
|
+
## Dynamic path and scope
|
|
47
|
+
|
|
48
|
+
Placeholders `${prop}` in the key string are resolved from **properties on the same component**. Declare them in the key’s second generic so TypeScript expects them on the host:
|
|
49
|
+
|
|
50
|
+
<sonic-code language="typescript">
|
|
51
|
+
<template>
|
|
52
|
+
type User = { firstName: string; lastName: string; email: string };
|
|
53
|
+
|
|
54
|
+
@subscribe(new DataProviderKey<User, { userIndex: number }>("demoUsers.${userIndex}"))
|
|
55
|
+
@state()
|
|
56
|
+
user: User | null = null;
|
|
57
|
+
|
|
58
|
+
@property({ type: Number }) userIndex = 0;
|
|
59
|
+
</template>
|
|
60
|
+
</sonic-code>
|
|
61
|
+
|
|
62
|
+
When `userIndex` changes, `@subscribe` re-resolves the path and refreshes `user`.
|
|
63
|
+
|
|
64
|
+
## Row / ancestor scope
|
|
65
|
+
|
|
66
|
+
List items (and wrappers like `<div dataProvider="…">`) set which branch the child reads. Pattern from the tutorial:
|
|
67
|
+
|
|
68
|
+
<sonic-code language="typescript">
|
|
69
|
+
<template>
|
|
70
|
+
export const rowKey = new DataProviderKey<
|
|
71
|
+
User,
|
|
72
|
+
{ dataProvider: string | null }
|
|
73
|
+
>("${dataProvider}");
|
|
74
|
+
|
|
75
|
+
@ancestorAttribute("dataProvider")
|
|
76
|
+
dataProvider: string | null = null;
|
|
77
|
+
|
|
78
|
+
@subscribe(rowKey)
|
|
79
|
+
@state()
|
|
80
|
+
user: User | null = null;
|
|
81
|
+
</template>
|
|
82
|
+
</sonic-code>
|
|
27
83
|
|
|
28
84
|
## Demo
|
|
29
85
|
|
|
30
86
|
<sonic-code>
|
|
31
87
|
<template>
|
|
88
|
+
<docs-demo-sources for="demo-subscribe-dynamic"></docs-demo-sources>
|
|
32
89
|
<demo-subscribe-dynamic></demo-subscribe-dynamic>
|
|
33
90
|
</template>
|
|
34
91
|
</sonic-code>
|
|
35
92
|
|
|
36
|
-
See also
|
|
93
|
+
## See also
|
|
94
|
+
|
|
95
|
+
- [Data flow](#docs/_core-concept/dataFlow.md/dataFlow) — overview
|
|
96
|
+
- [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) — paths and host generics
|
|
97
|
+
- [sub()](#docs/_directives/sub.md/sub) — same paths inside `html` templates
|
|
@@ -30,7 +30,7 @@ The parent is registered via `customElements.define()` (vanilla JS) rather than
|
|
|
30
30
|
<template>
|
|
31
31
|
import { html, LitElement } from "lit";
|
|
32
32
|
import { customElement, state } from "lit/decorators.js";
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
// Parent: registered later via customElements.define(), not @customElement
|
|
35
35
|
@dispatchConnectedEvent()
|
|
36
36
|
export class DemoWaitAncestorContainer extends LitElement {
|
|
@@ -38,21 +38,21 @@ export class DemoWaitAncestorContainer extends LitElement {
|
|
|
38
38
|
return html`<slot></slot>`;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// Child: waits for parent before initializing
|
|
43
43
|
@customElement("demo-wait-ancestor-value")
|
|
44
44
|
@awaitConnectedAncestors("demo-wait-ancestor-container[dataProvider]")
|
|
45
45
|
export class DemoWaitAncestorValue extends LitElement {
|
|
46
46
|
@ancestorAttribute("dataProvider")
|
|
47
47
|
dataProvider: string | null = null;
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
@state() initializedAt: string = "";
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
connectedCallback() {
|
|
52
52
|
super.connectedCallback();
|
|
53
53
|
this.initializedAt = new Date().toISOString();
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
render() {
|
|
57
57
|
return html`
|
|
58
58
|
<p>DataProvider from ancestor: <strong>${this.dataProvider || "—"}</strong></p>
|
|
@@ -60,7 +60,7 @@ export class DemoWaitAncestorValue extends LitElement {
|
|
|
60
60
|
`;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
// Demo section: register parent via customElements.define() when user clicks
|
|
65
65
|
@customElement("demo-wait-ancestors-section")
|
|
66
66
|
export class DemoWaitAncestorsSection extends LitElement {
|
|
@@ -83,7 +83,8 @@ export class DemoWaitAncestorsSection extends LitElement {
|
|
|
83
83
|
|
|
84
84
|
<sonic-code>
|
|
85
85
|
<template>
|
|
86
|
-
<demo-wait-ancestors-section></demo-
|
|
86
|
+
<docs-demo-sources for="demo-wait-ancestors-section"></docs-demo-sources>
|
|
87
|
+
<demo-wait-ancestors-section></demo-wait-ancestors-section>
|
|
87
88
|
</template>
|
|
88
89
|
</sonic-code>
|
|
89
90
|
|
|
@@ -105,7 +106,8 @@ When the parent is defined at load and already in the DOM, the child initializes
|
|
|
105
106
|
|
|
106
107
|
<sonic-code>
|
|
107
108
|
<template>
|
|
108
|
-
<demo-wait-ancestors-static-section></demo-
|
|
109
|
+
<docs-demo-sources for="demo-wait-ancestors-static-section"></docs-demo-sources>
|
|
110
|
+
<demo-wait-ancestors-static-section></demo-wait-ancestors-static-section>
|
|
109
111
|
</template>
|
|
110
112
|
</sonic-code>
|
|
111
113
|
|
|
@@ -113,7 +115,8 @@ When the parent is defined at load and already in the DOM, the child initializes
|
|
|
113
115
|
|
|
114
116
|
<sonic-code>
|
|
115
117
|
<template>
|
|
116
|
-
<demo-wait-ancestors-ready-section></demo-
|
|
118
|
+
<docs-demo-sources for="demo-wait-ancestors-ready-section"></docs-demo-sources>
|
|
119
|
+
<demo-wait-ancestors-ready-section></demo-wait-ancestors-ready-section>
|
|
117
120
|
</template>
|
|
118
121
|
</sonic-code>
|
|
119
122
|
|
|
@@ -145,7 +148,7 @@ The `sonic-connected` event bubbles, so you can listen to it from anywhere:
|
|
|
145
148
|
<sonic-code language="typescript">
|
|
146
149
|
<template>
|
|
147
150
|
import { CONNECTED } from "@supersoniks/concorde/decorators";
|
|
148
|
-
|
|
151
|
+
|
|
149
152
|
someConnectable.addEventListener(CONNECTED, (e) => {
|
|
150
153
|
console.log("Component connected:", e.target);
|
|
151
154
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# sub()
|
|
2
|
+
|
|
3
|
+
Read-only Lit directive: displays the current **DataProvider** value and updates whenever it is assigned.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
<sonic-code language="typescript">
|
|
8
|
+
<template>
|
|
9
|
+
import { sub } from "@supersoniks/concorde/directives";
|
|
10
|
+
</template>
|
|
11
|
+
</sonic-code>
|
|
12
|
+
|
|
13
|
+
## String path
|
|
14
|
+
|
|
15
|
+
<sonic-code language="typescript">
|
|
16
|
+
<template>
|
|
17
|
+
render() {
|
|
18
|
+
return html`<p>Count: ${sub("myCounter.count")}</p>`;
|
|
19
|
+
}
|
|
20
|
+
</template>
|
|
21
|
+
</sonic-code>
|
|
22
|
+
|
|
23
|
+
## DataProviderKey (static)
|
|
24
|
+
|
|
25
|
+
<sonic-code language="typescript">
|
|
26
|
+
<template>
|
|
27
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
28
|
+
import { sub } from "@supersoniks/concorde/directives";
|
|
29
|
+
|
|
30
|
+
const counterKey = new DataProviderKey<{ count: number }>("myCounter");
|
|
31
|
+
|
|
32
|
+
render() {
|
|
33
|
+
return html`<p>${sub(counterKey.count)}</p>`;
|
|
34
|
+
}
|
|
35
|
+
</template>
|
|
36
|
+
</sonic-code>
|
|
37
|
+
|
|
38
|
+
## Dynamic DataProviderKey (`${prop}`)
|
|
39
|
+
|
|
40
|
+
Like `@subscribe`: the path is resolved on the template **host component**; the directive re-subscribes when observed props change.
|
|
41
|
+
|
|
42
|
+
<sonic-code language="typescript">
|
|
43
|
+
<template>
|
|
44
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
45
|
+
import { sub } from "@supersoniks/concorde/directives";
|
|
46
|
+
|
|
47
|
+
type User = { firstName: string; lastName: string; email: string };
|
|
48
|
+
const userKey = new DataProviderKey<User, { userIndex: number }>(
|
|
49
|
+
"demoUsers.${userIndex}",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
@customElement("demo-sub-dynamic")
|
|
53
|
+
export class DemoSubDynamic extends LitElement {
|
|
54
|
+
@property({ type: Number }) userIndex = 0;
|
|
55
|
+
|
|
56
|
+
render() {
|
|
57
|
+
return html`
|
|
58
|
+
<p>${sub(userKey.email)}</p>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</template>
|
|
63
|
+
</sonic-code>
|
|
64
|
+
|
|
65
|
+
## Demo
|
|
66
|
+
|
|
67
|
+
<sonic-code toggleCode>
|
|
68
|
+
<template>
|
|
69
|
+
<docs-demo-sources for="demo-sub-template"></docs-demo-sources>
|
|
70
|
+
<demo-sub-template></demo-sub-template>
|
|
71
|
+
</template>
|
|
72
|
+
</sonic-code>
|
|
73
|
+
|
|
74
|
+
## Concatenation (forms)
|
|
75
|
+
|
|
76
|
+
<sonic-code language="typescript">
|
|
77
|
+
<template>
|
|
78
|
+
html`<span>${sub(this.formDataProvider + ".email")}</span>`
|
|
79
|
+
</template>
|
|
80
|
+
</sonic-code>
|
|
81
|
+
|
|
82
|
+
## sub() vs get() / set() / dp()
|
|
83
|
+
|
|
84
|
+
| API | Context | Dynamic `${…}` |
|
|
85
|
+
|-----|----------|------------------|
|
|
86
|
+
| `sub()` | Lit template, reactive | Yes |
|
|
87
|
+
| `get` / `set` / `dp` | Imperative code | No |
|
|
88
|
+
|
|
89
|
+
Do not replace `sub(path)` with `get(path)` in a template: `get` returns a one-time snapshot.
|
|
90
|
+
|
|
91
|
+
See [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey), [@subscribe](#docs/_decorators/subscribe.md/subscribe), and the `concorde-get-set-dp` migration skill.
|