@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.
Files changed (179) hide show
  1. package/.gitlab-ci.yml +23 -0
  2. package/README.md +106 -55
  3. package/ai/AGENTS.md +52 -0
  4. package/ai/README.md +30 -0
  5. package/ai/cursor/rules/concorde-menu.mdc +15 -0
  6. package/ai/cursor/rules/concorde-scope.mdc +14 -0
  7. package/ai/cursor/rules/concorde-theme.mdc +13 -0
  8. package/ai/cursor/rules/concorde.mdc +49 -0
  9. package/ai/jetbrains/rules/concorde.md +39 -0
  10. package/ai/skills/concorde/SKILL.md +220 -0
  11. package/ai/skills/concorde-get-set-dp/SKILL.md +194 -0
  12. package/ai/skills/concorde-imports/SKILL.md +78 -0
  13. package/ai/skills/concorde-menu/SKILL.md +74 -0
  14. package/ai/skills/concorde-scope/SKILL.md +70 -0
  15. package/ai/skills/concorde-theme/SKILL.md +46 -0
  16. package/build-infos.json +1 -1
  17. package/concorde-core.bundle.js +127 -127
  18. package/concorde-core.es.js +1435 -1364
  19. package/dist/altcha-widget.js +2662 -0
  20. package/dist/concorde-core.bundle.js +127 -127
  21. package/dist/concorde-core.es.js +1435 -1364
  22. package/dist/docs-mock-api-sw.js +589 -0
  23. package/dist/docs-mock-api-sw.js.map +7 -0
  24. package/docs/altcha-widget.js +2662 -0
  25. package/docs/assets/index-D9pxaQYK.js +7508 -0
  26. package/docs/assets/index-t0-i22oI.css +1 -0
  27. package/docs/docs-mock-api-sw.js +589 -0
  28. package/docs/docs-mock-api-sw.js.map +7 -0
  29. package/docs/index.html +2 -2
  30. package/docs/src/core/components/functional/fetch/fetch.md +13 -11
  31. package/docs/src/core/components/functional/if/if.md +4 -11
  32. package/docs/src/core/components/functional/list/list.md +60 -194
  33. package/docs/src/core/components/functional/queue/queue.md +70 -85
  34. package/docs/src/core/components/functional/router/router.md +62 -97
  35. package/docs/src/core/components/functional/states/states.md +2 -2
  36. package/docs/src/core/components/functional/submit/submit.md +86 -55
  37. package/docs/src/core/components/ui/captcha/captcha.md +2 -2
  38. package/docs/src/core/components/ui/card/card.md +1 -1
  39. package/docs/src/core/components/ui/form/checkbox/checkbox.md +5 -32
  40. package/docs/src/core/components/ui/form/input/input.md +5 -30
  41. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +6 -4
  42. package/docs/src/core/components/ui/form/radio/radio.md +5 -32
  43. package/docs/src/core/components/ui/form/select/select.md +5 -31
  44. package/docs/src/core/components/ui/form/switch/switch.md +5 -32
  45. package/docs/src/core/components/ui/loader/loader.md +1 -13
  46. package/docs/src/core/components/ui/table/table.md +3 -3
  47. package/docs/src/docs/_core-concept/dataFlow.md +73 -0
  48. package/docs/src/docs/_core-concept/subscriber.md +9 -10
  49. package/docs/src/docs/_decorators/ancestor-attribute.md +4 -3
  50. package/docs/src/docs/_decorators/auto-subscribe.md +19 -16
  51. package/docs/src/docs/_decorators/bind.md +20 -17
  52. package/docs/src/docs/_decorators/get.md +7 -4
  53. package/docs/src/docs/_decorators/handle.md +171 -0
  54. package/docs/src/docs/_decorators/on-assign.md +99 -73
  55. package/docs/src/docs/_decorators/publish.md +2 -1
  56. package/docs/src/docs/_decorators/subscribe.md +70 -9
  57. package/docs/src/docs/_decorators/wait-for-ancestors.md +13 -10
  58. package/docs/src/docs/_directives/sub.md +91 -0
  59. package/docs/src/docs/_getting-started/ai-agents.md +56 -0
  60. package/docs/src/docs/_getting-started/concorde-manual-install.md +133 -0
  61. package/docs/src/docs/_getting-started/concorde-outside.md +13 -123
  62. package/docs/src/docs/_getting-started/create-a-component.md +2 -0
  63. package/docs/src/docs/_getting-started/my-first-component.md +236 -0
  64. package/docs/src/docs/_getting-started/my-first-subscriber.md +29 -83
  65. package/docs/src/docs/_getting-started/pubsub.md +21 -134
  66. package/docs/src/docs/_getting-started/start.md +26 -18
  67. package/docs/src/docs/_misc/api-configuration.md +79 -0
  68. package/docs/src/docs/_misc/dataProviderKey.md +38 -5
  69. package/docs/src/docs/_misc/docs-mock-api.md +60 -0
  70. package/docs/src/docs/_misc/endpoint.md +2 -1
  71. package/docs/src/docs/_misc/html-integration.md +13 -0
  72. package/docs/src/docs/search/docs-search.json +4163 -873
  73. package/docs/src/tsconfig.json +380 -317
  74. package/gitlab/job_tests.sh +55 -0
  75. package/package.json +34 -3
  76. package/public/altcha-widget.js +2662 -0
  77. package/public/docs-mock-api-sw.js +589 -0
  78. package/public/docs-mock-api-sw.js.map +7 -0
  79. package/scripts/ai-init.mjs +167 -0
  80. package/scripts/docs-mock-api-vite-plugin.ts +116 -0
  81. package/scripts/docs-open-in-editor-plugin.ts +130 -0
  82. package/scripts/pre-publish.mjs +2 -1
  83. package/src/core/components/functional/example/example.ts +1 -1
  84. package/src/core/components/functional/fetch/fetch.md +13 -11
  85. package/src/core/components/functional/if/if.md +4 -11
  86. package/src/core/components/functional/list/list.demo.ts +4 -4
  87. package/src/core/components/functional/list/list.md +60 -194
  88. package/src/core/components/functional/list/list.ts +8 -7
  89. package/src/core/components/functional/queue/queue.demo.ts +1 -1
  90. package/src/core/components/functional/queue/queue.md +70 -85
  91. package/src/core/components/functional/queue/queue.ts +4 -4
  92. package/src/core/components/functional/router/router.md +62 -97
  93. package/src/core/components/functional/router/router.ts +1 -1
  94. package/src/core/components/functional/states/states.md +2 -2
  95. package/src/core/components/functional/submit/submit.md +86 -55
  96. package/src/core/components/functional/submit/submit.ts +10 -3
  97. package/src/core/components/ui/captcha/captcha.md +2 -2
  98. package/src/core/components/ui/card/card.md +1 -1
  99. package/src/core/components/ui/form/checkbox/checkbox.md +5 -32
  100. package/src/core/components/ui/form/input/input.md +5 -30
  101. package/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +6 -4
  102. package/src/core/components/ui/form/radio/radio.md +5 -32
  103. package/src/core/components/ui/form/select/select.md +5 -31
  104. package/src/core/components/ui/form/switch/switch.md +5 -32
  105. package/src/core/components/ui/loader/loader.md +1 -13
  106. package/src/core/components/ui/table/table.md +3 -3
  107. package/src/core/directives/DataProvider.sub.spec.ts +96 -0
  108. package/src/core/directives/DataProvider.ts +109 -40
  109. package/src/core/utils/PublisherProxy.ts +33 -18
  110. package/src/core/utils/dataProviderKey.ts +23 -0
  111. package/src/core/utils/publisherPathKey.spec.ts +58 -0
  112. package/src/docs/_core-concept/dataFlow.md +73 -0
  113. package/src/docs/_core-concept/subscriber.md +9 -10
  114. package/src/docs/_decorators/ancestor-attribute.md +4 -3
  115. package/src/docs/_decorators/auto-subscribe.md +19 -16
  116. package/src/docs/_decorators/bind.md +19 -16
  117. package/src/docs/_decorators/get.md +7 -4
  118. package/src/docs/_decorators/handle.md +15 -13
  119. package/src/docs/_decorators/on-assign.md +53 -53
  120. package/src/docs/_decorators/publish.md +2 -1
  121. package/src/docs/_decorators/subscribe.md +70 -9
  122. package/src/docs/_decorators/wait-for-ancestors.md +13 -10
  123. package/src/docs/_directives/sub.md +91 -0
  124. package/src/docs/_getting-started/ai-agents.md +56 -0
  125. package/src/docs/_getting-started/concorde-manual-install.md +133 -0
  126. package/src/docs/_getting-started/concorde-outside.md +13 -123
  127. package/src/docs/_getting-started/create-a-component.md +2 -0
  128. package/src/docs/_getting-started/my-first-component.md +236 -0
  129. package/src/docs/_getting-started/my-first-subscriber.md +29 -83
  130. package/src/docs/_getting-started/pubsub.md +21 -134
  131. package/src/docs/_getting-started/start.md +26 -18
  132. package/src/docs/_misc/api-configuration.md +79 -0
  133. package/src/docs/_misc/dataProviderKey.md +34 -1
  134. package/src/docs/_misc/docs-mock-api.md +60 -0
  135. package/src/docs/_misc/endpoint.md +2 -1
  136. package/src/docs/_misc/html-integration.md +13 -0
  137. package/src/docs/code.ts +58 -12
  138. package/src/docs/components/docs-demo-sources.ts +397 -0
  139. package/src/docs/components/docs-lit-demo-raw.ts +28 -0
  140. package/src/docs/components/docs-lit-demo.ts +166 -0
  141. package/src/docs/components/docs-source-link.ts +72 -0
  142. package/src/docs/docs-location.ts +54 -0
  143. package/src/docs/docs.ts +12 -0
  144. package/src/docs/example/decorators-demo-bind-demos.ts +41 -46
  145. package/src/docs/example/decorators-demo-geo.ts +16 -11
  146. package/src/docs/example/decorators-demo-init.ts +2 -228
  147. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +54 -14
  148. package/src/docs/example/decorators-demo.ts +71 -70
  149. package/src/docs/example/docs-api-config-demos.ts +234 -0
  150. package/src/docs/example/docs-joke-demos.ts +297 -0
  151. package/src/docs/example/docs-list-demos.ts +179 -0
  152. package/src/docs/example/docs-provider-keys.ts +315 -0
  153. package/src/docs/example/docs-queue-demos.ts +114 -0
  154. package/src/docs/example/docs-router-demos.ts +89 -0
  155. package/src/docs/example/docs-submit-demos.ts +455 -0
  156. package/src/docs/example/docs-toggle-demos.ts +73 -0
  157. package/src/docs/example/docs-user-two-scopes.ts +37 -0
  158. package/src/docs/example/docs-users-list.ts +71 -0
  159. package/src/docs/example/users.ts +41 -24
  160. package/src/docs/mock-api/api-config-mock.ts +152 -0
  161. package/src/docs/mock-api/fixtures.ts +377 -0
  162. package/src/docs/mock-api/register.ts +25 -0
  163. package/src/docs/mock-api/router.ts +234 -0
  164. package/src/docs/mock-api/service-worker.ts +23 -0
  165. package/src/docs/mock-api/urls.ts +11 -0
  166. package/src/docs/navigation/navigation.ts +39 -7
  167. package/src/docs/search/docs-search.json +4021 -936
  168. package/src/docs/search/markdown-renderer.ts +7 -3
  169. package/src/docs/search/page.ts +11 -14
  170. package/src/docs/search/sonic-code-markdown.spec.ts +29 -0
  171. package/src/docs/search/sonic-code-markdown.ts +28 -0
  172. package/src/docs.ts +4 -0
  173. package/src/tsconfig.json +87 -0
  174. package/src/tsconfig.tsbuildinfo +1 -1
  175. package/vite.config.mts +8 -0
  176. package/docs/assets/index-CaysOMFz.js +0 -5046
  177. package/docs/assets/index-D8mGoXzF.css +0 -1
  178. package/docs/src/docs/_misc/templates-demo.md +0 -19
  179. package/src/docs/_misc/templates-demo.md +0 -19
