@altazion/commerce-sdk-htmx 26.409.7573 → 26.415.7673

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,10 @@ initAltazionHtmx({
24
24
  client,
25
25
  handlebars: Handlebars, // optionnel — active les helpers HBS
26
26
  offlineSelector: '#app', // optionnel — élément qui reçoit la classe altz-offline
27
+ terminalMode: {
28
+ interactiveSelectors: ['#app-main', '.altz-action-bar'],
29
+ offlineScreenSelectors: '#offline-screen',
30
+ },
27
31
  })
28
32
  ```
29
33
 
@@ -79,21 +83,343 @@ import { templates } from '@altazion/commerce-sdk-htmx'
79
83
  // templates.productList — liste de produits
80
84
  ```
81
85
 
82
- ## Extension d'authentification HTMX
86
+ ## Extension HTMX Altazion
83
87
 
84
- L'initialisation enregistre automatiquement une extension HTMX `altazion-auth` qui injecte les headers de session sur toutes les requêtes HTMX vers le domaine de l'API :
88
+ L'initialisation enregistre automatiquement une extension HTMX `altazion` qui :
89
+
90
+ - injecte les headers de session sur les requêtes HTMX ;
91
+ - interprète les attributs `hx-altazion-cart-action` pour appeler directement `client.cart.*` du SDK core ;
92
+ - interprète les attributs `hx-altazion-session-action` pour appeler directement `client.session.*` du SDK core ;
93
+ - interprète les attributs `hx-altazion-marketing-action` pour appeler directement `client.marketing.*` du SDK core ;
94
+ - interprète les attributs `hx-altazion-stores-action` pour appeler directement `client.stores.*` du SDK core ;
95
+ - émet des événements DOM `altazion:cart:*` exploitables par HTMX pour recharger des fragments.
96
+
97
+ ### Convention de nommage
98
+
99
+ La convention officielle du DSL HTMX Altazion est la suivante :
100
+
101
+ - activation : `hx-ext="altazion"` ;
102
+ - action métier : `hx-altazion-<module>-action` ;
103
+ - paramètres simples : `hx-altazion-<module>-<parametre-kebab-case>` ;
104
+ - payload riche : formulaire ou `hx-vals` ;
105
+ - refresh partagé : `hx-altazion-refresh` ;
106
+ - événements : `altazion:<module>:before|after|error`, avec des événements complémentaires selon le module (`updated`, `loaded`, `changed`, etc.).
107
+
108
+ La convention détaillée est décrite dans [specs-temp/sdk.htmx-naming-convention.md](specs-temp/sdk.htmx-naming-convention.md).
109
+
110
+ ### Exemple addItem
85
111
 
86
112
  ```html
87
- <div hx-ext="altazion-auth">
88
- <button hx-post="/api/cart/add" hx-vals='{"ref":"REF-001","qty":1}'>
113
+ <div hx-ext="altazion">
114
+ <button
115
+ hx-altazion-cart-action="addItem"
116
+ hx-vals='{"reference":"REF-001","quantity":1}'
117
+ hx-altazion-refresh="#cart-mini; #cart-badge"
118
+ >
89
119
  Ajouter au panier
90
120
  </button>
121
+
122
+ <aside
123
+ id="cart-mini"
124
+ hx-get="/fragments/cart-mini"
125
+ hx-swap="outerHTML"
126
+ ></aside>
91
127
  </div>
92
128
  ```
93
129
 
