@supersoniks/concorde 3.2.8 → 3.3.2

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 (50) hide show
  1. package/build-infos.json +1 -1
  2. package/concorde-core.bundle.js +229 -229
  3. package/concorde-core.es.js +2166 -1831
  4. package/dist/concorde-core.bundle.js +229 -229
  5. package/dist/concorde-core.es.js +2166 -1831
  6. package/docs/assets/{index-C0K6xugr.css → index-B669R8JF.css} +1 -1
  7. package/docs/assets/index-BTo6ly4d.js +4820 -0
  8. package/docs/index.html +2 -2
  9. package/docs/src/core/components/functional/fetch/fetch.md +6 -0
  10. package/docs/src/core/components/ui/menu/menu.md +46 -5
  11. package/docs/src/core/components/ui/modal/modal.md +0 -4
  12. package/docs/src/core/components/ui/toast/toast.md +166 -0
  13. package/docs/src/docs/_misc/ancestor-attribute.md +94 -0
  14. package/docs/src/docs/_misc/auto-subscribe.md +199 -0
  15. package/docs/src/docs/_misc/bind.md +362 -0
  16. package/docs/src/docs/_misc/on-assign.md +336 -0
  17. package/docs/src/docs/_misc/templates-demo.md +19 -0
  18. package/docs/src/docs/search/docs-search.json +550 -0
  19. package/docs/src/tsconfig-model.json +1 -1
  20. package/docs/src/tsconfig.json +28 -8
  21. package/package.json +8 -1
  22. package/src/core/components/functional/queue/queue.demo.ts +8 -11
  23. package/src/core/components/functional/sdui/sdui.ts +0 -0
  24. package/src/core/decorators/Subscriber.ts +5 -187
  25. package/src/core/decorators/subscriber/ancestorAttribute.ts +17 -0
  26. package/src/core/decorators/subscriber/autoFill.ts +28 -0
  27. package/src/core/decorators/subscriber/autoSubscribe.ts +54 -0
  28. package/src/core/decorators/subscriber/bind.ts +305 -0
  29. package/src/core/decorators/subscriber/common.ts +50 -0
  30. package/src/core/decorators/subscriber/onAssign.ts +318 -0
  31. package/src/core/mixins/Fetcher.ts +0 -0
  32. package/src/core/utils/HTML.ts +0 -0
  33. package/src/core/utils/PublisherProxy.ts +1 -1
  34. package/src/core/utils/api.ts +0 -0
  35. package/src/decorators.ts +9 -2
  36. package/src/docs/_misc/ancestor-attribute.md +94 -0
  37. package/src/docs/_misc/auto-subscribe.md +199 -0
  38. package/src/docs/_misc/bind.md +362 -0
  39. package/src/docs/_misc/on-assign.md +336 -0
  40. package/src/docs/_misc/templates-demo.md +19 -0
  41. package/src/docs/example/decorators-demo.ts +658 -0
  42. package/src/docs/navigation/navigation.ts +22 -3
  43. package/src/docs/search/docs-search.json +415 -0
  44. package/src/docs.ts +4 -0
  45. package/src/tsconfig-model.json +1 -1
  46. package/src/tsconfig.json +22 -2
  47. package/src/tsconfig.tsbuildinfo +1 -1
  48. package/vite.config.mts +0 -2
  49. package/docs/assets/index-Dgl1lJQo.js +0 -4861
  50. package/templates-test.html +0 -32
package/docs/index.html CHANGED
@@ -10,8 +10,8 @@
10
10
  <!-- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
11
  -->
12
12
  <script src="https://cdn.jsdelivr.net/npm/marked@13.0.3"></script>
13
- <script type="module" crossorigin src="./assets/index-Dgl1lJQo.js"></script>
14
- <link rel="stylesheet" crossorigin href="./assets/index-C0K6xugr.css">
13
+ <script type="module" crossorigin src="./assets/index-BTo6ly4d.js"></script>
14
+ <link rel="stylesheet" crossorigin href="./assets/index-B669R8JF.css">
15
15
  </head>
16
16
  <body class="px-6 lg:px-20">