@@ -0,0 +1,234 @@
1
+ import { html, LitElement, nothing } from "lit";
2
+ import { customElement, state } from "lit/decorators.js";
3
+ import { get } from "@supersoniks/concorde/core/decorators/api";
4
+ import type { ApiGetResult } from "@supersoniks/concorde/core/utils/api";
5
+ import { Endpoint } from "@supersoniks/concorde/core/utils/endpoint";
6
+ import { set } from "@supersoniks/concorde/core/utils/PublisherProxy";
7
+ import { wording } from "@supersoniks/concorde/core/directives/Wording";
8
+ import { DOCS_MOCK_API_BASE } from "../mock-api/urls";
9
+ import {
10
+ DOCS_MOCK_EVENTS_API_TOKEN,
11
+ DOCS_MOCK_TOKEN_FRESH,
12
+ DOCS_MOCK_TOKEN_STALE,
13
+ DOCS_MOCK_TOKEN_VALID,
14
+ } from "../mock-api/api-config-mock";
15
+ import {
16
+ docsApiConfBearerKey,
17
+ docsApiConfStaleTokenKey,
18
+ docsApiConfTokenProviderKey,
19
+ docsApiConfWordingVersionKey,
20
+ } from "./docs-provider-keys";
21
+ import { tailwind } from "../tailwind";
22
+
23
+ import "../../core/components/functional/fetch/fetch";
24
+ import "../../core/components/functional/sonic-scope/sonic-scope";
25
+ import "../../core/components/ui/button/button";
26
+
27
+ type ProtectedResult = {
28
+ ok?: boolean;
29
+ message?: string;
30
+ tokenUsed?: string | null;
31
+ error?: string;
32
+ };
33
+
34
+ const protectedEndpoint = new Endpoint<ProtectedResult>("api/config/protected");
35
+
36
+ function resultPanel(payload: ApiGetResult<ProtectedResult> | null | undefined) {
37
+ const status = payload?.response?.status;
38
+ const r = payload?.result;
39
+ if (payload === null) {
40
+ return html`<p class="text-sm text-neutral-500">Loading…</p>`;
41
+ }
42
+ if (!payload?.response) {
43
+ return html`<p class="text-sm text-neutral-500">Waiting for request…</p>`;
44
+ }
45
+ return html`
46
+ <p class="text-sm">
47
+ HTTP <span class="font-mono">${status}</span>
48
+ ${r?.message
49
+ ? html` — <span class="font-medium">${r.message}</span>`
50
+ : r?.error
51
+ ? html` — <span class="text-red-600">${r.error}</span>`
52
+ : nothing}
53
+ ${r?.tokenUsed
54
+ ? html`<span class="block text-xs text-neutral-500 mt-1"
55
+ >token: ${r.tokenUsed}</span
56
+ >`
57
+ : nothing}
58
+ </p>
59
+ `;
60
+ }
61
+
62
+ /** Static Bearer token on APIConfiguration (`token` attribute / publisher). */
63
+ @customElement("docs-api-config-bearer-demo")
64
+ export class DocsApiConfigBearerDemo extends LitElement {
65
+ static styles = [tailwind];
66
+
67
+ @get(protectedEndpoint, docsApiConfBearerKey)
68
+ @state()
69
+ private payload: ApiGetResult<ProtectedResult> | null = null;
70
+
71
+ render() {
72
+ return html`
73
+ <p class="mb-2 text-sm text-neutral-600">
74
+ Config <code>${docsApiConfBearerKey.path}</code> —
75
+ <code>token="${DOCS_MOCK_TOKEN_VALID}"</code>
76
+ </p>
77
+ ${resultPanel(this.payload)}
78
+ `;
79
+ }
80
+ }
81
+
82
+ /** Basic auth + <code>tokenProvider</code> → GET <code>/auth/token</code> then Bearer. */
83
+ @customElement("docs-api-config-token-provider-demo")
84
+ export class DocsApiConfigTokenProviderDemo extends LitElement {
85
+ static styles = [tailwind];
86
+
87
+ @get(protectedEndpoint, docsApiConfTokenProviderKey)
88
+ @state()
89
+ private payload: ApiGetResult<ProtectedResult> | null = null;
90
+
91
+ render() {
92
+ return html`
93
+ <p class="mb-2 text-sm text-neutral-600">
94
+ <code>userName</code> / <code>password</code> +
95
+ <code>tokenProvider="auth/token"</code> (no static
96
+ <code>token</code>)
97
+ </p>
98
+ ${resultPanel(this.payload)}
99
+ `;
100
+ }
101
+ }
102
+
103
+ /** Stale Bearer → HTTP 498 → refresh via <code>tokenProvider</code> → retry. */
104
+ @customElement("docs-api-config-stale-token-demo")
105
+ export class DocsApiConfigStaleTokenDemo extends LitElement {
106
+ static styles = [tailwind];
107
+
108
+ @get(protectedEndpoint, docsApiConfStaleTokenKey)
109
+ @state()
110
+ private payload: ApiGetResult<ProtectedResult> | null = null;
111
+
112
+ render() {
113
+ const refreshed =
114
+ this.payload?.result?.tokenUsed === DOCS_MOCK_TOKEN_FRESH;
115
+ return html`
116
+ <p class="mb-2 text-sm text-neutral-600">
117
+ Starts with <code>token="${DOCS_MOCK_TOKEN_STALE}"</code> (mock returns
118
+ 498), then <code>auth()</code> fetches
119
+ <code>${DOCS_MOCK_TOKEN_FRESH}</code>.
120
+ </p>
121
+ ${resultPanel(this.payload)}
122
+ ${refreshed
123
+ ? html`<p class="mt-1 text-xs text-green-700">
124
+ Retry succeeded with refreshed token.
125
+ </p>`
126
+ : nothing}
127
+ `;
128
+ }
129
+ }
130
+
131
+ /** Ancestor attributes: <code>wordingProvider</code> + directive <code>wording()</code>. */
132
+ @customElement("docs-api-config-wording-demo")
133
+ export class DocsApiConfigWordingDemo extends LitElement {
134
+ static styles = [tailwind];
135
+
136
+ render() {
137
+ return html`
138
+ <div
139
+ serviceURL=${DOCS_MOCK_API_BASE}
140
+ wordingProvider="wording/labels?lang=fr"
141
+ wordingVersionProvider=${docsApiConfWordingVersionKey.path}
142
+ >
143
+ <p class="text-sm space-y-2">
144
+ <span class="block">${wording("api-config.greeting")}</span>
145
+ <span class="block">${wording("api-config.farewell")}</span>
146
+ <span class="block text-neutral-500"
147
+ >${wording("api-config.hint")}</span
148
+ >
149
+ </p>
150
+ <sonic-button
151
+ size="sm"
152
+ class="mt-3"
153
+ @click=${() => set(docsApiConfWordingVersionKey, Date.now())}
154
+ >Bump wording version (reload)</sonic-button
155
+ >
156
+ </div>
157
+ `;
158
+ }
159
+ }
160
+
161
+ /** Scoped attributes on <code>sonic-scope</code> (read by <code>HTML.getApiConfiguration</code>). */
162
+ @customElement("docs-api-config-scoped-attrs-demo")
163
+ export class DocsApiConfigScopedAttrsDemo extends LitElement {
164
+ static styles = [tailwind];
165
+
166
+ render() {
167
+ return html`
168
+ <sonic-scope
169
+ serviceURL=${DOCS_MOCK_API_BASE}
170
+ token=${DOCS_MOCK_TOKEN_VALID}
171
+ credentials="same-origin"
172
+ addHTTPResponse
173
+ class="text-sm space-y-2 block"
174
+ >
175
+ <p>
176
+ Descendant components inherit
177
+ <code>serviceURL</code>, <code>token</code>,
178
+ <code>credentials</code>, <code>addHTTPResponse</code> from this
179
+ scope (see table above).
180
+ </p>
181
+ <sonic-fetch dataProvider=${protectedEndpoint.path}></sonic-fetch>
182
+ <sonic-button dataProvider=${protectedEndpoint.path} debug
183
+ >Hover — protected fetch payload</sonic-button
184
+ >
185
+ </sonic-scope>
186
+ `;
187
+ }
188
+ }
189
+
190
+ /** <code>eventsApiToken</code> → Bearer on <code>/auth/token</code> (no Basic). */
191
+ @customElement("docs-api-config-events-token-demo")
192
+ export class DocsApiConfigEventsTokenDemo extends LitElement {
193
+ static styles = [tailwind];
194
+
195
+ @state()
196
+ private token: string | null = null;
197
+
198
+ @state()
199
+ private error: string | null = null;
200
+
201
+ async connectedCallback() {
202
+ super.connectedCallback();
203
+ try {
204
+ const res = await fetch(
205
+ `${DOCS_MOCK_API_BASE}/auth/token?serviceHost=${encodeURIComponent(window.location.origin)}`,
206
+ {
207
+ headers: {
208
+ Authorization: `Bearer ${DOCS_MOCK_EVENTS_API_TOKEN}`,
209
+ },
210
+ },
211
+ );
212
+ const data = (await res.json()) as { token?: string; error?: string };
213
+ if (res.ok && data.token) this.token = data.token;
214
+ else this.error = data.error ?? `HTTP ${res.status}`;
215
+ } catch (e) {
216
+ this.error = String(e);
217
+ }
218
+ }
219
+
220
+ render() {
221
+ return html`
222
+ <p class="text-sm text-neutral-600 mb-2">
223
+ Ancêtre <code>eventsApiToken</code> (mapped to
224
+ <code>authToken</code> in config) — utilisé par
225
+ <code>API.auth()</code> pour appeler <code>tokenProvider</code>.
226
+ </p>
227
+ ${this.token
228
+ ? html`<p class="font-mono text-sm">token: ${this.token}</p>`
229
+ : this.error
230
+ ? html`<p class="text-red-600 text-sm">${this.error}</p>`
231
+ : html`<p class="text-sm text-neutral-500">Loading…</p>`}
232
+ `;
233
+ }
234
+ }
@@ -0,0 +1,297 @@
1
+ import { html, LitElement, nothing } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import {
4
+ ancestorAttribute,
5
+ subscribe,
6
+ } from "@supersoniks/concorde/core/decorators/Subscriber";
7
+ import { DataProviderKey } from "@supersoniks/concorde/core/utils/dataProviderKey";
8
+ import { DOCS_MOCK_JOKES_SERVICE } from "../mock-api/urls";
9
+ import {
10
+ docsJokeCheckboxFilterKey,
11
+ docsJokeInputFilterKey,
12
+ docsJokeRadioFilterKey,
13
+ docsJokeSelectFilterKey,
14
+ docsJokeSwitchFilterKey,
15
+ } from "./docs-provider-keys";
16
+ import { tailwind } from "../tailwind";
17
+
18
+ import "../../core/components/functional/queue/queue";
19
+ import "../../core/components/ui/form/input/input";
20
+ import "../../core/components/ui/form/select/select";
21
+ import "../../core/components/ui/form/checkbox/checkbox";
22
+ import "../../core/components/ui/form/radio/radio";
23
+ import "../../core/components/ui/form/switch/switch";
24
+
25
+ type JokeRow = {
26
+ joke: string;
27
+ setup?: string;
28
+ delivery?: string;
29
+ };
30
+
31
+ const jokeRowKey = new DataProviderKey<
32
+ JokeRow,
33
+ { dataProvider: string | null }
34
+ >("${dataProvider}");
35
+
36
+ const filterFieldKey = new DataProviderKey<
37
+ string,
38
+ { filterProvider: string; field: string }
39
+ >("${filterProvider}.${field}");
40
+
41
+ @customElement("docs-joke-item")
42
+ export class DocsJokeItem extends LitElement {
43
+ static styles = [tailwind];
44
+
45
+ @ancestorAttribute("dataProvider")
46
+ dataProvider: string | null = null;
47
+
48
+ @subscribe(jokeRowKey)
49
+ @state()
50
+ row: JokeRow | null = null;
51
+
52
+ render() {
53
+ const r = this.row;
54
+ if (!r) return nothing;
55
+ return html`
56
+ <div
57
+ class="space-y-1 border-0 border-b border-dotted border-neutral-300 py-3"
58
+ >
59
+ <div>${r.joke}</div>
60
+ ${r.setup
61
+ ? html`<div class="text-sm text-neutral-500">${r.setup}</div>`
62
+ : nothing}
63
+ ${r.delivery
64
+ ? html`<div class="text-sm text-neutral-500">${r.delivery}</div>`
65
+ : nothing}
66
+ </div>
67
+ `;
68
+ }
69
+ }
70
+
71
+ @customElement("docs-joke-filter-caption")
72
+ export class DocsJokeFilterCaption extends LitElement {
73
+ @property() filterProvider = "";
74
+ @property() field = "";
75
+ @property() label = "";
76
+
77
+ @subscribe(filterFieldKey)
78
+ @state()
79
+ value = "";
80
+
81
+ render() {
82
+ return html`
83
+ <p class="my-4 block text-xl font-bold">
84
+ ${this.label} « <span class="text-info">${this.value || "…"}</span> » :
85
+ </p>
86
+ `;
87
+ }
88
+ }
89
+
90
+ @customElement("docs-joke-queue")
91
+ export class DocsJokeQueue extends LitElement {
92
+ @property() filterProvider = "";
93
+ @property() expression = "joke/Any?amount=10&lang=fr";
94
+
95
+ private jokeItem = () => html`<docs-joke-item></docs-joke-item>`;
96
+
97
+ render() {
98
+ return html`
99
+ <sonic-queue
100
+ lazyload
101
+ dataProviderExpression=${this.expression}
102
+ dataFilterProvider=${this.filterProvider}
103
+ serviceURL=${DOCS_MOCK_JOKES_SERVICE}
104
+ key="jokes"
105
+ .items=${this.jokeItem}
106
+ ></sonic-queue>
107
+ `;
108
+ }
109
+ }
110
+
111
+ @customElement("docs-joke-search-demo")
112
+ export class DocsJokeSearchDemo extends LitElement {
113
+ static styles = [tailwind];
114
+
115
+ render() {
116
+ return html`
117
+ <sonic-input
118
+ formDataProvider=${docsJokeInputFilterKey.path}
119
+ name="contains"
120
+ value="chien"
121
+ class="mb-4"
122
+ ></sonic-input>
123
+ <docs-joke-filter-caption
124
+ filterProvider=${docsJokeInputFilterKey.path}
125
+ field="contains"
126
+ label="Blagues trouvées pour"
127
+ ></docs-joke-filter-caption>
128
+ <docs-joke-queue
129
+ filterProvider=${docsJokeInputFilterKey.path}
130
+ expression="joke/Any?amount=10&lang=fr"
131
+ ></docs-joke-queue>
132
+ `;
133
+ }
134
+ }
135
+
136
+ @customElement("docs-joke-lang-demo")
137
+ export class DocsJokeLangDemo extends LitElement {
138
+ static styles = [tailwind];
139
+
140
+ render() {
141
+ return html`
142
+ <sonic-select
143
+ formDataProvider=${docsJokeSelectFilterKey.path}
144
+ name="lang"
145
+ value="fr"
146
+ >
147
+ <option value="fr">fr</option>
148
+ <option value="en">en</option>
149
+ </sonic-select>
150
+ <docs-joke-filter-caption
151
+ filterProvider=${docsJokeSelectFilterKey.path}
152
+ field="lang"
153
+ label="Blagues pour la langue"
154
+ ></docs-joke-filter-caption>
155
+ <docs-joke-queue
156
+ filterProvider=${docsJokeSelectFilterKey.path}
157
+ expression="joke/Any?amount=10"
158
+ ></docs-joke-queue>
159
+ `;
160
+ }
161
+ }
162
+
163
+ const jokeQueueRow = (row: JokeRow) => html`
164
+ <div class="border-0 border-b border-neutral-300 py-3 leading-tight">
165
+ <div>${row.joke}</div>
166
+ ${row.setup ? html`<div class="font-bold">${row.setup}</div>` : nothing}
167
+ ${row.delivery ? html`<div>${row.delivery}</div>` : nothing}
168
+ </div>
169
+ `;
170
+
171
+ const BLACKLIST_CONTROLS = html`
172
+ <sonic-checkbox name="blacklistFlags" value="nsfw">nsfw</sonic-checkbox>
173
+ <sonic-checkbox name="blacklistFlags" value="religious">religious</sonic-checkbox>
174
+ <sonic-checkbox name="blacklistFlags" value="political">political</sonic-checkbox>
175
+ <sonic-checkbox name="blacklistFlags" value="racist" checked>racist</sonic-checkbox>
176
+ <sonic-checkbox name="blacklistFlags" value="sexist" checked>sexist</sonic-checkbox>
177
+ <sonic-checkbox name="blacklistFlags" value="explicit">explicit</sonic-checkbox>
178
+ `;
179
+
180
+ /** Checkbox — « Remove following jokes » (blacklistFlags → mock API). */
181
+ @customElement("docs-joke-blacklist-demo")
182
+ export class DocsJokeBlacklistDemo extends LitElement {
183
+ static styles = [tailwind];
184
+
185
+ private items = jokeQueueRow;
186
+
187
+ render() {
188
+ return html`
189
+ <p class="my-4 block text-xl font-bold">Remove following jokes :</p>
190
+ <div
191
+ formDataProvider=${docsJokeCheckboxFilterKey.path}
192
+ class="mb-3 grid grid-cols-2 gap-x-6 gap-y-2 lg:grid-cols-3"
193
+ >
194
+ ${BLACKLIST_CONTROLS}
195
+ </div>
196
+ <sonic-queue
197
+ lazyload
198
+ dataProviderExpression="joke/Any?offset=$offset&limit=$limit&lang=en"
199
+ dataFilterProvider=${docsJokeCheckboxFilterKey.path}
200
+ serviceURL=${DOCS_MOCK_JOKES_SERVICE}
201
+ key="jokes"
202
+ limit="5"
203
+ class="grid max-h-96 overflow-y-auto"
204
+ .items=${this.items}
205
+ debug
206
+ ></sonic-queue>
207
+ `;
208
+ }
209
+ }
210
+
211
+ /** Radio group — same API, single `blacklistFlags` value. */
212
+ @customElement("docs-joke-blacklist-radio-demo")
213
+ export class DocsJokeBlacklistRadioDemo extends LitElement {
214
+ static styles = [tailwind];
215
+
216
+ private items = jokeQueueRow;
217
+
218
+ render() {
219
+ return html`
220
+ <p class="my-4 block text-xl font-bold">Remove following jokes :</p>
221
+ <div
222
+ formDataProvider=${docsJokeRadioFilterKey.path}
223
+ class="mb-3 grid grid-cols-2 gap-x-6 gap-y-2 lg:grid-cols-3"
224
+ >
225
+ <sonic-radio name="blacklistFlags" value="nsfw">nsfw</sonic-radio>
226
+ <sonic-radio name="blacklistFlags" value="religious"
227
+ >religious</sonic-radio
228
+ >
229
+ <sonic-radio name="blacklistFlags" value="political"
230
+ >political</sonic-radio
231
+ >
232
+ <sonic-radio name="blacklistFlags" value="racist" checked
233
+ >racist</sonic-radio
234
+ >
235
+ <sonic-radio name="blacklistFlags" value="sexist">sexist</sonic-radio>
236
+ <sonic-radio name="blacklistFlags" value="explicit"
237
+ >explicit</sonic-radio
238
+ >
239
+ </div>
240
+ <sonic-queue
241
+ lazyload
242
+ dataProviderExpression="joke/Any?offset=$offset&limit=$limit&lang=en"
243
+ dataFilterProvider="jokeFilterRadio"
244
+ serviceURL=${DOCS_MOCK_JOKES_SERVICE}
245
+ key="jokes"
246
+ limit="5"
247
+ class="grid max-h-96 overflow-y-auto"
248
+ .items=${this.items}
249
+ debug
250
+ ></sonic-queue>
251
+ `;
252
+ }
253
+ }
254
+
255
+ /** Switch — same blacklist filter. */
256
+ @customElement("docs-joke-blacklist-switch-demo")
257
+ export class DocsJokeBlacklistSwitchDemo extends LitElement {
258
+ static styles = [tailwind];
259
+
260
+ private items = jokeQueueRow;
261
+
262
+ render() {
263
+ return html`
264
+ <p class="my-4 block text-xl font-bold">Remove following jokes :</p>
265
+ <div
266
+ formDataProvider=${docsJokeSwitchFilterKey.path}
267
+ class="mb-3 grid grid-cols-2 gap-x-6 gap-y-2 lg:grid-cols-3"
268
+ >
269
+ <sonic-switch name="blacklistFlags" value="nsfw">nsfw</sonic-switch>
270
+ <sonic-switch name="blacklistFlags" value="religious"
271
+ >religious</sonic-switch
272
+ >
273
+ <sonic-switch name="blacklistFlags" value="political"
274
+ >political</sonic-switch
275
+ >
276
+ <sonic-switch name="blacklistFlags" value="racist" checked
277
+ >racist</sonic-switch
278
+ >
279
+ <sonic-switch name="blacklistFlags" value="sexist">sexist</sonic-switch>
280
+ <sonic-switch name="blacklistFlags" value="explicit"
281
+ >explicit</sonic-switch
282
+ >
283
+ </div>
284
+ <sonic-queue
285
+ lazyload
286
+ dataProviderExpression="joke/Any?offset=$offset&limit=$limit&lang=en"
287
+ dataFilterProvider=${docsJokeSwitchFilterKey.path}
288
+ serviceURL=${DOCS_MOCK_JOKES_SERVICE}
289
+ key="jokes"
290
+ limit="5"
291
+ class="grid max-h-96 overflow-y-auto"
292
+ .items=${this.items}
293
+ debug
294
+ ></sonic-queue>
295
+ `;
296
+ }
297
+ }