@supersoniks/concorde 4.6.0 → 4.7.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/.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 +220 -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 +127 -127
- package/concorde-core.es.js +1435 -1364
- package/dist/altcha-widget.js +2662 -0
- package/dist/concorde-core.bundle.js +127 -127
- package/dist/concorde-core.es.js +1435 -1364
- 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/directives/DataProvider.sub.spec.ts +96 -0
- package/src/core/directives/DataProvider.ts +109 -40
- 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
|
@@ -7,7 +7,7 @@ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) as the first argument.
|
|
|
7
7
|
## Configuration
|
|
8
8
|
|
|
9
9
|
- **Default:** `HTML.getApiConfiguration(host)` (ancestor `serviceURL`, etc.).
|
|
10
|
-
- **Second argument:** `DataProviderKey<APIConfiguration>` — config is read from the publisher at the resolved path; internal mutations trigger another GET.
|
|
10
|
+
- **Second argument:** `DataProviderKey<APIConfiguration>` — config is read from the publisher at the resolved path; internal mutations trigger another GET. See [API configuration](#docs/_misc/api-configuration.md/api-configuration) for mock demos.
|
|
11
11
|
|
|
12
12
|
## When the GET runs again
|
|
13
13
|
|
|
@@ -26,7 +26,7 @@ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
|
26
26
|
|
|
27
27
|
## Minimal example
|
|
28
28
|
|
|
29
|
-
Same demo service as [`sonic-queue`](../../core/components/functional/queue/queue.demo.ts) (
|
|
29
|
+
Same demo service as [`sonic-queue`](../../core/components/functional/queue/queue.demo.ts) (`/docs-mock-api/geo/`). Publisher setup lives in `decorators-demo-geo.ts` and `decorators-demo-subscribe-publish-get-demos.ts`.
|
|
30
30
|
|
|
31
31
|
<sonic-code language="typescript">
|
|
32
32
|
<template>
|
|
@@ -40,6 +40,7 @@ payload: ApiGetResult<User> | null = null;
|
|
|
40
40
|
|
|
41
41
|
<sonic-code>
|
|
42
42
|
<template>
|
|
43
|
+
<docs-demo-sources for="demo-api-get"></docs-demo-sources>
|
|
43
44
|
<demo-api-get></demo-api-get>
|
|
44
45
|
</template>
|
|
45
46
|
</sonic-code>
|
|
@@ -48,15 +49,17 @@ Dynamic config and endpoint path (`demo-api-get-configuration-key` in doc source
|
|
|
48
49
|
|
|
49
50
|
<sonic-code>
|
|
50
51
|
<template>
|
|
52
|
+
<docs-demo-sources for="demo-api-get-configuration-key"></docs-demo-sources>
|
|
51
53
|
<demo-api-get-configuration-key></demo-api-get-configuration-key>
|
|
52
54
|
</template>
|
|
53
55
|
</sonic-code>
|
|
54
56
|
|
|
55
|
-
Scoped `@get` with `@publish` / `@subscribe` on the payload (see [@publish](#docs/_decorators/publish.md/publish) and [@subscribe](#docs/_decorators/subscribe.md/subscribe)) — wrap under an ancestor with `serviceURL="
|
|
57
|
+
Scoped `@get` with `@publish` / `@subscribe` on the payload (see [@publish](#docs/_decorators/publish.md/publish) and [@subscribe](#docs/_decorators/subscribe.md/subscribe)) — wrap under an ancestor with `serviceURL="/docs-mock-api/geo/"`:
|
|
56
58
|
|
|
57
59
|
<sonic-code>
|
|
58
60
|
<template>
|
|
59
|
-
<div serviceURL="
|
|
61
|
+
<div serviceURL="/docs-mock-api/geo/">
|
|
62
|
+
<docs-demo-sources for="demo-api-get-publish-subscribe"></docs-demo-sources>
|
|
60
63
|
<demo-api-get-publish-subscribe></demo-api-get-publish-subscribe>
|
|
61
64
|
</div>
|
|
62
65
|
</template>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @handle
|
|
2
|
+
|
|
3
|
+
Typed callback on one or more `DataProviderKey<T>` paths: invokes the decorated **method** when a publisher assigns a value (calculations, side effects, updating other `@state` properties, etc.).
|
|
4
|
+
|
|
5
|
+
Unlike [@subscribe](#docs/_decorators/subscribe.md/subscribe), nothing is bound to the decorated member — only your method runs. `@handle` is typed and accepts up to **3 keys** plus an optional trailing `HandleOptions` object. It supersedes the string-based [@onAssign](#docs/_decorators/on-assign.md/on-assign).
|
|
6
|
+
|
|
7
|
+
By default the method is called on **every** assignment, even when the value is `null` / `undefined`. Use the options below to restrict that.
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
<sonic-code language="typescript">
|
|
12
|
+
<template>
|
|
13
|
+
import { handle, Skip } from "@supersoniks/concorde/decorators";
|
|
14
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
15
|
+
import { get, set } from "@supersoniks/concorde/utils";
|
|
16
|
+
</template>
|
|
17
|
+
</sonic-code>
|
|
18
|
+
|
|
19
|
+
## Basic example
|
|
20
|
+
|
|
21
|
+
<sonic-code language="typescript">
|
|
22
|
+
<template>
|
|
23
|
+
type DemoCounterData = { count: number };
|
|
24
|
+
const demoDataKey = new DataProviderKey<DemoCounterData>("demoData");
|
|
25
|
+
|
|
26
|
+
@customElement("demo-handle")
|
|
27
|
+
export class DemoHandle extends LitElement {
|
|
28
|
+
@state() doubled = 0;
|
|
29
|
+
@state() lastUpdate = "";
|
|
30
|
+
|
|
31
|
+
@handle(demoDataKey.count)
|
|
32
|
+
onCountChange(count: number) {
|
|
33
|
+
this.doubled = count * 2;
|
|
34
|
+
this.lastUpdate = new Date().toLocaleTimeString();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
incrementCount() {
|
|
38
|
+
const data = get(demoDataKey);
|
|
39
|
+
set(demoDataKey, { ...data, count: data.count + 1 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
render() {
|
|
43
|
+
return html`
|
|
44
|
+
<p>Doubled count: ${this.doubled}</p>
|
|
45
|
+
<p><small>Last update: ${this.lastUpdate}</small></p>
|
|
46
|
+
<sonic-button @click=${this.incrementCount}>Increment</sonic-button>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</template>
|
|
51
|
+
</sonic-code>
|
|
52
|
+
|
|
53
|
+
<sonic-code>
|
|
54
|
+
<template>
|
|
55
|
+
<docs-demo-sources for="demo-handle"></docs-demo-sources>
|
|
56
|
+
<demo-handle></demo-handle>
|
|
57
|
+
</template>
|
|
58
|
+
</sonic-code>
|
|
59
|
+
|
|
60
|
+
## Dynamic path
|
|
61
|
+
|
|
62
|
+
Placeholders in `DataProviderKey` resolve from the host component’s properties (same rules as `@bind` / `@subscribe`).
|
|
63
|
+
|
|
64
|
+
<sonic-code language="typescript">
|
|
65
|
+
<template>
|
|
66
|
+
type User = { firstName: string; lastName: string; email: string };
|
|
67
|
+
|
|
68
|
+
@customElement("demo-handle-dynamic")
|
|
69
|
+
export class DemoHandleDynamic extends LitElement {
|
|
70
|
+
@property({ type: Number })
|
|
71
|
+
userIndex = 0;
|
|
72
|
+
|
|
73
|
+
@state() displayName = "";
|
|
74
|
+
@state() lastUpdate = "";
|
|
75
|
+
|
|
76
|
+
@handle(new DataProviderKey<User, { userIndex: number }>("demoUsers.${userIndex}"))
|
|
77
|
+
onUserAssigned(user: User) {
|
|
78
|
+
this.displayName = `${user.firstName} ${user.lastName}`;
|
|
79
|
+
this.lastUpdate = new Date().toLocaleTimeString();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
render() {
|
|
83
|
+
return html`...`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</template>
|
|
87
|
+
</sonic-code>
|
|
88
|
+
|
|
89
|
+
<sonic-code>
|
|
90
|
+
<template>
|
|
91
|
+
<docs-demo-sources for="demo-handle-dynamic"></docs-demo-sources>
|
|
92
|
+
<demo-handle-dynamic></demo-handle-dynamic>
|
|
93
|
+
</template>
|
|
94
|
+
</sonic-code>
|
|
95
|
+
|
|
96
|
+
## Multiple paths
|
|
97
|
+
|
|
98
|
+
`@handle` accepts up to **3 keys**; the method receives one strongly-typed argument per key, in order. Each assignment triggers the method, so make your method safe against partial values (or use `waitForAllDefined`, see below).
|
|
99
|
+
|
|
100
|
+
<sonic-code language="typescript">
|
|
101
|
+
<template>
|
|
102
|
+
type QueueConfig = { onInactivity: { stillHere: { show: boolean } } };
|
|
103
|
+
const config = new DataProviderKey<QueueConfig>("sessionQueueConfig");
|
|
104
|
+
const idle = new DataProviderKey<{ isIdle: boolean }>("idleStatus");
|
|
105
|
+
|
|
106
|
+
@customElement("demo-handle-multi")
|
|
107
|
+
export class DemoHandleMulti extends LitElement {
|
|
108
|
+
|
|
109
|
+
// show: boolean, isIdle: boolean — fully typed from the keys
|
|
110
|
+
@handle(config.onInactivity.stillHere.show, idle.isIdle)
|
|
111
|
+
onInactivity(show: boolean, isIdle: boolean) {
|
|
112
|
+
if (show === true && isIdle === true) this.openModal();
|
|
113
|
+
else this.closeModal();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
</template>
|
|
117
|
+
</sonic-code>
|
|
118
|
+
|
|
119
|
+
## Options (`HandleOptions`)
|
|
120
|
+
|
|
121
|
+
Pass an options object as the **last** argument. The names map to real situations seen in the apps.
|
|
122
|
+
|
|
123
|
+
### `waitForAllDefined`
|
|
124
|
+
|
|
125
|
+
Only call the method once **all** watched keys are defined (non `null` / `undefined`). This reproduces the historical `@onAssign` semantics — use it when the logic only makes sense with every source ready (e.g. building a date from `date` + `timeZone` + `direction`).
|
|
126
|
+
|
|
127
|
+
<sonic-code language="typescript">
|
|
128
|
+
<template>
|
|
129
|
+
@handle(trip.departureDate, trip.event.timeZone, form.direction, {
|
|
130
|
+
waitForAllDefined: true,
|
|
131
|
+
})
|
|
132
|
+
updateDepartureDate(date: number, timeZone: string, direction: string) {
|
|
133
|
+
// called only when the three values are all available
|
|
134
|
+
this.formDate = formatDate(date, timeZone, direction);
|
|
135
|
+
}
|
|
136
|
+
</template>
|
|
137
|
+
</sonic-code>
|
|
138
|
+
|
|
139
|
+
### `skip`
|
|
140
|
+
|
|
141
|
+
Do **not** call the method when a received value belongs to one of the listed **categories** (the `Skip` enum). Each entry is a named category — not a value — so there is no value/pattern ambiguity (e.g. `{}` is `Skip.EmptyObject`, an explicit "empty object" category, never a value comparison).
|
|
142
|
+
|
|
143
|
+
| Category | Matches |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `Skip.Nullish` | `null` or `undefined` (a publisher always emits `null`, never `undefined`) |
|
|
146
|
+
| `Skip.EmptyString` | `""` |
|
|
147
|
+
| `Skip.EmptyObject` | object with no keys (`{}`), arrays excluded |
|
|
148
|
+
| `Skip.EmptyArray` | empty array (`[]`) |
|
|
149
|
+
|
|
150
|
+
Useful when a not-yet-initialized publisher emits `{}` as a "loading" state. For a **specific value** (e.g. a particular string), guard inside the method instead.
|
|
151
|
+
|
|
152
|
+
<sonic-code language="typescript">
|
|
153
|
+
<template>
|
|
154
|
+
@handle(user.profile, { skip: [Skip.Nullish, Skip.EmptyObject] })
|
|
155
|
+
onProfile(profile: Profile) {
|
|
156
|
+
// not called while the publisher still holds {} (not loaded yet)
|
|
157
|
+
this.displayName = profile.firstName;
|
|
158
|
+
}
|
|
159
|
+
</template>
|
|
160
|
+
</sonic-code>
|
|
161
|
+
|
|
162
|
+
> Options can be combined, e.g. `@handle(a, b, { waitForAllDefined: true, skip: [Skip.Nullish] })`. For any **arbitrary** validation on a specific value, just guard inside the method (`if (!isValid(v)) return;`) — that is exactly what an `accept`-style predicate would do, since `@handle` only runs your method.
|
|
163
|
+
|
|
164
|
+
## Highlights
|
|
165
|
+
|
|
166
|
+
- Strict typing: the method receives one argument per key, in order.
|
|
167
|
+
- Up to 3 keys; for 4+ keys (rare), keep [@onAssign](#docs/_decorators/on-assign.md/on-assign) for now.
|
|
168
|
+
- By default the method runs on **every** assignment, even with `null` / `undefined` (unlike `@onAssign`, which waits for all values). Opt back into that behavior with `waitForAllDefined`.
|
|
169
|
+
- `skip` filters out values by **named category** (e.g. `[Skip.Nullish, Skip.EmptyObject]`); for arbitrary checks on a specific value, guard inside the method.
|
|
170
|
+
|
|
171
|
+
See also [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey).
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# @onAssign
|
|
2
2
|
|
|
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.
|
|
4
|
+
|
|
3
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.
|
|
4
6
|
|
|
7
|
+
For a **typed** equivalent (recommended), use [@handle](#docs/_decorators/handle.md/handle).
|
|
8
|
+
|
|
5
9
|
## Principle
|
|
6
10
|
|
|
7
|
-
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).
|
|
8
12
|
|
|
9
13
|
This is particularly useful when you need to wait for multiple data sources to be ready before executing logic.
|
|
10
14
|
|
|
@@ -15,7 +19,6 @@ This is particularly useful when you need to wait for multiple data sources to b
|
|
|
15
19
|
<sonic-code language="typescript">
|
|
16
20
|
<template>
|
|
17
21
|
import { onAssign } from "@supersoniks/concorde/decorators";
|
|
18
|
-
import { DataProviderKey } from "@supersoniks/concorde/decorators";
|
|
19
22
|
</template>
|
|
20
23
|
</sonic-code>
|
|
21
24
|
|
|
@@ -28,11 +31,11 @@ import { DataProviderKey } from "@supersoniks/concorde/decorators";
|
|
|
28
31
|
@customElement("demo-on-assign")
|
|
29
32
|
export class DemoOnAssign extends LitElement {
|
|
30
33
|
static styles = [tailwind];
|
|
31
|
-
|
|
34
|
+
|
|
32
35
|
@state() userWithSettings: any = null;
|
|
33
36
|
@state() isReady: boolean = false;
|
|
34
37
|
@state() lastUpdate: string = "";
|
|
35
|
-
|
|
38
|
+
|
|
36
39
|
@onAssign("demoUser", "demoUserSettings")
|
|
37
40
|
handleDataReady(user: any, settings: any) {
|
|
38
41
|
this.isReady = Object.keys(user).length > 0 && Object.keys(settings).length > 0;
|
|
@@ -40,12 +43,12 @@ export class DemoOnAssign extends LitElement {
|
|
|
40
43
|
this.lastUpdate = new Date().toLocaleTimeString();
|
|
41
44
|
this.requestUpdate();
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
|
|
44
47
|
render() {
|
|
45
48
|
const { name, email, theme, language } = this.userWithSettings;
|
|
46
49
|
return //...
|
|
47
50
|
}
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
updateData() {
|
|
50
53
|
const user = PublisherManager.get("demoUser");
|
|
51
54
|
const userSettings = PublisherManager.get("demoUserSettings");
|
|
@@ -54,7 +57,7 @@ export class DemoOnAssign extends LitElement {
|
|
|
54
57
|
name: `User n°${userNumber}`,
|
|
55
58
|
email: `user-${userNumber}@example.com`,
|
|
56
59
|
});
|
|
57
|
-
|
|
60
|
+
|
|
58
61
|
userSettings.set({
|
|
59
62
|
theme: ["light", "dark", "auto"][Math.floor(Math.random() * 3)],
|
|
60
63
|
language: ["en", "fr", "es"][Math.floor(Math.random() * 3)],
|
|
@@ -66,6 +69,7 @@ export class DemoOnAssign extends LitElement {
|
|
|
66
69
|
|
|
67
70
|
<sonic-code>
|
|
68
71
|
<template>
|
|
72
|
+
<docs-demo-sources for="demo-on-assign"></docs-demo-sources>
|
|
69
73
|
<demo-on-assign></demo-on-assign>
|
|
70
74
|
</template>
|
|
71
75
|
</sonic-code>
|
|
@@ -79,17 +83,17 @@ export class DemoOnAssign extends LitElement {
|
|
|
79
83
|
export class ProductView extends LitElement {
|
|
80
84
|
product: any = null;
|
|
81
85
|
inventory: any = null;
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
@onAssign("store.product", "store.inventory")
|
|
84
88
|
handleProductData(product: any, inventory: any) {
|
|
85
89
|
this.product = product;
|
|
86
90
|
this.inventory = inventory;
|
|
87
91
|
this.requestUpdate();
|
|
88
92
|
}
|
|
89
|
-
|
|
93
|
+
|
|
90
94
|
render() {
|
|
91
95
|
if (!this.product) return html`<div>Loading...</div>`;
|
|
92
|
-
|
|
96
|
+
|
|
93
97
|
const stock = this.inventory[this.product.id] || 0;
|
|
94
98
|
return html`
|
|
95
99
|
<div>
|
|
@@ -103,31 +107,6 @@ export class ProductView extends LitElement {
|
|
|
103
107
|
</template>
|
|
104
108
|
</sonic-code>
|
|
105
109
|
|
|
106
|
-
### Example with `DataProviderKey` (type-safe)
|
|
107
|
-
|
|
108
|
-
<sonic-code language="typescript">
|
|
109
|
-
<template>
|
|
110
|
-
type User = { id: string; name: string };
|
|
111
|
-
type Settings = { theme: "light" | "dark" };
|
|
112
|
-
|
|
113
|
-
const userKey = new DataProviderKey<User>("demoUser");
|
|
114
|
-
const settingsKey = new DataProviderKey<Settings>("demoUserSettings");
|
|
115
|
-
|
|
116
|
-
@customElement("demo-on-assign-typed")
|
|
117
|
-
export class DemoOnAssignTyped extends LitElement {
|
|
118
|
-
@state() user: User | null = null;
|
|
119
|
-
@state() settings: Settings | null = null;
|
|
120
|
-
|
|
121
|
-
@onAssign(userKey, settingsKey)
|
|
122
|
-
handleReady(user: User, settings: Settings) {
|
|
123
|
-
this.user = user;
|
|
124
|
-
this.settings = settings;
|
|
125
|
-
this.requestUpdate();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
</template>
|
|
129
|
-
</sonic-code>
|
|
130
|
-
|
|
131
110
|
## Path syntax
|
|
132
111
|
|
|
133
112
|
The path uses dot notation to navigate through the publisher structure:
|
|
@@ -155,32 +134,32 @@ Each placeholder is replaced at runtime with the current value of the correspond
|
|
|
155
134
|
@customElement("demo-on-assign-dynamic")
|
|
156
135
|
export class DemoOnAssignDynamic extends LitElement {
|
|
157
136
|
static styles = [tailwind];
|
|
158
|
-
|
|
137
|
+
|
|
159
138
|
@property({ type: String })
|
|
160
139
|
dataProvider: "demoUsers" | "demoUsersAlt" = "demoUsers";
|
|
161
|
-
|
|
140
|
+
|
|
162
141
|
@property({ type: Number })
|
|
163
142
|
userIndex: number = 0;
|
|
164
|
-
|
|
143
|
+
|
|
165
144
|
@state() user: any = null;
|
|
166
145
|
@state() userSettings: any = null;
|
|
167
|
-
|
|
146
|
+
|
|
168
147
|
@onAssign("${dataProvider}.${userIndex}", "${dataProvider}Settings.${userIndex}")
|
|
169
148
|
handleUserDataReady(user: any, settings: any) {
|
|
170
149
|
this.user = user;
|
|
171
150
|
this.userSettings = settings;
|
|
172
151
|
}
|
|
173
|
-
|
|
152
|
+
|
|
174
153
|
updateUserIndex(e: Event) {
|
|
175
154
|
this.userIndex = parseInt((e.target as HTMLInputElement).value);
|
|
176
155
|
}
|
|
177
|
-
|
|
156
|
+
|
|
178
157
|
updateDataProvider(e: Event) {
|
|
179
158
|
this.dataProvider = (e.target as HTMLSelectElement).value as
|
|
180
159
|
| "demoUsers"
|
|
181
160
|
| "demoUsersAlt";
|
|
182
161
|
}
|
|
183
|
-
|
|
162
|
+
|
|
184
163
|
updateCurrentUserData() {
|
|
185
164
|
const usersPublisher = PublisherManager.get(this.dataProvider);
|
|
186
165
|
const settingsPublisher = PublisherManager.get(
|
|
@@ -194,7 +173,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
194
173
|
settingsPublisher,
|
|
195
174
|
[String(this.userIndex)]
|
|
196
175
|
) as PublisherProxy;
|
|
197
|
-
|
|
176
|
+
|
|
198
177
|
if (userPublisher && settingPublisher) {
|
|
199
178
|
// Générer de nouvelles données aléatoires
|
|
200
179
|
const randomNames = [
|
|
@@ -204,7 +183,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
204
183
|
];
|
|
205
184
|
const randomThemes = ["light", "dark", "auto"];
|
|
206
185
|
const randomLanguages = ["en", "fr", "es"];
|
|
207
|
-
|
|
186
|
+
|
|
208
187
|
const randomName =
|
|
209
188
|
randomNames[Math.floor(Math.random() * randomNames.length)];
|
|
210
189
|
const randomEmail = `${randomName.firstName.toLowerCase()}.${randomName.lastName.toLowerCase()}@example.com`;
|
|
@@ -212,7 +191,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
212
191
|
randomThemes[Math.floor(Math.random() * randomThemes.length)];
|
|
213
192
|
const randomLanguage =
|
|
214
193
|
randomLanguages[Math.floor(Math.random() * randomLanguages.length)];
|
|
215
|
-
|
|
194
|
+
|
|
216
195
|
// Mettre à jour l'utilisateur directement
|
|
217
196
|
const currentUser = userPublisher.get() || {};
|
|
218
197
|
userPublisher.set({
|
|
@@ -221,7 +200,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
221
200
|
lastName: randomName.lastName,
|
|
222
201
|
email: randomEmail,
|
|
223
202
|
});
|
|
224
|
-
|
|
203
|
+
|
|
225
204
|
// Mettre à jour les settings directement
|
|
226
205
|
settingPublisher.set({
|
|
227
206
|
theme: randomTheme,
|
|
@@ -229,15 +208,15 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
229
208
|
});
|
|
230
209
|
}
|
|
231
210
|
}
|
|
232
|
-
|
|
211
|
+
|
|
233
212
|
render() {
|
|
234
213
|
return html`
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
241
220
|
type="number"
|
|
242
221
|
.value=${this.userIndex}
|
|
243
222
|
@input=${this.updateUserIndex}
|
|
@@ -245,26 +224,25 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
245
224
|
max="9"
|
|
246
225
|
label="Index"
|
|
247
226
|
class="block"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<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>
|
|
256
234
|
${this.user?.firstName} ${this.user?.lastName}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
235
|
+
</div>
|
|
236
|
+
<div>
|
|
237
|
+
<sonic-icon name="envelope" library="heroicons"></sonic-icon>
|
|
260
238
|
${this.user?.email}
|
|
261
|
-
|
|
262
|
-
|
|
239
|
+
</div>
|
|
240
|
+
<div>
|
|
263
241
|
Theme: ${this.userSettings?.theme} | Language:
|
|
264
242
|
${this.userSettings?.language}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
268
246
|
`;
|
|
269
247
|
}
|
|
270
248
|
}
|
|
@@ -273,6 +251,7 @@ export class DemoOnAssignDynamic extends LitElement {
|
|
|
273
251
|
|
|
274
252
|
<sonic-code>
|
|
275
253
|
<template>
|
|
254
|
+
<docs-demo-sources for="demo-on-assign-dynamic"></docs-demo-sources>
|
|
276
255
|
<demo-on-assign-dynamic></demo-on-assign-dynamic>
|
|
277
256
|
</template>
|
|
278
257
|
</sonic-code>
|
|
@@ -311,13 +290,13 @@ import { html, LitElement } from "lit";
|
|
|
311
290
|
import { customElement } from "lit/decorators.js";
|
|
312
291
|
import { onAssign } from "@supersoniks/concorde/decorators";
|
|
313
292
|
import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
|
|
314
|
-
|
|
293
|
+
|
|
315
294
|
@customElement("order-summary")
|
|
316
295
|
export class OrderSummary extends LitElement {
|
|
317
296
|
order: any = null;
|
|
318
297
|
customer: any = null;
|
|
319
298
|
shipping: any = null;
|
|
320
|
-
|
|
299
|
+
|
|
321
300
|
@onAssign("orderData", "customerData", "shippingData")
|
|
322
301
|
handleOrderReady(order: any, customer: any, shipping: any) {
|
|
323
302
|
this.order = order;
|
|
@@ -325,12 +304,12 @@ export class OrderSummary extends LitElement {
|
|
|
325
304
|
this.shipping = shipping;
|
|
326
305
|
this.requestUpdate();
|
|
327
306
|
}
|
|
328
|
-
|
|
307
|
+
|
|
329
308
|
render() {
|
|
330
309
|
if (!this.order || !this.customer || !this.shipping) {
|
|
331
310
|
return html`<div>Loading order details...</div>`;
|
|
332
311
|
}
|
|
333
|
-
|
|
312
|
+
|
|
334
313
|
return html`
|
|
335
314
|
<div class="order-summary">
|
|
336
315
|
<h2>Order #${this.order.id}</h2>
|
|
@@ -353,6 +332,53 @@ shippingPub.set({ address: "123 Main St" });
|
|
|
353
332
|
</template>
|
|
354
333
|
</sonic-code>
|
|
355
334
|
|
|
335
|
+
## Migrating to @handle
|
|
336
|
+
|
|
337
|
+
`@handle` is the typed successor of `@onAssign`. The key behavioral difference: `@onAssign` waits for **all** values to be defined before calling the method, whereas `@handle` calls it on **every** assignment by default. Use the `waitForAllDefined` option to keep the old semantics.
|
|
338
|
+
|
|
339
|
+
### Why migrate
|
|
340
|
+
|
|
341
|
+
- **Typed paths**: keys are `DataProviderKey<T>`, so the method arguments are strongly typed (no more `any`).
|
|
342
|
+
- **Explicit intent**: `waitForAllDefined` and `skip` replace implicit behavior.
|
|
343
|
+
- **Single API**: `@handle` covers the mono- and multi-path cases (up to 3 keys).
|
|
344
|
+
|
|
345
|
+
### Equivalent semantics (`waitForAllDefined`)
|
|
346
|
+
|
|
347
|
+
<sonic-code language="typescript">
|
|
348
|
+
<template>
|
|
349
|
+
// Before
|
|
350
|
+
@onAssign("demoUser", "demoUserSettings")
|
|
351
|
+
handleDataReady(user: any, settings: any) { /* ... */ }
|
|
352
|
+
|
|
353
|
+
// After — same "wait for everything" behavior, but typed
|
|
354
|
+
const user = new DataProviderKey<User>("demoUser");
|
|
355
|
+
const settings = new DataProviderKey<Settings>("demoUserSettings");
|
|
356
|
+
|
|
357
|
+
@handle(user, settings, { waitForAllDefined: true })
|
|
358
|
+
handleDataReady(user: User, settings: Settings) { /* ... */ }
|
|
359
|
+
</template>
|
|
360
|
+
</sonic-code>
|
|
361
|
+
|
|
362
|
+
### Single path
|
|
363
|
+
|
|
364
|
+
<sonic-code language="typescript">
|
|
365
|
+
<template>
|
|
366
|
+
// Before
|
|
367
|
+
@onAssign("settings.modules.logs_route.enabled")
|
|
368
|
+
onLogRoute(value: boolean) { /* ... */ }
|
|
369
|
+
|
|
370
|
+
// After
|
|
371
|
+
const settings = new DataProviderKey<AppSettings>("settings");
|
|
372
|
+
|
|
373
|
+
@handle(settings.modules.logs_route.enabled)
|
|
374
|
+
onLogRoute(value: boolean) { /* ... */ }
|
|
375
|
+
</template>
|
|
376
|
+
</sonic-code>
|
|
377
|
+
|
|
378
|
+
### 4+ paths
|
|
379
|
+
|
|
380
|
+
`@handle` is capped at 3 keys. For the rare case of 4 or more publishers, keep `@onAssign` for now, or split the logic into several `@handle` methods that each store their value and call a shared method (guarding against partial values).
|
|
381
|
+
|
|
356
382
|
## Notes
|
|
357
383
|
|
|
358
384
|
- This decorator works with any component that has `connectedCallback` and `disconnectedCallback` methods (such as `LitElement` or components extending `Subscriber`)
|
|
@@ -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>
|