@supersoniks/concorde 5.0.5 → 5.1.1

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.
Files changed (282) hide show
  1. package/.claude/launch.json +15 -0
  2. package/.claude/vite.http.config.mjs +7 -0
  3. package/.taste-skill/after/desktop-fold.jpg +0 -0
  4. package/.taste-skill/after/desktop-page-01.jpg +0 -0
  5. package/.taste-skill/after/desktop-page-02.jpg +0 -0
  6. package/.taste-skill/after/desktop-page-03.jpg +0 -0
  7. package/.taste-skill/after/mobile-fold.jpg +0 -0
  8. package/.taste-skill/after/mobile-page-01.jpg +0 -0
  9. package/.taste-skill/after/report.json +4103 -0
  10. package/.taste-skill/after/tablet-fold.jpg +0 -0
  11. package/.taste-skill/after/tablet-page-01.jpg +0 -0
  12. package/.taste-skill/after/tablet-page-02.jpg +0 -0
  13. package/.taste-skill/before/desktop-fold.jpg +0 -0
  14. package/.taste-skill/before/desktop-page-01.jpg +0 -0
  15. package/.taste-skill/before/desktop-page-02.jpg +0 -0
  16. package/.taste-skill/before/mobile-fold.jpg +0 -0
  17. package/.taste-skill/before/mobile-page-01.jpg +0 -0
  18. package/.taste-skill/before/report.json +2556 -0
  19. package/.taste-skill/before/tablet-fold.jpg +0 -0
  20. package/.taste-skill/before/tablet-page-01.jpg +0 -0
  21. package/.taste-skill/direction.json +510 -0
  22. package/.taste-skill/notes.md +84 -0
  23. package/.taste-skill/prod/desktop-fold.jpg +0 -0
  24. package/.taste-skill/prod/desktop-page-01.jpg +0 -0
  25. package/.taste-skill/prod/desktop-page-02.jpg +0 -0
  26. package/.taste-skill/prod/desktop-page-03.jpg +0 -0
  27. package/.taste-skill/prod/mobile-fold.jpg +0 -0
  28. package/.taste-skill/prod/mobile-page-01.jpg +0 -0
  29. package/.taste-skill/prod/mobile-page-02.jpg +0 -0
  30. package/.taste-skill/prod/report.json +1973 -0
  31. package/.taste-skill/prod/tablet-fold.jpg +0 -0
  32. package/.taste-skill/prod/tablet-page-01.jpg +0 -0
  33. package/.taste-skill/prod/tablet-page-02.jpg +0 -0
  34. package/.tmp-cem/custom-elements.json +13705 -0
  35. package/AUDIT.md +228 -0
  36. package/PLAN-AMELIORATION.md +220 -0
  37. package/build-infos.json +1 -1
  38. package/concorde-core.bundle.js +196 -196
  39. package/concorde-core.es.js +3854 -3572
  40. package/custom-elements-manifest.config.mjs +14 -0
  41. package/dist/concorde-core.bundle.js +196 -196
  42. package/dist/concorde-core.es.js +3854 -3572
  43. package/dist/img/archive/concorde-001-2cv.webp +0 -0
  44. package/dist/img/archive/concorde-red-arrows-2002-poster.jpg +0 -0
  45. package/dist/img/archive/concorde-red-arrows-2002.webm +0 -0
  46. package/dist/img/archive/credits.json +28 -0
  47. package/docs/assets/index-Bb-_LRyY.js +12280 -0
  48. package/docs/index.html +1 -1
  49. package/index.html +27 -63
  50. package/package.json +44 -3
  51. package/public/img/archive/concorde-001-2cv.webp +0 -0
  52. package/public/img/archive/concorde-red-arrows-2002-poster.jpg +0 -0
  53. package/public/img/archive/concorde-red-arrows-2002.webm +0 -0
  54. package/public/img/archive/credits.json +28 -0
  55. package/scripts/generate-sdui-catalog.mjs +250 -0
  56. package/scripts/generate-sdui-catalog.spec.ts +91 -0
  57. package/scripts/pre-build.mjs +15 -0
  58. package/src/core/components/functional/fetch/fetch.md +2 -2
  59. package/src/core/components/functional/sdui/SDUIDescriptorTransformer.ts +5 -5
  60. package/src/core/components/functional/sdui/__fixtures__/descriptors.ts +251 -0
  61. package/src/core/components/functional/sdui/__snapshots__/sdui-action.spec.ts.snap +73 -0
  62. package/src/core/components/functional/sdui/__snapshots__/sdui-incremental.spec.ts.snap +50 -0
  63. package/src/core/components/functional/sdui/__snapshots__/sdui-partial.spec.ts.snap +50 -0
  64. package/src/core/components/functional/sdui/__snapshots__/sdui.regression.spec.ts.snap +461 -0
  65. package/src/core/components/functional/sdui/component-catalog.gaps.json +511 -0
  66. package/src/core/components/functional/sdui/component-catalog.json +3103 -0
  67. package/src/core/components/functional/sdui/component-catalog.spec.ts +23 -0
  68. package/src/core/components/functional/sdui/sdui-action.spec.ts +99 -0
  69. package/src/core/components/functional/sdui/sdui-dom-snapshot.ts +61 -0
  70. package/src/core/components/functional/sdui/sdui-extension-points.md +94 -0
  71. package/src/core/components/functional/sdui/sdui-incremental.spec.ts +137 -0
  72. package/src/core/components/functional/sdui/sdui-partial.spec.ts +83 -0
  73. package/src/core/components/functional/sdui/sdui-partial.ts +19 -0
  74. package/src/core/components/functional/sdui/sdui-safe.spec.ts +188 -0
  75. package/src/core/components/functional/sdui/sdui-safe.ts +100 -0
  76. package/src/core/components/functional/sdui/sdui.md +122 -0
  77. package/src/core/components/functional/sdui/sdui.regression.spec.ts +64 -0
  78. package/src/core/components/functional/sdui/sdui.ts +442 -34
  79. package/src/core/components/functional/sdui/types.ts +49 -0
  80. package/src/core/components/ui/modal/modal.ts +14 -11
  81. package/src/docs/_getting-started/components.md +5 -0
  82. package/src/docs/_getting-started/theming.md +2 -2
  83. package/src/docs/code.ts +88 -75
  84. package/src/docs/components/docs-demo-sources.ts +16 -8
  85. package/src/docs/components/docs-lit-demo.ts +19 -37
  86. package/src/docs/docs.ts +5 -0
  87. package/src/docs/example/docs-boarding-pass-demo.ts +134 -0
  88. package/src/docs/example/docs-provider-keys.ts +12 -0
  89. package/src/docs/header/header.ts +233 -69
  90. package/src/docs/home/catalog.ts +44 -0
  91. package/src/docs/home/home.ts +513 -0
  92. package/src/docs/home/mach-stage.ts +389 -0
  93. package/src/docs/layout.ts +3 -1
  94. package/src/docs/navigation/components-index.ts +56 -0
  95. package/src/docs/navigation/navigation.ts +240 -232
  96. package/src/docs/navigation/pager.ts +67 -0
  97. package/src/docs/navigation/toc.ts +98 -0
  98. package/src/docs/search/docs-search.json +330 -20
  99. package/src/docs/search/markdown-renderer.ts +11 -3
  100. package/src/docs/shiki/index.ts +109 -0
  101. package/src/docs/tailwind/css/base/scrollbar.css +22 -0
  102. package/src/docs/tailwind/css/base/theme.css +96 -0
  103. package/src/docs/tailwind/css/base/typography.css +4 -0
  104. package/src/docs/tailwind/css/docs/boarding-pass.css +65 -0
  105. package/src/docs/tailwind/css/docs/content.css +32 -0
  106. package/src/docs/tailwind/css/docs/page.css +10 -0
  107. package/src/docs/tailwind/css/docs.css +9 -0
  108. package/src/docs/tailwind/css/home/hero.css +210 -0
  109. package/src/docs/tailwind/css/home/motion.css +84 -0
  110. package/src/docs/tailwind/css/home/sections.css +416 -0
  111. package/src/docs/tailwind/css/tailwind.css +7 -185
  112. package/src/tsconfig.json +113 -2
  113. package/src/tsconfig.tsbuildinfo +1 -1
  114. package/tailwind.config.js +8 -4
  115. package/docs/assets/index-wVZpLVYW.js +0 -12122
  116. package/docs/crawl/core/components/functional/date/date.html +0 -190
  117. package/docs/crawl/core/components/functional/fetch/fetch.html +0 -109
  118. package/docs/crawl/core/components/functional/if/if.html +0 -27
  119. package/docs/crawl/core/components/functional/jsonata/jsonata.html +0 -79
  120. package/docs/crawl/core/components/functional/list/list.html +0 -70
  121. package/docs/crawl/core/components/functional/mix/mix.html +0 -45
  122. package/docs/crawl/core/components/functional/queue/queue.html +0 -96
  123. package/docs/crawl/core/components/functional/router/router.html +0 -106
  124. package/docs/crawl/core/components/functional/sdui/sdui.html +0 -269
  125. package/docs/crawl/core/components/functional/states/states.html +0 -66
  126. package/docs/crawl/core/components/functional/submit/submit.html +0 -160
  127. package/docs/crawl/core/components/functional/subscriber/subscriber.html +0 -67
  128. package/docs/crawl/core/components/functional/value/value.html +0 -51
  129. package/docs/crawl/core/components/ui/alert/alert.html +0 -211
  130. package/docs/crawl/core/components/ui/alert-messages/alert-messages.html +0 -131
  131. package/docs/crawl/core/components/ui/badge/badge.html +0 -197
  132. package/docs/crawl/core/components/ui/button/button.html +0 -150
  133. package/docs/crawl/core/components/ui/captcha/captcha.html +0 -112
  134. package/docs/crawl/core/components/ui/card/card.html +0 -253
  135. package/docs/crawl/core/components/ui/divider/divider.html +0 -144
  136. package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +0 -73
  137. package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +0 -266
  138. package/docs/crawl/core/components/ui/form/form-actions/form-actions.html +0 -122
  139. package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +0 -121
  140. package/docs/crawl/core/components/ui/form/input/input.html +0 -109
  141. package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +0 -38
  142. package/docs/crawl/core/components/ui/form/radio/radio.html +0 -56
  143. package/docs/crawl/core/components/ui/form/select/select.html +0 -73
  144. package/docs/crawl/core/components/ui/form/switch/switch.html +0 -56
  145. package/docs/crawl/core/components/ui/form/textarea/textarea.html +0 -59
  146. package/docs/crawl/core/components/ui/group/group.html +0 -104
  147. package/docs/crawl/core/components/ui/icon/icon.html +0 -222
  148. package/docs/crawl/core/components/ui/image/image.html +0 -298
  149. package/docs/crawl/core/components/ui/link/link.html +0 -100
  150. package/docs/crawl/core/components/ui/loader/loader.html +0 -124
  151. package/docs/crawl/core/components/ui/menu/menu.html +0 -394
  152. package/docs/crawl/core/components/ui/modal/modal.html +0 -342
  153. package/docs/crawl/core/components/ui/pop/pop.html +0 -296
  154. package/docs/crawl/core/components/ui/progress/progress.html +0 -131
  155. package/docs/crawl/core/components/ui/table/table.html +0 -545
  156. package/docs/crawl/core/components/ui/theme/theme.html +0 -131
  157. package/docs/crawl/core/components/ui/toast/toast.html +0 -233
  158. package/docs/crawl/core/components/ui/tooltip/tooltip.html +0 -140
  159. package/docs/crawl/docs/_core-concept/dataFlow.html +0 -177
  160. package/docs/crawl/docs/_core-concept/overview.html +0 -66
  161. package/docs/crawl/docs/_core-concept/subscriber.html +0 -75
  162. package/docs/crawl/docs/_decorators/ancestor-attribute.html +0 -117
  163. package/docs/crawl/docs/_decorators/auto-subscribe.html +0 -202
  164. package/docs/crawl/docs/_decorators/bind.html +0 -141
  165. package/docs/crawl/docs/_decorators/get.html +0 -149
  166. package/docs/crawl/docs/_decorators/handle.html +0 -165
  167. package/docs/crawl/docs/_decorators/on-assign.html +0 -355
  168. package/docs/crawl/docs/_decorators/patch.html +0 -78
  169. package/docs/crawl/docs/_decorators/post.html +0 -139
  170. package/docs/crawl/docs/_decorators/publish.html +0 -61
  171. package/docs/crawl/docs/_decorators/put.html +0 -78
  172. package/docs/crawl/docs/_decorators/subscribe.html +0 -109
  173. package/docs/crawl/docs/_decorators/wait-for-ancestors.html +0 -137
  174. package/docs/crawl/docs/_directives/sub.html +0 -93
  175. package/docs/crawl/docs/_getting-started/ai-agents.html +0 -173
  176. package/docs/crawl/docs/_getting-started/concorde-manual-install.html +0 -100
  177. package/docs/crawl/docs/_getting-started/concorde-outside.html +0 -58
  178. package/docs/crawl/docs/_getting-started/create-a-component.html +0 -111
  179. package/docs/crawl/docs/_getting-started/my-first-component.html +0 -189
  180. package/docs/crawl/docs/_getting-started/my-first-subscriber.html +0 -110
  181. package/docs/crawl/docs/_getting-started/pubsub.html +0 -62
  182. package/docs/crawl/docs/_getting-started/start.html +0 -76
  183. package/docs/crawl/docs/_getting-started/theming.html +0 -333
  184. package/docs/crawl/docs/_misc/api-configuration.html +0 -149
  185. package/docs/crawl/docs/_misc/dataProviderKey.html +0 -135
  186. package/docs/crawl/docs/_misc/docs-mock-api.html +0 -158
  187. package/docs/crawl/docs/_misc/dynamic-path.html +0 -180
  188. package/docs/crawl/docs/_misc/endpoint.html +0 -50
  189. package/docs/crawl/docs/_misc/formelement-subscriber-removal.html +0 -138
  190. package/docs/crawl/docs/_misc/html-integration.html +0 -48
  191. package/docs/crawl/hubs/decorators.html +0 -547
  192. package/docs/crawl/hubs/form-components.html +0 -381
  193. package/docs/crawl/hubs/remote-data.html +0 -452
  194. package/docs/crawl/index.html +0 -102
  195. package/docs/llms-full.txt +0 -7008
  196. package/docs/llms.txt +0 -97
  197. package/docs/sitemap.xml +0 -240
  198. package/docs/src/core/components/functional/date/date.md +0 -290
  199. package/docs/src/core/components/functional/fetch/fetch.md +0 -125
  200. package/docs/src/core/components/functional/if/if.md +0 -9
  201. package/docs/src/core/components/functional/jsonata/jsonata.md +0 -56
  202. package/docs/src/core/components/functional/list/list.md +0 -65
  203. package/docs/src/core/components/functional/mix/mix.md +0 -41
  204. package/docs/src/core/components/functional/queue/queue.md +0 -72
  205. package/docs/src/core/components/functional/router/router.md +0 -94
  206. package/docs/src/core/components/functional/sdui/default-library.json +0 -108
  207. package/docs/src/core/components/functional/sdui/example.json +0 -99
  208. package/docs/src/core/components/functional/sdui/sdui.md +0 -356
  209. package/docs/src/core/components/functional/states/states.md +0 -87
  210. package/docs/src/core/components/functional/submit/submit.md +0 -114
  211. package/docs/src/core/components/functional/subscriber/subscriber.md +0 -91
  212. package/docs/src/core/components/functional/value/value.md +0 -35
  213. package/docs/src/core/components/ui/alert/alert.md +0 -196
  214. package/docs/src/core/components/ui/alert-messages/alert-messages.md +0 -85
  215. package/docs/src/core/components/ui/badge/badge.md +0 -188
  216. package/docs/src/core/components/ui/button/button.md +0 -183
  217. package/docs/src/core/components/ui/captcha/captcha.md +0 -71
  218. package/docs/src/core/components/ui/card/card.md +0 -215
  219. package/docs/src/core/components/ui/divider/divider.md +0 -129
  220. package/docs/src/core/components/ui/form/checkbox/checkbox.md +0 -77
  221. package/docs/src/core/components/ui/form/fieldset/fieldset.md +0 -205
  222. package/docs/src/core/components/ui/form/form-actions/form-actions.md +0 -100
  223. package/docs/src/core/components/ui/form/form-layout/form-layout.md +0 -119
  224. package/docs/src/core/components/ui/form/input/input.md +0 -142
  225. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +0 -29
  226. package/docs/src/core/components/ui/form/radio/radio.md +0 -57
  227. package/docs/src/core/components/ui/form/select/select.md +0 -71
  228. package/docs/src/core/components/ui/form/switch/switch.md +0 -57
  229. package/docs/src/core/components/ui/form/textarea/textarea.md +0 -65
  230. package/docs/src/core/components/ui/group/group.md +0 -75
  231. package/docs/src/core/components/ui/icon/icon.md +0 -214
  232. package/docs/src/core/components/ui/icon/icons.json +0 -1
  233. package/docs/src/core/components/ui/image/image.md +0 -233
  234. package/docs/src/core/components/ui/link/link.md +0 -81
  235. package/docs/src/core/components/ui/loader/loader.md +0 -112
  236. package/docs/src/core/components/ui/menu/menu.md +0 -370
  237. package/docs/src/core/components/ui/modal/modal.md +0 -235
  238. package/docs/src/core/components/ui/pop/pop.md +0 -294
  239. package/docs/src/core/components/ui/progress/progress.md +0 -109
  240. package/docs/src/core/components/ui/table/table.md +0 -431
  241. package/docs/src/core/components/ui/theme/theme.md +0 -122
  242. package/docs/src/core/components/ui/toast/toast.md +0 -216
  243. package/docs/src/core/components/ui/tooltip/tooltip.md +0 -124
  244. package/docs/src/docs/_core-concept/dataFlow.md +0 -88
  245. package/docs/src/docs/_core-concept/overview.md +0 -57
  246. package/docs/src/docs/_core-concept/subscriber.md +0 -75
  247. package/docs/src/docs/_decorators/ancestor-attribute.md +0 -128
  248. package/docs/src/docs/_decorators/auto-subscribe.md +0 -202
  249. package/docs/src/docs/_decorators/bind.md +0 -167
  250. package/docs/src/docs/_decorators/get.md +0 -162
  251. package/docs/src/docs/_decorators/handle.md +0 -175
  252. package/docs/src/docs/_decorators/on-assign.md +0 -390
  253. package/docs/src/docs/_decorators/patch.md +0 -70
  254. package/docs/src/docs/_decorators/post.md +0 -146
  255. package/docs/src/docs/_decorators/publish.md +0 -55
  256. package/docs/src/docs/_decorators/put.md +0 -70
  257. package/docs/src/docs/_decorators/subscribe.md +0 -100
  258. package/docs/src/docs/_decorators/wait-for-ancestors.md +0 -163
  259. package/docs/src/docs/_directives/sub.md +0 -91
  260. package/docs/src/docs/_getting-started/ai-agents.md +0 -76
  261. package/docs/src/docs/_getting-started/concorde-manual-install.md +0 -133
  262. package/docs/src/docs/_getting-started/concorde-outside.md +0 -33
  263. package/docs/src/docs/_getting-started/create-a-component.md +0 -139
  264. package/docs/src/docs/_getting-started/my-first-component.md +0 -236
  265. package/docs/src/docs/_getting-started/my-first-subscriber.md +0 -120
  266. package/docs/src/docs/_getting-started/pubsub.md +0 -37
  267. package/docs/src/docs/_getting-started/start.md +0 -47
  268. package/docs/src/docs/_getting-started/theming.md +0 -253
  269. package/docs/src/docs/_misc/api-configuration.md +0 -81
  270. package/docs/src/docs/_misc/dataProviderKey.md +0 -168
  271. package/docs/src/docs/_misc/docs-mock-api.md +0 -63
  272. package/docs/src/docs/_misc/dynamic-path.md +0 -71
  273. package/docs/src/docs/_misc/endpoint.md +0 -45
  274. package/docs/src/docs/_misc/formelement-subscriber-removal.md +0 -115
  275. package/docs/src/docs/_misc/html-integration.md +0 -15
  276. package/docs/src/docs/search/docs-search.json +0 -12242
  277. package/docs/src/tag-list.json +0 -1
  278. package/docs/src/tsconfig-model.json +0 -23
  279. package/docs/src/tsconfig.json +0 -1242
  280. package/src/docs/prism/index.ts +0 -6
  281. package/src/docs/prism/prism.css +0 -158
  282. package/src/docs/prism/prism.js +0 -1022