17
17
  <sonic-theme
@@ -2,6 +2,10 @@
2
2
  The **sonic-fetch** component is used to request and store data from an API.
3
3
  Fetch extends the mixins Fetcher and [Subscriber](#docs/_core-concept/subscriber.md/subscriber)
4
4
 
5
+
6
+
7
+
8
+
5
9
  ## Basic usage
6
10
  In order to work properly the <b>sonic-fetch</b> component needs at least the following attributes.
7
11
  - **serviceURL** : A base service url. This attribute can be inherited from an ancestor.
@@ -11,6 +15,8 @@ In order to work properly the <b>sonic-fetch</b> component needs at least the fo
11
15
  - **dataProvider *(Required)*** : An ID that is used as a reference to the object storing the data returned by the API.
12
16
  This attribute can be inherited from an ancestor.
13
17
 
18
+
19
+
14
20
  <sonic-code>
15
21
  <template>
16
22
  <sonic-fetch serviceURL="https://reqres.in" endPoint="api/users?page=2" dataProvider="myDataObj"></sonic-fetch>
@@ -264,6 +264,36 @@
264
264
  <sonic-icon library="iconoir" name="clock-outline" slot="prefix"></sonic-icon>
265
265
  History
266
266
  </sonic-menu-item>
267
+ <sonic-menu-item >
268
+ <sonic-icon library="iconoir" name="bag" slot="prefix"></sonic-icon>
269
+ Orders
270
+ </sonic-menu-item> <sonic-menu-item variant="ghost">
271
+ <sonic-icon library="iconoir" name="home" slot="prefix"></sonic-icon>
272
+ Home
273
+ </sonic-menu-item>
274
+ <sonic-menu-item>
275
+ <sonic-icon library="iconoir" name="user" slot="prefix"></sonic-icon>
276
+ Profile
277
+ </sonic-menu-item>
278
+ <sonic-menu-item >
279
+ <sonic-icon library="iconoir" name="clock-outline" slot="prefix"></sonic-icon>
280
+ History
281
+ </sonic-menu-item>
282
+ <sonic-menu-item >
283
+ <sonic-icon library="iconoir" name="bag" slot="prefix"></sonic-icon>
284
+ Orders
285
+ </sonic-menu-item> <sonic-menu-item variant="ghost">
286
+ <sonic-icon library="iconoir" name="home" slot="prefix"></sonic-icon>
287
+ Home
288
+ </sonic-menu-item>
289
+ <sonic-menu-item>
290
+ <sonic-icon library="iconoir" name="user" slot="prefix"></sonic-icon>
291
+ Profile
292
+ </sonic-menu-item>
293
+ <sonic-menu-item >
294
+ <sonic-icon library="iconoir" name="clock-outline" slot="prefix"></sonic-icon>
295
+ History
296
+ </sonic-menu-item>
267
297
  <sonic-menu-item >
268
298
  <sonic-icon library="iconoir" name="bag" slot="prefix"></sonic-icon>
269
299
  Orders
@@ -276,11 +306,22 @@
276
306
  <sonic-icon library="iconoir" name="chat-bubble" slot="prefix"></sonic-icon>
277
307
  Messages
278
308
  </sonic-menu-item>
279
- <sonic-menu-item type="danger">
280
- <sonic-icon library="iconoir" name="log-out" slot="prefix"></sonic-icon>
281
- Log out
282
- </sonic-menu-item>
283
- </sonic-menu>
309
+ <sonic-menu slot="more">
310
+ <sonic-menu-item >
311
+ <sonic-icon library="iconoir" name="settings" slot="prefix"></sonic-icon>
312
+ Settings
313
+ </sonic-menu-item>
314
+ <sonic-menu-item >
315
+ <sonic-icon library="iconoir" name="chat-bubble" slot="prefix"></sonic-icon>
316
+ Messages
317
+ </sonic-menu-item>
318
+ <sonic-divider></sonic-divider>
319
+ <sonic-menu-item type="danger">
320
+ <sonic-icon library="iconoir" name="log-out" slot="prefix"></sonic-icon>
321
+ Log out
322
+ </sonic-menu-item>
323
+ </sonic-menu>
324
+
284
325
  </template>
285
326
  </sonic-code>
286
327
 
@@ -6,7 +6,6 @@
6
6
  Simple modal
7
7
  </sonic-button>
8
8
  <sonic-modal id="modalExample">
9
- <sonic-modal-close></sonic-modal-close>
10
9
  <sonic-modal-title
11
10
  >Lorem ipsum dolor sit amet <sonic-badge type="danger" size="sm">+33</sonic-badge></sonic-modal-title
12
11
  >
@@ -31,7 +30,6 @@
31
30
  <template>
32
31
  <sonic-button onclick="document.getElementById('modalText').show()"> Long text </sonic-button>
33
32
  <sonic-modal align="left" id="modalText">
34
- <sonic-modal-close></sonic-modal-close>
35
33
  <sonic-modal-title>Infos et tarifs </sonic-modal-title>
36
34
  <sonic-modal-content>
37
35
  <div class="prose">
@@ -97,7 +95,6 @@
97
95
  <template>
98
96
  <sonic-button onclick="document.getElementById('CustomWidth').show()"> Custom width </sonic-button>
99
97
  <sonic-modal maxHeight="50vh" maxWidth="90vw" width="100%" height="100%" id="CustomWidth">
100
- <sonic-modal-close></sonic-modal-close>
101
98
  <sonic-image
102
99
  cover
103
100
  objectPosition="center 20%"
@@ -113,7 +110,6 @@
113
110
  <template>
114
111
  <sonic-button onclick="document.getElementById('Fullscreen').show()"> Fullscreen </sonic-button>
115
112
  <sonic-modal fullscreen id="Fullscreen">
116
- <sonic-modal-close></sonic-modal-close>
117
113
  <sonic-image
118
114
  cover
119
115
  src="https://thegoodlife.fr/wp-content/uploads/sites/2/2022/03/compagnies-aeriennes-nostalgie-coeur-insert-03-dr.jpg"
@@ -0,0 +1,166 @@
1
+ # Toast
2
+
3
+ ## Utilisation de base
4
+
5
+ <sonic-code>
6
+ <template>
7
+ <sonic-button onclick="window.SonicToast.add({ text: 'Message de notification simple' })">
8
+ Afficher un toast
9
+ </sonic-button>
10
+ </template>
11
+ </sonic-code>
12
+
13
+ ## Statut
14
+
15
+ Les statuts disponibles sont : `success`, `error`, `warning`, `info` ou vide (par défaut).
16
+
17
+ <sonic-code>
18
+ <template>
19
+ <div class="flex flex-wrap gap-2">
20
+ <sonic-button onclick="window.SonicToast.add({ text: 'Message par défaut', status: '' })">
21
+ Default
22
+ </sonic-button>
23
+ <sonic-button onclick="window.SonicToast.add({ text: 'Opération réussie !', status: 'success' })">
24
+ Success
25
+ </sonic-button>
26
+ <sonic-button onclick="window.SonicToast.add({ text: 'Une erreur est survenue', status: 'error' })">
27
+ Error
28
+ </sonic-button>
29
+ <sonic-button onclick="window.SonicToast.add({ text: 'Attention à ce point', status: 'warning' })">
30
+ Warning
31
+ </sonic-button>
32
+ <sonic-button onclick="window.SonicToast.add({ text: 'Information importante', status: 'info' })">
33
+ Info
34
+ </sonic-button>
35
+ </div>
36
+ </template>
37
+ </sonic-code>
38
+
39
+ ## Avec titre
40
+
41
+ <sonic-code>
42
+ <template>
43
+ <div class="flex flex-wrap gap-2">
44
+ <sonic-button onclick="window.SonicToast.add({ title: 'Succès', text: 'Votre demande a été traitée avec succès.', status: 'success' })">
45
+ Toast avec titre
46
+ </sonic-button>
47
+ <sonic-button onclick="window.SonicToast.add({ title: 'Test', text: 'Une erreur est survenue lors du traitement.', status: 'error' })">
48
+ Toast d'erreur avec titre
49
+ </sonic-button>
50
+ </div>
51
+ </template>
52
+ </sonic-code>
53
+
54
+ ## Avec contenu HTML
55
+
56
+ Le contenu du toast peut contenir du HTML.
57
+
58
+ <sonic-code>
59
+ <template>
60
+ <sonic-button onclick="window.SonicToast.add({ text: 'Message avec <strong>HTML</strong> et un <a href=&quot;#&quot;>lien cliquable</a>', status: 'info' })">
61
+ Toast avec HTML
62
+ </sonic-button>
63
+ </template>
64
+ </sonic-code>
65
+
66
+
67
+ ## Persistance
68
+
69
+ Par défaut, les toasts disparaissent automatiquement. Avec `preserve: true`, le toast reste affiché jusqu'à ce qu'il soit fermé manuellement.
70
+
71
+ <sonic-code>
72
+ <template>
73
+ <sonic-button onclick="window.SonicToast.add({ text: 'Ce toast ne disparaîtra pas automatiquement', status: 'info', preserve: true })">
74
+ Toast persistant
75
+ </sonic-button>
76
+ </template>
77
+ </sonic-code>
78
+
79
+ ## Masquer définitivement
80
+
81
+ Avec `dismissForever: true` et un `id`, le toast peut être masqué définitivement. Une fois fermé, il ne réapparaîtra plus même après rechargement de la page.
82
+
83
+ <sonic-code>
84
+ <template>
85
+ <sonic-button onclick="window.SonicToast.add({ id: 'unique-toast-id', text: 'Ce toast peut être masqué définitivement', status: 'info', dismissForever: true })">
86
+ Toast avec dismiss forever
87
+ </sonic-button>
88
+ </template>
89
+ </sonic-code>
90
+
91
+ ## Fantôme
92
+
93
+ Avec `ghost: true`, le toast devient semi-transparent et non-interactif.
94
+
95
+ <sonic-code>
96
+ <template>
97
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast fantôme (semi-transparent)', status: 'info', ghost: true })">
98
+ Toast ghost
99
+ </sonic-button>
100
+ </template>
101
+ </sonic-code>
102
+
103
+
104
+ ## Méthodes de suppression
105
+
106
+ ### Tout supprimer
107
+
108
+ Supprime tous les toasts sauf ceux marqués comme `ghost`.
109
+
110
+ <sonic-code>
111
+ <template>
112
+ <div class="flex flex-wrap gap-2">
113
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast 1', status: 'info' })">Ajouter toast 1</sonic-button>
114
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast 2', status: 'success' })">Ajouter toast 2</sonic-button>
115
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast 3', status: 'warning' })">Ajouter toast 3</sonic-button>
116
+ <sonic-button onclick="window.SonicToast.removeAll()">Supprimer tous</sonic-button>
117
+ </div>
118
+ </template>
119
+ </sonic-code>
120
+
121
+ ### Supprimer par statut
122
+
123
+ Supprime tous les toasts d'un statut spécifique.
124
+
125
+ <sonic-code>
126
+ <template>
127
+ <div class="flex flex-wrap gap-2">
128
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast success', status: 'success' })">Success</sonic-button>
129
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast error', status: 'error' })">Error</sonic-button>
130
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast warning', status: 'warning' })">Warning</sonic-button>
131
+ <sonic-button onclick="window.SonicToast.removeItemsByStatus('success')">Supprimer success</sonic-button>
132
+ <sonic-button onclick="window.SonicToast.removeItemsByStatus('error')">Supprimer error</sonic-button>
133
+ </div>
134
+ </template>
135
+ </sonic-code>
136
+
137
+ ### Supprimer les éléments temporaires
138
+
139
+ Supprime tous les toasts qui ne sont pas marqués comme `preserve`.
140
+
141
+ <sonic-code>
142
+ <template>
143
+ <div class="flex flex-wrap gap-2">
144
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast temporaire', status: 'info' })">Temporaire</sonic-button>
145
+ <sonic-button onclick="window.SonicToast.add({ text: 'Toast persistant', status: 'success', preserve: true })">Persistant</sonic-button>
146
+ <sonic-button onclick="window.SonicToast.removeTemporaryItems()">Supprimer temporaires</sonic-button>
147
+ </div>
148
+ </template>
149
+ </sonic-code>
150
+
151
+ ## Exemple complet
152
+
153
+ <sonic-code>
154
+ <template>
155
+ <sonic-button onclick="window.SonicToast.add({
156
+ id: 'welcome-toast',
157
+ title: 'Bienvenue',
158
+ text: 'Bienvenue sur notre plateforme ! Vous pouvez commencer à explorer les fonctionnalités.',
159
+ status: 'success',
160
+ dismissForever: true
161
+ })">
162
+ Toast complet
163
+ </sonic-button>
164
+ </template>
165
+ </sonic-code>
166
+
@@ -0,0 +1,94 @@
1
+ # @ancestorAttribute
2
+
3
+ The `@ancestorAttribute` decorator automatically injects the value of an ancestor's attribute into a class property at the time of `connectedCallback`.
4
+
5
+ ## Principle
6
+
7
+ This decorator uses `HTML.getAncestorAttributeValue` to traverse up the DOM tree from the current element and find the first ancestor that has the specified attribute. The value of this attribute is then assigned to the decorated property.
8
+
9
+ ## Usage
10
+
11
+ ### Import
12
+
13
+ <sonic-code language="typescript">
14
+ <template>
15
+ import { ancestorAttribute } from "@supersoniks/concorde/decorators";
16
+ </template>
17
+ </sonic-code>
18
+
19
+ ### Basic example
20
+ Le composant lit les attributs `dataProvider` et `testAttribute` exposés par son conteneur ancêtre.
21
+
22
+ <sonic-code language="typescript">
23
+ <template>
24
+ //...
25
+ @customElement("demo-bind-reflect")
26
+ export class DemoBindReflect extends LitElement {
27
+ static styles = [tailwind];
28
+ //
29
+ @bind("bindReflectDemo.count", { reflect: true })
30
+ @state()
31
+ withReflect: number = 0;
32
+ //
33
+ @bind("bindReflectDemo.count")
34
+ @state()
35
+ withoutReflect: number = 0;
36
+ // initialize the publisher data
37
+ connectedCallback() {
38
+ super.connectedCallback();
39
+ this.resetData();
40
+ }
41
+ //
42
+ resetData() {
43
+ PublisherManager.get("bindReflectDemo").set({ count: 0 });
44
+ }
45
+ render() {
46
+ return html`
47
+ <div class="mb-3">
48
+ from publisher : ${sub("bindReflectDemo.count")} <br />
49
+ from component with reflect : ${this.withReflect} <br />
50
+ from component without reflect : ${this.withoutReflect}
51
+ </div>
52
+ <sonic-button @click=${() => this.withReflect++}
53
+ >Increment with reflect</sonic-button
54
+ >
55
+ <sonic-button @click=${() => this.withoutReflect++}
56
+ >Increment without reflect</sonic-button
57
+ >
58
+ <sonic-button @click=${this.resetData}>Reset publisher data</sonic-button>
59
+ `;
60
+ }
61
+ }
62
+ </template>
63
+ </sonic-code>
64
+
65
+ <sonic-code>
66
+ <template>
67
+ <div dataProvider="demoDataProvider" testAttribute="test-value-123">
68
+ <demo-ancestor-attribute></demo-ancestor-attribute>
69
+ </div>
70
+ </template>
71
+ </sonic-code>
72
+
73
+
74
+ ## Use cases
75
+
76
+ This decorator is particularly useful for:
77
+
78
+ - **Retrieving the `dataProvider`** from an ancestor without having to pass it explicitly
79
+ - **Retrieving the `formDataProvider`** in form components
80
+ - **Retrieving the `wordingProvider`** for translation
81
+ - **Retrieving any other attribute** defined on an ancestor
82
+
83
+ ## Behavior
84
+
85
+ - The search starts from the current element and traverses up the DOM tree
86
+ - If the attribute is not found, the property will be assigned `null`
87
+ - The injection happens automatically at the time of `connectedCallback`
88
+ - The value is not reactive: it is only updated once when the element is connected to the DOM
89
+
90
+ ## Notes
91
+
92
+ - This decorator works with any component that has a `connectedCallback` method (such as `LitElement` or components extending `Subscriber`)
93
+ - The search also traverses Shadow DOM if necessary
94
+ - If multiple ancestors have the attribute, the closest one will be used
@@ -0,0 +1,199 @@
1
+ # @autoSubscribe
2
+
3
+ The `@autoSubscribe` decorator automatically detects which publishers are accessed within a method and subscribes to them. When any of these publishers change, the method is automatically re-executed.
4
+
5
+ ## Principle
6
+
7
+ This decorator wraps a method to track which publishers are accessed during its execution. It then subscribes to all accessed publishers, and when any of them change, the method is re-executed. This provides automatic reactivity without manually managing subscriptions.
8
+
9
+ ## Usage
10
+
11
+ ### Import
12
+
13
+ <sonic-code language="typescript">
14
+ <template>
15
+ import { autoSubscribe } from "@supersoniks/concorde/decorators";
16
+ </template>
17
+ </sonic-code>
18
+
19
+ ### Basic example
20
+
21
+
22
+ <sonic-code language="typescript">
23
+ <template>
24
+ @customElement("demo-auto-subscribe")
25
+ export class DemoAutoSubscribe extends LitElement {
26
+ static styles = [tailwind];
27
+ //
28
+ @state() displayText: string = "";
29
+ @state() computedValue: number = 0;
30
+ //
31
+ @autoSubscribe()
32
+ updateDisplay() {
33
+ const value1 = PublisherManager.get("autoValue1").get() || 0;
34
+ const value2 = PublisherManager.get("autoValue2").get() || 0;
35
+ this.computedValue = value1 + value2;
36
+ this.displayText = `${value1} + ${value2} = ${this.computedValue}`;
37
+ }
38
+ //
39
+ render() {
40
+ return html`
41
+ <p><strong>${this.displayText}</strong></p>
42
+ <div>
43
+ <sonic-button @click=${() => this.randomizeValue("autoValue1")}>
44
+ Randomize Value 1
45
+ </sonic-button>
46
+ <sonic-button @click=${() => this.randomizeValue("autoValue2")}>
47
+ Randomize Value 2
48
+ </sonic-button>
49
+ </div>
50
+ `;
51
+ }
52
+ //
53
+ randomizeValue(publisherId: string) {
54
+ const value = PublisherManager.get(publisherId);
55
+ value.set(Math.floor(Math.random() * 100));
56
+ }
57
+ }
58
+ </template>
59
+ </sonic-code>
60
+
61
+
62
+ <sonic-code >
63
+ <template>
64
+ <demo-auto-subscribe></demo-auto-subscribe>
65
+ </template>
66
+ </sonic-code>
67
+
68
+ ### Example with render method
69
+
70
+ <sonic-code language="typescript">
71
+ <template>
72
+ @customElement("reactive-view")
73
+ export class ReactiveView extends LitElement {
74
+ @autoSubscribe()
75
+ render() {
76
+ const data = PublisherManager.get("myData");
77
+ const config = PublisherManager.get("config");
78
+ //
79
+ // This render method will be automatically re-executed
80
+ // when myData or config change
81
+ const value = data.get()?.value || 0;
82
+ const multiplier = config.get()?.multiplier || 1;
83
+ //
84
+ return html`
85
+ <div>
86
+ <h1>Result: ${value * multiplier}</h1>
87
+ <p>Value: ${value}</p>
88
+ <p>Multiplier: ${multiplier}</p>
89
+ </div>
90
+ `;
91
+ }
92
+ }
93
+ </template>
94
+ </sonic-code>
95
+
96
+ ## How it works
97
+
98
+ 1. **First execution**: When the method is called, `PublisherManager.collectModifiedPublisher()` is used to track which publishers are accessed
99
+ 2. **Subscription**: After execution, the decorator subscribes to all detected publishers
100
+ 3. **Re-execution**: When any subscribed publisher changes, the method is automatically called again
101
+ 4. **Cleanup**: On `disconnectedCallback`, all subscriptions are automatically removed
102
+
103
+ ## Behavior
104
+
105
+ - The method is automatically called on `connectedCallback`
106
+ - The method is re-executed whenever any accessed publisher changes
107
+ - Subscriptions are managed automatically (no manual cleanup needed)
108
+ - Only publishers accessed during method execution are subscribed to
109
+ - The decorator uses `queueMicrotask` to batch multiple updates and avoid unnecessary re-renders
110
+
111
+ ## Use cases
112
+
113
+ This decorator is particularly useful for:
114
+
115
+ - **Reactive rendering** where the render method depends on multiple publishers
116
+ - **Data transformation** that needs to update when source data changes
117
+ - **Computed properties** that depend on multiple data sources
118
+ - **Automatic synchronization** between publishers and component state
119
+
120
+ ## Complete example
121
+
122
+ <sonic-code language="typescript">
123
+ <template>
124
+ import { html, LitElement } from "lit";
125
+ import { customElement } from "lit/decorators.js";
126
+ import { autoSubscribe } from "@supersoniks/concorde/decorators";
127
+ import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
128
+ //
129
+ @customElement("shopping-cart")
130
+ export class ShoppingCart extends LitElement {
131
+ items: any[] = [];
132
+ total: number = 0;
133
+ discount: number = 0;
134
+ //
135
+ @autoSubscribe()
136
+ calculateTotal() {
137
+ const cart = PublisherManager.get("cart");
138
+ const promo = PublisherManager.get("promo");
139
+ //
140
+ // Access cart items
141
+ this.items = cart.items.get() || [];
142
+ //
143
+ // Access promo code
144
+ const promoCode = promo.code.get() || "";
145
+ const discountPercent = promoCode === "SAVE10" ? 0.1 : 0;
146
+ //
147
+ // Calculate totals
148
+ const subtotal = this.items.reduce((sum, item) =>
149
+ sum + (item.price * item.quantity), 0
150
+ );
151
+ this.discount = subtotal * discountPercent;
152
+ this.total = subtotal - this.discount;
153
+ //
154
+ this.requestUpdate();
155
+ }
156
+ //
157
+ connectedCallback() {
158
+ super.connectedCallback();
159
+ this.calculateTotal();
160
+ }
161
+ //
162
+ render() {
163
+ return html`
164
+ <div class="cart">
165
+ <h2>Shopping Cart</h2>
166
+ ${this.items.map(item => html`
167
+ <div>${item.name} x${item.quantity} - ${item.price}€</div>
168
+ `)}
169
+ <div class="total">
170
+ <p>Subtotal: ${this.total + this.discount}€</p>
171
+ <p>Discount: -${this.discount}€</p>
172
+ <p><strong>Total: ${this.total}€</strong></p>
173
+ </div>
174
+ </div>
175
+ `;
176
+ }
177
+ }
178
+ //
179
+ // When you update the publishers, calculateTotal is automatically called:
180
+ const cart = PublisherManager.get("cart");
181
+ cart.items.set([
182
+ { name: "Product 1", price: 10, quantity: 2 },
183
+ { name: "Product 2", price: 15, quantity: 1 }
184
+ ]);
185
+ //
186
+ const promo = PublisherManager.get("promo");
187
+ promo.code.set("SAVE10");
188
+ // calculateTotal will be automatically called and the UI will update
189
+ </template>
190
+ </sonic-code>
191
+
192
+ ## Notes
193
+
194
+ - This decorator works with any component that has `connectedCallback` and `disconnectedCallback` methods (such as `LitElement` or components extending `Subscriber`)
195
+ - The method is called automatically on `connectedCallback`
196
+ - Remember to call `this.requestUpdate()` if you're updating component properties
197
+ - The decorator uses debouncing via `queueMicrotask` to prevent excessive re-executions
198
+ - For more information about publishers, see the documentation on [Sharing data](#docs/_getting-started/pubsub.md/pubsub)
199
+