130
+ ### Actions supportées
131
+
132
+ | Action | Méthode SDK | Payload attendu |
133
+ |---|---|---|
134
+ | `getCart` | `client.cart.getCart()` | aucun |
135
+ | `getValidationStatus` | `client.cart.getValidationStatus()` | aucun |
136
+ | `addItem` | `client.cart.addItem(reference, quantity, options?)` | `reference`, `quantity` |
137
+ | `updateItem` | `client.cart.updateItem(lineId, quantity)` | `lineId`, `quantity` |
138
+ | `removeItem` | `client.cart.removeItem(lineId)` | `lineId` |
139
+ | `applyCoupon` | `client.cart.applyCoupon(code)` | `code` |
140
+ | `removeCoupon` | `client.cart.removeCoupon(code)` | `code` |
141
+
142
+ Le payload peut être fourni via `hx-vals`, via les champs d'un formulaire, ou avec les attributs dédiés `hx-altazion-cart-reference`, `hx-altazion-cart-quantity`, `hx-altazion-cart-line-id`, `hx-altazion-cart-code`.
143
+
144
+ ### Session: lecture de la session courante
145
+
146
+ Pour les sites en server rendering, la session peut déjà exister côté serveur et être portée par cookie. Le premier besoin côté HTMX est donc souvent simplement de relire cet état pour rafraîchir des fragments.
147
+
148
+ ```html
149
+ <section
150
+ hx-ext="altazion"
151
+ hx-altazion-session-action="getSession"
152
+ hx-trigger="load, altazion:session:refresh from:body"
153
+ hx-altazion-refresh="#header-session; #mini-cart"
154
+ >
155
+ </section>
156
+
157
+ <header id="header-session" hx-get="/fragments/header-session" hx-swap="outerHTML"></header>
158
+ <aside id="mini-cart" hx-get="/fragments/cart-mini" hx-swap="outerHTML"></aside>
159
+ ```
160
+
161
+ Action supportée pour l'instant :
162
+
163
+ | Action | Méthode SDK | Payload attendu |
164
+ |---|---|---|
165
+ | `getSession` | `client.session.getSession()` | aucun |
166
+
167
+ Événements émis :
168
+
169
+ | Événement | Description |
170
+ |---|---|
171
+ | `altazion:session:before` | avant l'appel au SDK |
172
+ | `altazion:session:after` | après un appel réussi |
173
+ | `altazion:session:loaded` | après `getSession` |
174
+ | `altazion:session:error` | en cas d'erreur |
175
+
176
+ ### Rendu déclaratif inline
177
+
178
+ Quand `handlebars` est fourni à `initAltazionHtmx`, le package sait aussi piloter un rendu inline déclaratif dans le DOM.
179
+
180
+ Le pattern visé est :
181
+
182
+ ```html
183
+ <section
184
+ hx-ext="altazion"
185
+ hx-altazion-marketing-action="getItems"
186
+ hx-altazion-marketing-codes='["HOMEPAGEHERO"]'
187
+ hx-altazion-data-slot="homepageHero"
188
+ ></section>
189
+
190
+ <aside id="store-detail" class="panel">
191
+ <p
192
+ hx-altazion-data-source="slot:homepageHero"
193
+ hx-altazion-data-status="null"
194
+ class="placeholder"
195
+ >
196
+ Aucun contenu marketing chargé.
197
+ </p>
198
+
199
+ <div
200
+ hx-altazion-data-source="slot:homepageHero"
201
+ hx-altazion-data-status="not-empty"
202
+ hx-render-template="homepage-hero-template"
203
+ ></div>
204
+ </aside>
205
+
206
+ <script id="homepage-hero-template" type="text/x-handlebars-template">
207
+ {{#each slot}}
208
+ <article>
209
+ <h2>{{linkItem.altData}}</h2>
210
+ </article>
211
+ {{/each}}
212
+ </script>
213
+ ```
214
+
215
+ Attributs actuellement pris en charge :
216
+
217
+ - `hx-altazion-data-source="cart|session|store|stores|marketing-item|marketing-items"`
218
+ - `hx-altazion-data-source="slot:nom-libre"`
219
+ - `hx-altazion-data-status="null|not-null|empty|not-empty"`
220
+ - `hx-altazion-data-slot="nom-libre"` sur un élément d'action Altazion pour ranger son résultat dans un slot nommé
221
+ - `hx-render-template="template-id"`
222
+
223
+ Le contexte Handlebars exposé au template contient aujourd'hui : `cart`, `session`, `store`, `stores`, `marketingItem`, `marketingItems`, `slots` et `slot`.
224
+
225
+ ### Marketing: items de widgets et d'animation
226
+
227
+ Le module `marketing` sert bien au cas d'usage widgets de page: récupérer un item marketing isolé ou un lot d'items, puis laisser HTMX rafraîchir les fragments de rendu serveur associés.
228
+
229
+ ```html
230
+ <section
231
+ hx-ext="altazion"
232
+ hx-altazion-marketing-action="getItems"
233
+ hx-altazion-marketing-codes='["home-hero", "home-sidebar", "home-footer"]'
234
+ hx-trigger="load, altazion:marketing:refresh from:body"
235
+ hx-altazion-refresh="#widget-hero; #widget-sidebar; #widget-footer"
236
+ >
237
+ </section>
238
+
239
+ <section id="widget-hero" hx-get="/fragments/widgets/hero" hx-swap="outerHTML"></section>
240
+ <aside id="widget-sidebar" hx-get="/fragments/widgets/sidebar" hx-swap="outerHTML"></aside>
241
+ <footer id="widget-footer" hx-get="/fragments/widgets/footer" hx-swap="outerHTML"></footer>
242
+ ```
243
+
244
+ Actions supportées :
245
+
246
+ | Action | Méthode SDK | Payload attendu |
247
+ |---|---|---|
248
+ | `getItem` | `client.marketing.getItem(code)` | `code` |
249
+ | `getItems` | `client.marketing.getItems(codes)` | `codes` |
250
+
251
+ Le payload peut être fourni via `hx-vals`, via un formulaire, ou avec :
252
+
253
+ - `hx-altazion-marketing-code` pour un item unique ;
254
+ - `hx-altazion-marketing-codes` pour une liste JSON de codes ;
255
+ - une chaîne séparée par des virgules dans `codes` si c'est plus simple côté template.
256
+
257
+ Événements émis :
258
+
259
+ | Événement | Description |
260
+ |---|---|
261
+ | `altazion:marketing:before` | avant l'appel au SDK |
262
+ | `altazion:marketing:after` | après un appel réussi |
263
+ | `altazion:marketing:loaded` | après une lecture réussie |
264
+ | `altazion:marketing:item-loaded` | après `getItem` |
265
+ | `altazion:marketing:items-loaded` | après `getItems` |
266
+ | `altazion:marketing:error` | en cas d'erreur |
267
+
268
+ ### Stores: store locator et détail magasin
269
+
270
+ Le domaine `stores` est un bon candidat HTMX parce qu'il est purement orienté lecture pour l'instant: localiser une liste de magasins ou charger la fiche d'un magasin pour un widget, une modale ou une page de retrait.
271
+
272
+ ```html
273
+ <form
274
+ hx-ext="altazion"
275
+ hx-altazion-stores-action="findByPostalCode"
276
+ hx-trigger="submit"
277
+ hx-altazion-refresh="#store-results"
278
+ >
279
+ <input type="text" name="postalCode" value="75002">
280
+ <input type="hidden" name="countryCode" value="FR">
281
+ <button type="submit">Chercher un magasin</button>
282
+ </form>
283
+
284
+ <section id="store-results" hx-get="/fragments/stores/results" hx-swap="outerHTML"></section>
285
+ ```
286
+
287
+ ```html
288
+ <button
289
+ hx-ext="altazion"
290
+ hx-altazion-stores-action="getStore"
291
+ hx-altazion-stores-store-guid="STORE-GUID-001"
292
+ hx-altazion-refresh="#store-detail"
293
+ >
294
+ Voir ce magasin
295
+ </button>
296
+
297
+ <aside id="store-detail" hx-get="/fragments/stores/detail" hx-swap="outerHTML"></aside>
298
+ ```
299
+
300
+ Actions supportées :
301
+
302
+ | Action | Méthode SDK | Payload attendu |
303
+ |---|---|---|
304
+ | `findByLocation` | `client.stores.findByLocation(latitude, longitude, radiusKm?)` | `latitude`, `longitude` |
305
+ | `findByPostalCode` | `client.stores.findByPostalCode(postalCode, countryCode?)` | `postalCode` |
306
+ | `getStore` | `client.stores.getStore(storeGuid)` | `storeGuid` |
307
+
308
+ Paramètres HTML pris en charge :
309
+
310
+ - `hx-altazion-stores-latitude`
311
+ - `hx-altazion-stores-longitude`
312
+ - `hx-altazion-stores-radius-km`
313
+ - `hx-altazion-stores-postal-code`
314
+ - `hx-altazion-stores-country-code`
315
+ - `hx-altazion-stores-store-guid`
316
+
317
+ Événements émis :
318
+
319
+ | Événement | Description |
320
+ |---|---|
321
+ | `altazion:stores:before` | avant l'appel au SDK |
322
+ | `altazion:stores:after` | après un appel réussi |
323
+ | `altazion:stores:loaded` | après une lecture réussie |
324
+ | `altazion:stores:stores-loaded` | après une recherche de magasins |
325
+ | `altazion:stores:store-loaded` | après `getStore` |
326
+ | `altazion:stores:error` | en cas d'erreur |
327
+
328
+ ### Rafraîchissement déclaratif
329
+
330
+ L'attribut `hx-altazion-refresh` accepte une ou plusieurs cibles CSS séparées par `;`.
331
+
332
+ - si la cible porte un attribut `hx-get`, `hx-post`, `hx-put`, `hx-patch` ou `hx-delete`, l'extension force immédiatement la requête HTMX correspondante ;
333
+ - sinon l'extension émet `altazion:refresh` sur la cible, ce qui laisse la main à du rendu client ou à un autre binding HTMX.
334
+
335
+ ```html
336
+ <button
337
+ hx-ext="altazion"
338
+ hx-altazion-cart-action="applyCoupon"
339
+ hx-altazion-cart-code="WELCOME10"
340
+ hx-altazion-refresh="#cart-mini; #cart-totals"
341
+ >
342
+ Appliquer le coupon
343
+ </button>
344
+
345
+ <section id="cart-totals" hx-get="/fragments/cart-totals" hx-swap="outerHTML"></section>
346
+ ```
347
+
348
+ ### Événements émis
349
+
350
+ | Événement | Description |
351
+ |---|---|
352
+ | `altazion:cart:before` | avant l'appel au SDK |
353
+ | `altazion:cart:after` | après un appel réussi |
354
+ | `altazion:cart:loaded` | après une lecture réussie, par exemple `getCart` |
355
+ | `altazion:cart:updated` | après une action retournant un panier |
356
+ | `altazion:cart:changed` | après une action réussie sans payload de retour |
357
+ | `altazion:cart:status` | après `getValidationStatus` |
358
+ | `altazion:cart:error` | en cas d'erreur, avec `detail.errorDetail` normalisé |
359
+ | `altazion:refresh` | fallback émis sur une cible sans requête HTMX |
360
+
361
+ Le moteur HTMX générique applique désormais une sémantique commune :
362
+
363
+ - `loaded` pour les actions de lecture ;
364
+ - `updated` pour les mutations avec résultat métier ;
365
+ - `changed` pour les mutations qui réussissent sans retourner de payload ;
366
+ - `error` avec un objet `detail.errorDetail` structuré : `name`, `message`, `status`, `problem`, `isOffline`, `isConflict`.
367
+
94
368
  ## Détection offline
