@altazion/commerce-sdk-htmx 26.407.7565 → 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 +331 -5
- package/dist/index.cjs +965 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +82 -7
- package/dist/index.iife.js +966 -142
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +965 -140
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
86
|
+
## Extension HTMX Altazion
|
|
83
87
|
|
|
84
|
-
L'initialisation enregistre automatiquement une extension HTMX `altazion
|
|
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
|
|
88
|
-
<button
|
|
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,
|
|
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 {
|