@@ -1,175 +0,0 @@
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&lt;DemoCounterData&gt;("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
- &lt;p&gt;Doubled count: ${this.doubled}&lt;/p&gt;
45
- &lt;p&gt;&lt;small&gt;Last update: ${this.lastUpdate}&lt;/small&gt;&lt;/p&gt;
46
- &lt;sonic-button @click=${this.incrementCount}&gt;Increment&lt;/sonic-button&gt;
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&lt;User, { userIndex: number }&gt;("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&lt;QueueConfig&gt;("sessionQueueConfig");
104
- const idle = new DataProviderKey&lt;{ isIdle: boolean }&gt;("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
- ### `skipEmptyPlaceholder`
165
-
166
- On a **dynamic key path** (`"items.${itemId}"`), if `true`, a placeholder resolved to `''` is treated as not ready (no subscription) — **empty string only**, not `0` or `false`. See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
167
-
168
- ## Highlights
169
-
170
- - Strict typing: the method receives one argument per key, in order.
171
- - Up to 3 keys; for 4+ keys (rare), keep [@onAssign](#docs/_decorators/on-assign.md/on-assign) for now.
172
- - 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`.
173
- - `skip` filters out values by **named category** (e.g. `[Skip.Nullish, Skip.EmptyObject]`); for arbitrary checks on a specific value, guard inside the method.
174
-
175
- See also [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) and [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
@@ -1,390 +0,0 @@
1
- # @onAssign
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
-
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.
6
-
7
- For a **typed** equivalent (recommended), use [@handle](#docs/_decorators/handle.md/handle).
8
-
9
- ## Principle
10
-
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).
12
-
13
- This is particularly useful when you need to wait for multiple data sources to be ready before executing logic.
14
-
15
- ## Usage
16
-
17
- ### Import
18
-
19
- <sonic-code language="typescript">
20
- <template>
21
- import { onAssign } from "@supersoniks/concorde/decorators";
22
- </template>
23
- </sonic-code>
24
-
25
- ### Basic example
26
-
27
-
28
- <sonic-code language="typescript">
29
- <template>
30
- //...
31
- @customElement("demo-on-assign")
32
- export class DemoOnAssign extends LitElement {
33
- static styles = [tailwind];
34
-
35
- @state() userWithSettings: any = null;
36
- @state() isReady: boolean = false;
37
- @state() lastUpdate: string = "";
38
-
39
- @onAssign("demoUser", "demoUserSettings")
40
- handleDataReady(user: any, settings: any) {
41
- this.isReady = Object.keys(user).length > 0 && Object.keys(settings).length > 0;
42
- this.userWithSettings = { ...user, ...settings };
43
- this.lastUpdate = new Date().toLocaleTimeString();
44
- this.requestUpdate();
45
- }
46
-
47
- render() {
48
- const { name, email, theme, language } = this.userWithSettings;
49
- return //...
50
- }
51
-
52
- updateData() {
53
- const user = PublisherManager.get("demoUser");
54
- const userSettings = PublisherManager.get("demoUserSettings");
55
- const userNumber = Math.floor(Math.random() * 100);
56
- user.set({
57
- name: `User n°${userNumber}`,
58
- email: `user-${userNumber}@example.com`,
59
- });
60
-
61
- userSettings.set({
62
- theme: ["light", "dark", "auto"][Math.floor(Math.random() * 3)],
63
- language: ["en", "fr", "es"][Math.floor(Math.random() * 3)],
64
- });
65
- }
66
- }
67
- </template>
68
- </sonic-code>
69
-
70
- <sonic-code>
71
- <template>
72
- <docs-demo-sources for="demo-on-assign"></docs-demo-sources>
73
- <demo-on-assign></demo-on-assign>
74
- </template>
75
- </sonic-code>
76
-
77
-
78
- ### Example with nested paths
79
-
80
- <sonic-code language="typescript">
81
- <template>
82
- @customElement("product-view")
83
- export class ProductView extends LitElement {
84
- product: any = null;
85
- inventory: any = null;
86
-
87
- @onAssign("store.product", "store.inventory")
88
- handleProductData(product: any, inventory: any) {
89
- this.product = product;
90
- this.inventory = inventory;
91
- this.requestUpdate();
92
- }
93
-
94
- render() {
95
- if (!this.product) return html`<div>Loading...</div>`;
96
-
97
- const stock = this.inventory[this.product.id] || 0;
98
- return html`
99
- <div>
100
- <h2>${this.product.name}</h2>
101
- <p>Price: ${this.product.price}€</p>
102
- <p>Stock: ${stock}</p>
103
- </div>
104
- `;
105
- }
106
- }
107
- </template>
108
- </sonic-code>
109
-
110
- ## Path syntax
111
-
112
- The path uses dot notation to navigate through the publisher structure:
113
-
114
- - **First segment**: dataProvider identifier (e.g., `"userData"`)
115
- - **Following segments**: nested properties (e.g., `"store.product"`)
116
- - **Array access**: use numeric index (e.g., `"data.items.0"`)
117
-
118
- ### Dynamic path driven by class properties
119
-
120
- You can now build the paths dynamically by referencing the host class properties inside the strings passed to `@onAssign`. Two placeholder syntaxes are supported:
121
-
122
- - `` ${myProperty} `` or `` ${this.myProperty} ``
123
- - `` {$myProperty} ``
124
-
125
- Each placeholder is replaced at runtime with the current value of the corresponding property. `@onAssign` automatically watches those properties and:
126
-
127
- - re-evaluates the final paths when one of them changes,
128
- - removes the previous subscriptions before attaching the new ones,
129
- - observe the changes inside `willUpdate(changedProperties)` so nothing touches the getters/setters.
130
-
131
- While a placeholder is `null`/`undefined`, subscriptions are detached. Optional `skipEmptyPlaceholder` on [@handle](#docs/_decorators/handle.md/handle) (typed replacement). Details: [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
132
-
133
-
134
- <sonic-code language="typescript">
135
- <template>
136
- @customElement("demo-on-assign-dynamic")
137
- export class DemoOnAssignDynamic extends LitElement {
138
- static styles = [tailwind];
139
-
140
- @property({ type: String })
141
- dataProvider: "demoUsers" | "demoUsersAlt" = "demoUsers";
142
-
143
- @property({ type: Number })
144
- userIndex: number = 0;
145
-
146
- @state() user: any = null;
147
- @state() userSettings: any = null;
148
-
149
- @onAssign("${dataProvider}.${userIndex}", "${dataProvider}Settings.${userIndex}")
150
- handleUserDataReady(user: any, settings: any) {
151
- this.user = user;
152
- this.userSettings = settings;
153
- }
154
-
155
- updateUserIndex(e: Event) {
156
- this.userIndex = parseInt((e.target as HTMLInputElement).value);
157
- }
158
-
159
- updateDataProvider(e: Event) {
160
- this.dataProvider = (e.target as HTMLSelectElement).value as
161
- | "demoUsers"
162
- | "demoUsersAlt";
163
- }
164
-
165
- updateCurrentUserData() {
166
- const usersPublisher = PublisherManager.get(this.dataProvider);
167
- const settingsPublisher = PublisherManager.get(
168
- `${this.dataProvider}Settings`
169
- );
170
- const userPublisher = Objects.traverse(
171
- usersPublisher,
172
- [String(this.userIndex)]
173
- ) as PublisherProxy;
174
- const settingPublisher = Objects.traverse(
175
- settingsPublisher,
176
- [String(this.userIndex)]
177
- ) as PublisherProxy;
178
-
179
- if (userPublisher && settingPublisher) {
180
- // Générer de nouvelles données aléatoires
181
- const randomNames = [
182
- { firstName: "Alice", lastName: "Wonder" },
183
- { firstName: "Bob", lastName: "Builder" },
184
- { firstName: "Charlie", lastName: "Chaplin" },
185
- ];
186
- const randomThemes = ["light", "dark", "auto"];
187
- const randomLanguages = ["en", "fr", "es"];
188
-
189
- const randomName =
190
- randomNames[Math.floor(Math.random() * randomNames.length)];
191
- const randomEmail = `${randomName.firstName.toLowerCase()}.${randomName.lastName.toLowerCase()}@example.com`;
192
- const randomTheme =
193
- randomThemes[Math.floor(Math.random() * randomThemes.length)];
194
- const randomLanguage =
195
- randomLanguages[Math.floor(Math.random() * randomLanguages.length)];
196
-
197
- // Mettre à jour l'utilisateur directement
198
- const currentUser = userPublisher.get() || {};
199
- userPublisher.set({
200
- ...currentUser,
201
- firstName: randomName.firstName,
202
- lastName: randomName.lastName,
203
- email: randomEmail,
204
- });
205
-
206
- // Mettre à jour les settings directement
207
- settingPublisher.set({
208
- theme: randomTheme,
209
- language: randomLanguage,
210
- });
211
- }
212
- }
213
-
214
- render() {
215
- return html`
216
- &lt;div class="flex flex-col gap-2"&gt;
217
- &lt;sonic-select label="Users set" @change=${this.updateDataProvider}&gt;
218
- &lt;option value="demoUsers"&gt;First set of users&lt;/option&gt;
219
- &lt;option value="demoUsersAlt"&gt;Second set of users&lt;/option&gt;
220
- &lt;/sonic-select&gt;
221
- &lt;sonic-input
222
- type="number"
223
- .value=${this.userIndex}
224
- @input=${this.updateUserIndex}
225
- min="0"
226
- max="9"
227
- label="Index"
228
- class="block"
229
- &gt;&lt;/sonic-input&gt;
230
- &lt;sonic-button @click=${this.updateCurrentUserData}&gt;
231
- Update current user data
232
- &lt;/sonic-button&gt;
233
- &lt;div class="flex flex-col gap-2 border p-2"&gt;
234
- &lt;div&gt;
235
- &lt;sonic-icon name="user" library="heroicons"&gt;&lt;/sonic-icon&gt;
236
- ${this.user?.firstName} ${this.user?.lastName}
237
- &lt;/div&gt;
238
- &lt;div&gt;
239
- &lt;sonic-icon name="envelope" library="heroicons"&gt;&lt;/sonic-icon&gt;
240
- ${this.user?.email}
241
- &lt;/div&gt;
242
- &lt;div&gt;
243
- Theme: ${this.userSettings?.theme} | Language:
244
- ${this.userSettings?.language}
245
- &lt;/div&gt;
246
- &lt;/div&gt;
247
- &lt;/div&gt;
248
- `;
249
- }
250
- }
251
- </template>
252
- </sonic-code>
253
-
254
- <sonic-code>
255
- <template>
256
- <docs-demo-sources for="demo-on-assign-dynamic"></docs-demo-sources>
257
- <demo-on-assign-dynamic></demo-on-assign-dynamic>
258
- </template>
259
- </sonic-code>
260
-
261
- > ⚠️ Use classic string literals: `@onAssign("${dataProvider}.${profileId}", "settings.${profileId}")`. Do **not** use template literals (backticks), otherwise JavaScript would try to interpolate the value immediately.
262
-
263
- Additional constraints:
264
-
265
- - The hosting class must expose a `willUpdate(changedProperties?: Map<PropertyKey, unknown>)` method (LitElement already provides it) so that `@onAssign` can listen to dependency changes.
266
- - Dependencies need to be reactive (e.g. `@property()` on LitElement) or you must call `this.requestUpdate("myProp")` manually after changing them, otherwise `willUpdate` will never be notified.
267
- - If you use nested expressions like `${user.id}`, the first segment (`user`) is the one being observed: you need to reassign `this.user` (e.g. with a new object) so that the binding can detect the change.
268
-
269
- ## Behavior
270
-
271
- - The method is called only when **all** specified publishers have been assigned values
272
- - The method receives the values of all publishers as arguments, in the same order as specified
273
- - Subscription happens at the time of `connectedCallback`
274
- - Unsubscription happens automatically at the time of `disconnectedCallback`
275
- - If a publisher doesn't exist yet, it will be created with the value `null`
276
- - The method is called with `this` bound to the component instance
277
-
278
- ## Use cases
279
-
280
- This decorator is particularly useful for:
281
-
282
- - **Waiting for multiple data sources** before rendering or executing logic
283
- - **Synchronizing data** from different publishers
284
- - **Initializing components** that depend on multiple data providers
285
- - **Handling complex data dependencies** where you need all data to be ready
286
-
287
- ## Complete example
288
-
289
- <sonic-code language="typescript">
290
- <template>
291
- import { html, LitElement } from "lit";
292
- import { customElement } from "lit/decorators.js";
293
- import { onAssign } from "@supersoniks/concorde/decorators";
294
- import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
295
-
296
- @customElement("order-summary")
297
- export class OrderSummary extends LitElement {
298
- order: any = null;
299
- customer: any = null;
300
- shipping: any = null;
301
-
302
- @onAssign("orderData", "customerData", "shippingData")
303
- handleOrderReady(order: any, customer: any, shipping: any) {
304
- this.order = order;
305
- this.customer = customer;
306
- this.shipping = shipping;
307
- this.requestUpdate();
308
- }
309
-
310
- render() {
311
- if (!this.order || !this.customer || !this.shipping) {
312
- return html`<div>Loading order details...</div>`;
313
- }
314
-
315
- return html`
316
- <div class="order-summary">
317
- <h2>Order #${this.order.id}</h2>
318
- <p>Customer: ${this.customer.name}</p>
319
- <p>Shipping to: ${this.shipping.address}</p>
320
- <p>Total: ${this.order.total}€</p>
321
- </div>
322
- `;
323
- }
324
- }
325
- // Somewhere in your code, update the publishers:
326
- const orderPub = PublisherManager.get("orderData");
327
- const customerPub = PublisherManager.get("customerData");
328
- const shippingPub = PublisherManager.get("shippingData");
329
- // The method will be called only when all three are set:
330
- orderPub.set({ id: "123", total: 99.99 });
331
- customerPub.set({ name: "John Doe", email: "john@example.com" });
332
- shippingPub.set({ address: "123 Main St" });
333
- // handleOrderReady will be called with all three values
334
- </template>
335
- </sonic-code>
336
-
337
- ## Migrating to @handle
338
-
339
- `@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.
340
-
341
- ### Why migrate
342
-
343
- - **Typed paths**: keys are `DataProviderKey<T>`, so the method arguments are strongly typed (no more `any`).
344
- - **Explicit intent**: `waitForAllDefined` and `skip` replace implicit behavior.
345
- - **Single API**: `@handle` covers the mono- and multi-path cases (up to 3 keys).
346
-
347
- ### Equivalent semantics (`waitForAllDefined`)
348
-
349
- <sonic-code language="typescript">
350
- <template>
351
- // Before
352
- @onAssign("demoUser", "demoUserSettings")
353
- handleDataReady(user: any, settings: any) { /* ... */ }
354
-
355
- // After — same "wait for everything" behavior, but typed
356
- const user = new DataProviderKey&lt;User&gt;("demoUser");
357
- const settings = new DataProviderKey&lt;Settings&gt;("demoUserSettings");
358
-
359
- @handle(user, settings, { waitForAllDefined: true })
360
- handleDataReady(user: User, settings: Settings) { /* ... */ }
361
- </template>
362
- </sonic-code>
363
-
364
- ### Single path
365
-
366
- <sonic-code language="typescript">
367
- <template>
368
- // Before
369
- @onAssign("settings.modules.logs_route.enabled")
370
- onLogRoute(value: boolean) { /* ... */ }
371
-
372
- // After
373
- const settings = new DataProviderKey&lt;AppSettings&gt;("settings");
374
-
375
- @handle(settings.modules.logs_route.enabled)
376
- onLogRoute(value: boolean) { /* ... */ }
377
- </template>
378
- </sonic-code>
379
-
380
- ### 4+ paths
381
-
382
- `@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).
383
-
384
- ## Notes
385
-
386
- - This decorator works with any component that has `connectedCallback` and `disconnectedCallback` methods (such as `LitElement` or components extending `Subscriber`)
387
- - The method is called synchronously when all publishers are ready
388
- - If you need to update the UI, remember to call `this.requestUpdate()` inside the method
389
- - For more information about publishers, see the documentation on [Sharing data](#docs/_getting-started/pubsub.md/pubsub)
390
-
@@ -1,70 +0,0 @@
1
- # @patch
2
-
3
- `@patch` applies a partial update with the body from a publisher. It uses the
4
- same model as [@post](#docs/_decorators/post.md/post):
5
- `Endpoint<T>`, `DataProviderKey<B>`, `ApiResult<T>`, options, and the
6
- `send(component)` handle.
7
-
8
- ## Minimal usage
9
-
10
- ```typescript
11
- const bodyKey = new DataProviderKey<Partial<User>>("users.patch");
12
- const patchUser = patch(new Endpoint<User>("users/42"), bodyKey);
13
-
14
- @patchUser
15
- @state()
16
- payload?: ApiResult<User>;
17
- ```
18
-
19
- The same decorator adapts to a method:
20
-
21
- ```typescript
22
- @patch(new Endpoint<User>("users/42"), bodyKey)
23
- handlePatch(payload?: ApiResult<User>) {
24
- if (payload?.response?.ok) console.log(payload.result);
25
- }
26
- ```
27
-
28
- To run the operation again from the component:
29
-
30
- ```typescript
31
- async savePatch() {
32
- await patchUser.send(this);
33
- }
34
- ```
35
-
36
- `send(component)` returns a Promise resolved after the PATCH completes. See
37
- [@post](#docs/_decorators/post.md/post) for the method-decorator form and the
38
- `when: "before"` option.
39
-
40
- The PATCH is also sent automatically when the body mutates, unless
41
- `autoPostOnBodyMutation: false` is set. The available options are described in
42
- [@post](#docs/_decorators/post.md/post):
43
-
44
- - `autoPostOnBodyMutation`
45
- - `skipIfBodyMissing`
46
- - `refetchEveryMs`
47
- - `skipEmptyPlaceholder`
48
- - `triggerKey`
49
-
50
- Pass explicit API configuration as the third argument:
51
-
52
- ```typescript
53
- @patch(endpoint, bodyKey, apiConfigurationKey)
54
- ```
55
-
56
- ## Import
57
-
58
- <sonic-code language="typescript">
59
- <template>
60
- import { patch, type ApiResult } from "@supersoniks/concorde/decorators";
61
- import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
62
- import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
63
- </template>
64
- </sonic-code>
65
-
66
- ## See also
67
-
68
- - [@post](#docs/_decorators/post.md/post) — options and triggers
69
- - [@put](#docs/_decorators/put.md/put) — full replacement
70
- - [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path)