95
369
 
96
- Lorsque la connexion est perdue, la classe CSS `altz-offline` est ajoutée sur l'élément ciblé par `offlineSelector` (par défaut `body`), permettant d'afficher un bandeau ou de masquer des actions :
370
+ Lorsque la connexion est perdue, l'élément ciblé par `offlineSelector` (par défaut `body`) reçoit :
371
+
372
+ - la classe `altz-offline` ;
373
+ - la classe `altz-terminal-offline` ou `altz-terminal-online` ;
374
+ - l'attribut `data-altazion-terminal-state="offline|online"` ;
375
+ - les événements `altazion:connectivity:changed`, `altazion:offline` et `altazion:online`.
376
+
377
+ Le mode borne peut aussi masquer des zones interactives et afficher un écran passif via `terminalMode.interactiveSelectors` et `terminalMode.offlineScreenSelectors`.
378
+
379
+ ### Exemple de bascule borne
380
+
381
+ ```html
382
+ <body>
383
+ <main id="app-main">
384
+ <!-- interface interactive -->
385
+ </main>
386
+
387
+ <aside id="offline-screen" hidden>
388
+ <!-- pub, branding, message d'attente -->
389
+ </aside>
390
+ </body>
391
+ ```
392
+
393
+ ```css
394
+ body.altz-terminal-offline {
395
+ background: #111;
396
+ color: #fff;
397
+ }
398
+
399
+ #offline-screen[hidden] {
400
+ display: none;
401
+ }
402
+ ```
403
+
404
+ L'écran offline est affiché automatiquement quand la borne passe hors ligne, et les zones interactives ciblées sont masquées.
405
+
406
+ ### Exemple d'écoute des événements
407
+
408
+ ```ts
409
+ document.body.addEventListener('altazion:offline', () => {
410
+ console.log('Le terminal est hors ligne')
411
+ })
412
+
413
+ document.body.addEventListener('altazion:online', () => {
414
+ console.log('Le terminal est de nouveau en ligne')
415
+ })
416
+ ```
417
+
418
+ Les mutations métier continuent d'échouer immédiatement hors ligne avec `OfflineError`. Le mode offline borne ne réintroduit aucune synchronisation différée.
419
+
420
+ ### Exemple CSS minimal
421
+
422
+ Lorsque seule l'indication visuelle suffit, la classe `altz-offline` permet d'afficher un bandeau ou de masquer des actions :
97
423
 
98
424
  ```css
99
425
  body.altz-offline .altz-cart-actions {