@altazion/commerce-sdk-htmx 1.0.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.
- package/dist/index.cjs +322 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.iife.js +325 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.js +322 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function registerAltazionAuthExtension(client) {
|
|
4
|
+
if (typeof window === "undefined" || !window.htmx) {
|
|
5
|
+
console.warn("[AltazionHtmx] htmx non disponible sur window — extension non enregistrée");
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const handler = (event) => {
|
|
9
|
+
const e = event;
|
|
10
|
+
e.detail.headers["Accept-Language"] = client.context.locale;
|
|
11
|
+
e.detail.headers["X-Altazion-Currency"] = client.context.currency;
|
|
12
|
+
};
|
|
13
|
+
window.htmx.on("htmx:configRequest", handler);
|
|
14
|
+
}
|
|
15
|
+
function registerPriceHelpers(hbs, defaults) {
|
|
16
|
+
hbs.registerHelper(
|
|
17
|
+
"formatPrice",
|
|
18
|
+
function(value, currency, locale) {
|
|
19
|
+
const amount = typeof value === "number" ? value : parseFloat(String(value));
|
|
20
|
+
if (isNaN(amount)) return "";
|
|
21
|
+
const resolvedCurrency = typeof currency === "string" ? currency : defaults.currency;
|
|
22
|
+
const resolvedLocale = typeof locale === "string" ? locale : defaults.locale;
|
|
23
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
24
|
+
style: "currency",
|
|
25
|
+
currency: resolvedCurrency
|
|
26
|
+
}).format(amount);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
hbs.registerHelper(
|
|
30
|
+
"formatPriceCompact",
|
|
31
|
+
function(value, currency, locale) {
|
|
32
|
+
const amount = typeof value === "number" ? value : parseFloat(String(value));
|
|
33
|
+
if (isNaN(amount)) return "";
|
|
34
|
+
const resolvedCurrency = typeof currency === "string" ? currency : defaults.currency;
|
|
35
|
+
const resolvedLocale = typeof locale === "string" ? locale : defaults.locale;
|
|
36
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
37
|
+
style: "currency",
|
|
38
|
+
currency: resolvedCurrency,
|
|
39
|
+
notation: "compact"
|
|
40
|
+
}).format(amount);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
hbs.registerHelper(
|
|
44
|
+
"discountPercent",
|
|
45
|
+
function(original, discounted) {
|
|
46
|
+
const orig = typeof original === "number" ? original : parseFloat(String(original));
|
|
47
|
+
const disc = typeof discounted === "number" ? discounted : parseFloat(String(discounted));
|
|
48
|
+
if (isNaN(orig) || isNaN(disc) || orig === 0) return "";
|
|
49
|
+
const pct = Math.round((orig - disc) / orig * 100);
|
|
50
|
+
return `-${pct}%`;
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
function registerProductHelpers(hbs, siteUrl) {
|
|
55
|
+
hbs.registerHelper("productUrl", function(reference) {
|
|
56
|
+
if (typeof reference !== "string" && typeof reference !== "number") return "";
|
|
57
|
+
const base = siteUrl.replace(/\/$/, "");
|
|
58
|
+
return `${base}/product/${encodeURIComponent(String(reference))}`;
|
|
59
|
+
});
|
|
60
|
+
hbs.registerHelper("thumbnailUrl", function(imageUrl, size) {
|
|
61
|
+
if (typeof imageUrl !== "string" || !imageUrl) return "";
|
|
62
|
+
const resolvedSize = typeof size === "number" ? size : 300;
|
|
63
|
+
const separator = imageUrl.includes("?") ? "&" : "?";
|
|
64
|
+
return `${imageUrl}${separator}w=${resolvedSize}`;
|
|
65
|
+
});
|
|
66
|
+
hbs.registerHelper("isAvailable", function(availability) {
|
|
67
|
+
if (availability === null || availability === void 0) return true;
|
|
68
|
+
if (typeof availability === "boolean") return availability;
|
|
69
|
+
if (typeof availability === "number") return availability > 0;
|
|
70
|
+
if (typeof availability === "string") {
|
|
71
|
+
const lower = availability.toLowerCase();
|
|
72
|
+
return lower === "available" || lower === "instock";
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
});
|
|
76
|
+
hbs.registerHelper(
|
|
77
|
+
"ifCond",
|
|
78
|
+
function(v1, operator, v2, options) {
|
|
79
|
+
let result = false;
|
|
80
|
+
switch (operator) {
|
|
81
|
+
case "==":
|
|
82
|
+
result = v1 == v2;
|
|
83
|
+
break;
|
|
84
|
+
case "===":
|
|
85
|
+
result = v1 === v2;
|
|
86
|
+
break;
|
|
87
|
+
case "!=":
|
|
88
|
+
result = v1 != v2;
|
|
89
|
+
break;
|
|
90
|
+
case "!==":
|
|
91
|
+
result = v1 !== v2;
|
|
92
|
+
break;
|
|
93
|
+
case ">":
|
|
94
|
+
result = v1 > v2;
|
|
95
|
+
break;
|
|
96
|
+
case "<":
|
|
97
|
+
result = v1 < v2;
|
|
98
|
+
break;
|
|
99
|
+
case ">=":
|
|
100
|
+
result = v1 >= v2;
|
|
101
|
+
break;
|
|
102
|
+
case "<=":
|
|
103
|
+
result = v1 <= v2;
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
result = false;
|
|
107
|
+
}
|
|
108
|
+
return result ? options.fn(this) : options.inverse(this);
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function registerCartHelpers(hbs, getCart, defaults) {
|
|
113
|
+
hbs.registerHelper("cartCount", function() {
|
|
114
|
+
const cart = getCart();
|
|
115
|
+
if (!cart) return 0;
|
|
116
|
+
return cart.totalQuantity;
|
|
117
|
+
});
|
|
118
|
+
hbs.registerHelper("cartTotal", function() {
|
|
119
|
+
const cart = getCart();
|
|
120
|
+
if (!cart) return "";
|
|
121
|
+
return new Intl.NumberFormat(defaults.locale, {
|
|
122
|
+
style: "currency",
|
|
123
|
+
currency: defaults.currency
|
|
124
|
+
}).format(cart.totalAmountWithTax);
|
|
125
|
+
});
|
|
126
|
+
hbs.registerHelper("cartTotalRaw", function() {
|
|
127
|
+
const cart = getCart();
|
|
128
|
+
return (cart == null ? void 0 : cart.totalAmountWithTax) ?? 0;
|
|
129
|
+
});
|
|
130
|
+
hbs.registerHelper("cartHasItems", function(options) {
|
|
131
|
+
const cart = getCart();
|
|
132
|
+
const hasItems = cart ? cart.totalQuantity > 0 : false;
|
|
133
|
+
return hasItems ? options.fn(this) : options.inverse(this);
|
|
134
|
+
});
|
|
135
|
+
hbs.registerHelper("cartLineCount", function() {
|
|
136
|
+
const cart = getCart();
|
|
137
|
+
if (!(cart == null ? void 0 : cart.content)) return 0;
|
|
138
|
+
return cart.content.reduce((sum, group) => {
|
|
139
|
+
var _a;
|
|
140
|
+
return sum + (((_a = group.lines) == null ? void 0 : _a.length) ?? 0);
|
|
141
|
+
}, 0);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const productCardTemplate = `<article class="altz-product-card{{#unless (isAvailable availability)}} altz-product-card--unavailable{{/unless}}">
|
|
145
|
+
{{#if imageUrl}}
|
|
146
|
+
<a href="{{productUrl reference}}" class="altz-product-card__image-link" aria-label="{{name}}">
|
|
147
|
+
<img
|
|
148
|
+
src="{{thumbnailUrl imageUrl 400}}"
|
|
149
|
+
alt="{{name}}"
|
|
150
|
+
class="altz-product-card__image"
|
|
151
|
+
loading="lazy"
|
|
152
|
+
width="400"
|
|
153
|
+
/>
|
|
154
|
+
</a>
|
|
155
|
+
{{/if}}
|
|
156
|
+
|
|
157
|
+
<div class="altz-product-card__body">
|
|
158
|
+
<a href="{{productUrl reference}}" class="altz-product-card__title">
|
|
159
|
+
{{name}}
|
|
160
|
+
</a>
|
|
161
|
+
|
|
162
|
+
<div class="altz-product-card__pricing">
|
|
163
|
+
{{#if discount}}
|
|
164
|
+
<span class="altz-product-card__price altz-product-card__price--discounted">
|
|
165
|
+
{{formatPrice price}}
|
|
166
|
+
</span>
|
|
167
|
+
<span class="altz-product-card__price altz-product-card__price--original">
|
|
168
|
+
{{formatPrice originalPrice}}
|
|
169
|
+
</span>
|
|
170
|
+
<span class="altz-product-card__discount-badge">
|
|
171
|
+
{{discountPercent originalPrice price}}
|
|
172
|
+
</span>
|
|
173
|
+
{{else}}
|
|
174
|
+
<span class="altz-product-card__price">
|
|
175
|
+
{{formatPrice price}}
|
|
176
|
+
</span>
|
|
177
|
+
{{/if}}
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{{#unless (isAvailable availability)}}
|
|
181
|
+
<p class="altz-product-card__unavailable">{{unavailableLabel}}</p>
|
|
182
|
+
{{/unless}}
|
|
183
|
+
|
|
184
|
+
{{#if (isAvailable availability)}}
|
|
185
|
+
<button
|
|
186
|
+
class="altz-product-card__add-to-cart"
|
|
187
|
+
hx-post="/commerce/api/process/cart/lines"
|
|
188
|
+
hx-vals='{"productReference":"{{reference}}","quantity":1}'
|
|
189
|
+
hx-target="#altz-cart-summary"
|
|
190
|
+
hx-swap="outerHTML"
|
|
191
|
+
hx-indicator=".altz-spinner"
|
|
192
|
+
>
|
|
193
|
+
{{addToCartLabel}}
|
|
194
|
+
</button>
|
|
195
|
+
{{/if}}
|
|
196
|
+
</div>
|
|
197
|
+
</article>
|
|
198
|
+
`;
|
|
199
|
+
const cartMiniTemplate = '<div id="altz-cart-mini" class="altz-cart-mini">\n <div class="altz-cart-mini__header">\n <span class="altz-cart-mini__label">{{cartLabel}}</span>\n <span class="altz-cart-mini__count">{{cartCount}}</span>\n </div>\n\n {{#cartHasItems}}\n <ul class="altz-cart-mini__lines">\n {{#each lines}}\n <li class="altz-cart-mini__line">\n <span class="altz-cart-mini__line-name">{{this.productName}}</span>\n <span class="altz-cart-mini__line-qty">× {{this.quantity}}</span>\n <span class="altz-cart-mini__line-price">{{formatPrice this.unitPriceWithTax}}</span>\n </li>\n {{/each}}\n </ul>\n\n <div class="altz-cart-mini__total">\n <span>{{totalLabel}}</span>\n <strong>{{cartTotal}}</strong>\n </div>\n\n <a href="{{cartUrl}}" class="altz-cart-mini__cta">\n {{viewCartLabel}}\n </a>\n {{/cartHasItems}}\n\n {{^cartHasItems}}\n <p class="altz-cart-mini__empty">{{emptyLabel}}</p>\n {{/cartHasItems}}\n</div>\n';
|
|
200
|
+
const productListTemplate = `<section class="altz-product-list">
|
|
201
|
+
{{#if title}}
|
|
202
|
+
<h2 class="altz-product-list__title">{{title}}</h2>
|
|
203
|
+
{{/if}}
|
|
204
|
+
|
|
205
|
+
{{#if products.length}}
|
|
206
|
+
<ul class="altz-product-list__grid" role="list">
|
|
207
|
+
{{#each products}}
|
|
208
|
+
<li class="altz-product-list__item">
|
|
209
|
+
<article class="altz-product-card{{#unless (isAvailable this.availability)}} altz-product-card--unavailable{{/unless}}">
|
|
210
|
+
{{#if this.imageUrl}}
|
|
211
|
+
<a href="{{productUrl this.reference}}" class="altz-product-card__image-link" aria-label="{{this.name}}">
|
|
212
|
+
<img
|
|
213
|
+
src="{{thumbnailUrl this.imageUrl 300}}"
|
|
214
|
+
alt="{{this.name}}"
|
|
215
|
+
class="altz-product-card__image"
|
|
216
|
+
loading="lazy"
|
|
217
|
+
width="300"
|
|
218
|
+
/>
|
|
219
|
+
</a>
|
|
220
|
+
{{/if}}
|
|
221
|
+
|
|
222
|
+
<div class="altz-product-card__body">
|
|
223
|
+
<a href="{{productUrl this.reference}}" class="altz-product-card__title">
|
|
224
|
+
{{this.name}}
|
|
225
|
+
</a>
|
|
226
|
+
|
|
227
|
+
<div class="altz-product-card__pricing">
|
|
228
|
+
{{#if this.discount}}
|
|
229
|
+
<span class="altz-product-card__price altz-product-card__price--discounted">
|
|
230
|
+
{{formatPrice this.price}}
|
|
231
|
+
</span>
|
|
232
|
+
<span class="altz-product-card__price altz-product-card__price--original">
|
|
233
|
+
{{formatPrice this.originalPrice}}
|
|
234
|
+
</span>
|
|
235
|
+
<span class="altz-product-card__discount-badge">
|
|
236
|
+
{{discountPercent this.originalPrice this.price}}
|
|
237
|
+
</span>
|
|
238
|
+
{{else}}
|
|
239
|
+
<span class="altz-product-card__price">
|
|
240
|
+
{{formatPrice this.price}}
|
|
241
|
+
</span>
|
|
242
|
+
{{/if}}
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{{#if (isAvailable this.availability)}}
|
|
246
|
+
<button
|
|
247
|
+
class="altz-product-card__add-to-cart"
|
|
248
|
+
hx-post="/commerce/api/process/cart/lines"
|
|
249
|
+
hx-vals='{"productReference":"{{this.reference}}","quantity":1}'
|
|
250
|
+
hx-target="#altz-cart-summary"
|
|
251
|
+
hx-swap="outerHTML"
|
|
252
|
+
hx-indicator=".altz-spinner"
|
|
253
|
+
>
|
|
254
|
+
{{../addToCartLabel}}
|
|
255
|
+
</button>
|
|
256
|
+
{{/if}}
|
|
257
|
+
</div>
|
|
258
|
+
</article>
|
|
259
|
+
</li>
|
|
260
|
+
{{/each}}
|
|
261
|
+
</ul>
|
|
262
|
+
{{else}}
|
|
263
|
+
<p class="altz-product-list__empty">{{emptyLabel}}</p>
|
|
264
|
+
{{/if}}
|
|
265
|
+
</section>
|
|
266
|
+
`;
|
|
267
|
+
function init(config) {
|
|
268
|
+
var _a;
|
|
269
|
+
const { client, handlebars, offlineSelector = "body" } = config;
|
|
270
|
+
registerAltazionAuthExtension(client);
|
|
271
|
+
if (typeof window !== "undefined" && window.htmx) {
|
|
272
|
+
window.htmx.config = window.htmx.config ?? {};
|
|
273
|
+
window.htmx.config.withCredentials = true;
|
|
274
|
+
}
|
|
275
|
+
const defaults = {
|
|
276
|
+
locale: client.context.locale,
|
|
277
|
+
currency: client.context.currency
|
|
278
|
+
};
|
|
279
|
+
let currentCart = null;
|
|
280
|
+
const getCart = () => currentCart;
|
|
281
|
+
client.onQueueEvent(async (event) => {
|
|
282
|
+
if (event.type === "flushed") {
|
|
283
|
+
try {
|
|
284
|
+
currentCart = await client.cart.getCart();
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (handlebars) {
|
|
290
|
+
registerPriceHelpers(handlebars, defaults);
|
|
291
|
+
registerProductHelpers(handlebars, client.context.siteUrl ?? "");
|
|
292
|
+
registerCartHelpers(handlebars, getCart, defaults);
|
|
293
|
+
}
|
|
294
|
+
const offlineEl = typeof document !== "undefined" ? document.querySelector(offlineSelector) : null;
|
|
295
|
+
const updateOfflineClass = () => {
|
|
296
|
+
offlineEl == null ? void 0 : offlineEl.classList.toggle("altz-offline", client.isOffline);
|
|
297
|
+
};
|
|
298
|
+
updateOfflineClass();
|
|
299
|
+
const unsubscribeConnectivity = (_a = client.connectivity) == null ? void 0 : _a.subscribe(() => updateOfflineClass());
|
|
300
|
+
const noHbs = () => () => "<!-- handlebars non initialisé dans @altazion/commerce-sdk-htmx -->";
|
|
301
|
+
const templates = handlebars ? {
|
|
302
|
+
productCard: handlebars.compile(productCardTemplate),
|
|
303
|
+
cartMini: handlebars.compile(cartMiniTemplate),
|
|
304
|
+
productList: handlebars.compile(productListTemplate)
|
|
305
|
+
} : {
|
|
306
|
+
productCard: noHbs(),
|
|
307
|
+
cartMini: noHbs(),
|
|
308
|
+
productList: noHbs()
|
|
309
|
+
};
|
|
310
|
+
return {
|
|
311
|
+
templates,
|
|
312
|
+
dispose() {
|
|
313
|
+
unsubscribeConnectivity == null ? void 0 : unsubscribeConnectivity();
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
exports.init = init;
|
|
318
|
+
exports.registerAltazionAuthExtension = registerAltazionAuthExtension;
|
|
319
|
+
exports.registerCartHelpers = registerCartHelpers;
|
|
320
|
+
exports.registerPriceHelpers = registerPriceHelpers;
|
|
321
|
+
exports.registerProductHelpers = registerProductHelpers;
|
|
322
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/extensions/altazion-auth.ts","../src/helpers/price.ts","../src/helpers/product.ts","../src/helpers/cart.ts","../src/templates/product-card.hbs?raw","../src/templates/cart-mini.hbs?raw","../src/templates/product-list.hbs?raw","../src/index.ts"],"sourcesContent":["import type { CommerceClient } from '@altazion/commerce-sdk-core'\n\ndeclare global {\n interface Window {\n htmx?: {\n on(eventName: string, listener: (event: Event) => void): void\n off(eventName: string, listener: (event: Event) => void): void\n }\n }\n}\n\ninterface HtmxConfigRequestEvent extends Event {\n detail: {\n headers: Record<string, string>\n elt: Element\n }\n}\n\n/**\n * Extension HTMX qui injecte automatiquement les headers de contexte Altazion\n * sur chaque requête HTMX.\n *\n * En modes browser et kiosk, les cookies sont transmis via credentials:'include'\n * par le navigateur. Cette extension ajoute uniquement les headers de contexte\n * (locale, currency) utiles côté serveur.\n */\nexport function registerAltazionAuthExtension(client: CommerceClient): void {\n if (typeof window === 'undefined' || !window.htmx) {\n console.warn('[AltazionHtmx] htmx non disponible sur window — extension non enregistrée')\n return\n }\n\n const handler = (event: Event): void => {\n const e = event as HtmxConfigRequestEvent\n e.detail.headers['Accept-Language'] = client.context.locale\n e.detail.headers['X-Altazion-Currency'] = client.context.currency\n }\n\n window.htmx.on('htmx:configRequest', handler)\n}\n","import type Handlebars from 'handlebars'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Formate un montant selon la locale et la devise.\n * Usage : {{formatPrice 19.99 \"EUR\" \"fr-FR\"}}\n * Si locale/currency sont omis, utilise les valeurs passées au registre.\n */\nexport function registerPriceHelpers(\n hbs: HandlebarsInstance,\n defaults: { locale: string; currency: string }\n): void {\n hbs.registerHelper(\n 'formatPrice',\n function (\n value: unknown,\n currency?: unknown,\n locale?: unknown\n ): string {\n const amount = typeof value === 'number' ? value : parseFloat(String(value))\n if (isNaN(amount)) return ''\n\n const resolvedCurrency =\n typeof currency === 'string' ? currency : defaults.currency\n const resolvedLocale =\n typeof locale === 'string' ? locale : defaults.locale\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency: resolvedCurrency,\n }).format(amount)\n }\n )\n\n hbs.registerHelper(\n 'formatPriceCompact',\n function (value: unknown, currency?: unknown, locale?: unknown): string {\n const amount = typeof value === 'number' ? value : parseFloat(String(value))\n if (isNaN(amount)) return ''\n\n const resolvedCurrency =\n typeof currency === 'string' ? currency : defaults.currency\n const resolvedLocale =\n typeof locale === 'string' ? locale : defaults.locale\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency: resolvedCurrency,\n notation: 'compact',\n }).format(amount)\n }\n )\n\n hbs.registerHelper(\n 'discountPercent',\n function (original: unknown, discounted: unknown): string {\n const orig = typeof original === 'number' ? original : parseFloat(String(original))\n const disc = typeof discounted === 'number' ? discounted : parseFloat(String(discounted))\n if (isNaN(orig) || isNaN(disc) || orig === 0) return ''\n const pct = Math.round(((orig - disc) / orig) * 100)\n return `-${pct}%`\n }\n )\n}\n","import type Handlebars from 'handlebars'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Helpers Handlebars liés aux produits.\n *\n * Usage :\n * {{productUrl reference}}\n * {{thumbnailUrl imageUrl 400}}\n * {{#if (isAvailable availability)}} ... {{/if}}\n */\nexport function registerProductHelpers(hbs: HandlebarsInstance, siteUrl: string): void {\n // URL de la fiche produit côté site vitrine\n hbs.registerHelper('productUrl', function (reference: unknown): string {\n if (typeof reference !== 'string' && typeof reference !== 'number') return ''\n const base = siteUrl.replace(/\\/$/, '')\n return `${base}/product/${encodeURIComponent(String(reference))}`\n })\n\n // Transforme une URL d'image en URL redimensionnée si le serveur le supporte\n // Pattern attendu : /media/images/{id} → /media/images/{id}?w={size}\n hbs.registerHelper('thumbnailUrl', function (imageUrl: unknown, size?: unknown): string {\n if (typeof imageUrl !== 'string' || !imageUrl) return ''\n const resolvedSize = typeof size === 'number' ? size : 300\n const separator = imageUrl.includes('?') ? '&' : '?'\n return `${imageUrl}${separator}w=${resolvedSize}`\n })\n\n // Retourne true si le produit est disponible (availability > 0 ou pas de stock géré)\n hbs.registerHelper('isAvailable', function (availability: unknown): boolean {\n if (availability === null || availability === undefined) return true\n if (typeof availability === 'boolean') return availability\n if (typeof availability === 'number') return availability > 0\n if (typeof availability === 'string') {\n const lower = availability.toLowerCase()\n return lower === 'available' || lower === 'instock'\n }\n return false\n })\n\n // Helper ifCond simple pour les comparaisons dans les templates\n hbs.registerHelper(\n 'ifCond',\n function (\n this: unknown,\n v1: unknown,\n operator: string,\n v2: unknown,\n options: Handlebars.HelperOptions\n ): string {\n let result = false\n switch (operator) {\n case '==': result = v1 == v2; break\n case '===': result = v1 === v2; break\n case '!=': result = v1 != v2; break\n case '!==': result = v1 !== v2; break\n case '>': result = (v1 as number) > (v2 as number); break\n case '<': result = (v1 as number) < (v2 as number); break\n case '>=': result = (v1 as number) >= (v2 as number); break\n case '<=': result = (v1 as number) <= (v2 as number); break\n default: result = false\n }\n return result ? options.fn(this as object) : options.inverse(this as object)\n }\n )\n}\n","import type Handlebars from 'handlebars'\nimport type { Cart, CartContent } from '@altazion/commerce-sdk-core'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Helpers Handlebars liés au panier.\n *\n * Nécessite un getter réactif sur le panier actuel.\n * Usage :\n * Nombre d'articles : {{cartCount}}\n * Total : {{cartTotal}}\n * {{#if cartHasItems}} ... {{/if}}\n */\nexport function registerCartHelpers(\n hbs: HandlebarsInstance,\n getCart: () => Cart | null,\n defaults: { locale: string; currency: string }\n): void {\n hbs.registerHelper('cartCount', function (): number {\n const cart = getCart()\n if (!cart) return 0\n return cart.totalQuantity\n })\n\n hbs.registerHelper('cartTotal', function (): string {\n const cart = getCart()\n if (!cart) return ''\n return new Intl.NumberFormat(defaults.locale, {\n style: 'currency',\n currency: defaults.currency,\n }).format(cart.totalAmountWithTax)\n })\n\n hbs.registerHelper('cartTotalRaw', function (): number {\n const cart = getCart()\n return cart?.totalAmountWithTax ?? 0\n })\n\n hbs.registerHelper('cartHasItems', function (this: unknown, options: Handlebars.HelperOptions): string {\n const cart = getCart()\n const hasItems = cart ? cart.totalQuantity > 0 : false\n return hasItems ? options.fn(this as object) : options.inverse(this as object)\n })\n\n hbs.registerHelper('cartLineCount', function (): number {\n const cart = getCart()\n if (!cart?.content) return 0\n return cart.content.reduce((sum: number, group: CartContent) => sum + (group.lines?.length ?? 0), 0)\n })\n}\n","export default \"<article class=\\\"altz-product-card{{#unless (isAvailable availability)}} altz-product-card--unavailable{{/unless}}\\\">\\n {{#if imageUrl}}\\n <a href=\\\"{{productUrl reference}}\\\" class=\\\"altz-product-card__image-link\\\" aria-label=\\\"{{name}}\\\">\\n <img\\n src=\\\"{{thumbnailUrl imageUrl 400}}\\\"\\n alt=\\\"{{name}}\\\"\\n class=\\\"altz-product-card__image\\\"\\n loading=\\\"lazy\\\"\\n width=\\\"400\\\"\\n />\\n </a>\\n {{/if}}\\n\\n <div class=\\\"altz-product-card__body\\\">\\n <a href=\\\"{{productUrl reference}}\\\" class=\\\"altz-product-card__title\\\">\\n {{name}}\\n </a>\\n\\n <div class=\\\"altz-product-card__pricing\\\">\\n {{#if discount}}\\n <span class=\\\"altz-product-card__price altz-product-card__price--discounted\\\">\\n {{formatPrice price}}\\n </span>\\n <span class=\\\"altz-product-card__price altz-product-card__price--original\\\">\\n {{formatPrice originalPrice}}\\n </span>\\n <span class=\\\"altz-product-card__discount-badge\\\">\\n {{discountPercent originalPrice price}}\\n </span>\\n {{else}}\\n <span class=\\\"altz-product-card__price\\\">\\n {{formatPrice price}}\\n </span>\\n {{/if}}\\n </div>\\n\\n {{#unless (isAvailable availability)}}\\n <p class=\\\"altz-product-card__unavailable\\\">{{unavailableLabel}}</p>\\n {{/unless}}\\n\\n {{#if (isAvailable availability)}}\\n <button\\n class=\\\"altz-product-card__add-to-cart\\\"\\n hx-post=\\\"/commerce/api/process/cart/lines\\\"\\n hx-vals='{\\\"productReference\\\":\\\"{{reference}}\\\",\\\"quantity\\\":1}'\\n hx-target=\\\"#altz-cart-summary\\\"\\n hx-swap=\\\"outerHTML\\\"\\n hx-indicator=\\\".altz-spinner\\\"\\n >\\n {{addToCartLabel}}\\n </button>\\n {{/if}}\\n </div>\\n</article>\\n\"","export default \"<div id=\\\"altz-cart-mini\\\" class=\\\"altz-cart-mini\\\">\\n <div class=\\\"altz-cart-mini__header\\\">\\n <span class=\\\"altz-cart-mini__label\\\">{{cartLabel}}</span>\\n <span class=\\\"altz-cart-mini__count\\\">{{cartCount}}</span>\\n </div>\\n\\n {{#cartHasItems}}\\n <ul class=\\\"altz-cart-mini__lines\\\">\\n {{#each lines}}\\n <li class=\\\"altz-cart-mini__line\\\">\\n <span class=\\\"altz-cart-mini__line-name\\\">{{this.productName}}</span>\\n <span class=\\\"altz-cart-mini__line-qty\\\">× {{this.quantity}}</span>\\n <span class=\\\"altz-cart-mini__line-price\\\">{{formatPrice this.unitPriceWithTax}}</span>\\n </li>\\n {{/each}}\\n </ul>\\n\\n <div class=\\\"altz-cart-mini__total\\\">\\n <span>{{totalLabel}}</span>\\n <strong>{{cartTotal}}</strong>\\n </div>\\n\\n <a href=\\\"{{cartUrl}}\\\" class=\\\"altz-cart-mini__cta\\\">\\n {{viewCartLabel}}\\n </a>\\n {{/cartHasItems}}\\n\\n {{^cartHasItems}}\\n <p class=\\\"altz-cart-mini__empty\\\">{{emptyLabel}}</p>\\n {{/cartHasItems}}\\n</div>\\n\"","export default \"<section class=\\\"altz-product-list\\\">\\n {{#if title}}\\n <h2 class=\\\"altz-product-list__title\\\">{{title}}</h2>\\n {{/if}}\\n\\n {{#if products.length}}\\n <ul class=\\\"altz-product-list__grid\\\" role=\\\"list\\\">\\n {{#each products}}\\n <li class=\\\"altz-product-list__item\\\">\\n <article class=\\\"altz-product-card{{#unless (isAvailable this.availability)}} altz-product-card--unavailable{{/unless}}\\\">\\n {{#if this.imageUrl}}\\n <a href=\\\"{{productUrl this.reference}}\\\" class=\\\"altz-product-card__image-link\\\" aria-label=\\\"{{this.name}}\\\">\\n <img\\n src=\\\"{{thumbnailUrl this.imageUrl 300}}\\\"\\n alt=\\\"{{this.name}}\\\"\\n class=\\\"altz-product-card__image\\\"\\n loading=\\\"lazy\\\"\\n width=\\\"300\\\"\\n />\\n </a>\\n {{/if}}\\n\\n <div class=\\\"altz-product-card__body\\\">\\n <a href=\\\"{{productUrl this.reference}}\\\" class=\\\"altz-product-card__title\\\">\\n {{this.name}}\\n </a>\\n\\n <div class=\\\"altz-product-card__pricing\\\">\\n {{#if this.discount}}\\n <span class=\\\"altz-product-card__price altz-product-card__price--discounted\\\">\\n {{formatPrice this.price}}\\n </span>\\n <span class=\\\"altz-product-card__price altz-product-card__price--original\\\">\\n {{formatPrice this.originalPrice}}\\n </span>\\n <span class=\\\"altz-product-card__discount-badge\\\">\\n {{discountPercent this.originalPrice this.price}}\\n </span>\\n {{else}}\\n <span class=\\\"altz-product-card__price\\\">\\n {{formatPrice this.price}}\\n </span>\\n {{/if}}\\n </div>\\n\\n {{#if (isAvailable this.availability)}}\\n <button\\n class=\\\"altz-product-card__add-to-cart\\\"\\n hx-post=\\\"/commerce/api/process/cart/lines\\\"\\n hx-vals='{\\\"productReference\\\":\\\"{{this.reference}}\\\",\\\"quantity\\\":1}'\\n hx-target=\\\"#altz-cart-summary\\\"\\n hx-swap=\\\"outerHTML\\\"\\n hx-indicator=\\\".altz-spinner\\\"\\n >\\n {{../addToCartLabel}}\\n </button>\\n {{/if}}\\n </div>\\n </article>\\n </li>\\n {{/each}}\\n </ul>\\n {{else}}\\n <p class=\\\"altz-product-list__empty\\\">{{emptyLabel}}</p>\\n {{/if}}\\n</section>\\n\"","import type { CommerceClient, Cart, QueueEvent } from '@altazion/commerce-sdk-core'\nimport type Handlebars from 'handlebars'\n\nimport { registerAltazionAuthExtension } from './extensions/altazion-auth'\nimport { registerPriceHelpers } from './helpers/price'\nimport { registerProductHelpers } from './helpers/product'\nimport { registerCartHelpers } from './helpers/cart'\n\n// Templates pré-compilés importés en tant que chaînes brutes\nimport productCardTemplate from './templates/product-card.hbs?raw'\nimport cartMiniTemplate from './templates/cart-mini.hbs?raw'\nimport productListTemplate from './templates/product-list.hbs?raw'\n\nexport type { Cart }\n\nexport interface AltazionHtmxConfig {\n /** Instance CommerceClient déjà initialisée */\n client: CommerceClient\n /**\n * Instance Handlebars (global ou importée).\n * Si omis, les helpers ne sont pas enregistrés.\n */\n handlebars?: typeof Handlebars\n /**\n * Sélecteur CSS de l'élément DOM à basculer avec la classe `altz-offline`\n * lorsque la connectivité est perdue/restaurée.\n * @default 'body'\n */\n offlineSelector?: string\n}\n\nexport interface AltazionHtmxInstance {\n /** Templates Handlebars compilés prêts à l'emploi */\n templates: {\n productCard: HandlebarsTemplateDelegate\n cartMini: HandlebarsTemplateDelegate\n productList: HandlebarsTemplateDelegate\n }\n /** Détruit les listeners et libère les ressources */\n dispose(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype HandlebarsTemplateDelegate = (context?: any) => string\n\n/**\n * Initialise le SDK HTMX Altazion.\n *\n * - Enregistre l'extension HTMX (headers contexte) si htmx est présent\n * - Enregistre les helpers Handlebars (prix, produits, panier) si hbs est fourni\n * - Active l'indicateur d'état offline sur le DOM\n * - Retourne les templates compilés\n *\n * @example\n * ```ts\n * import Handlebars from 'handlebars'\n * const sdk = init({ client, handlebars: Handlebars })\n * const html = sdk.templates.productCard(product)\n * document.getElementById('product')!.innerHTML = html\n * ```\n */\nexport function init(config: AltazionHtmxConfig): AltazionHtmxInstance {\n const { client, handlebars, offlineSelector = 'body' } = config\n\n // 1. Extension HTMX — headers de contexte sur chaque requête\n registerAltazionAuthExtension(client)\n\n // 2. HTMX config globale — transmettre les cookies sur les requêtes XHR HTMX\n if (typeof window !== 'undefined' && window.htmx) {\n // @ts-expect-error — htmx.config n'est pas dans les types window\n window.htmx.config = window.htmx.config ?? {}\n // @ts-expect-error\n window.htmx.config.withCredentials = true\n }\n\n // 3. Helpers Handlebars\n const defaults = {\n locale: client.context.locale,\n currency: client.context.currency,\n }\n\n // Getter réactif sur le panier (mis à jour lors du flush offline)\n let currentCart: Cart | null = null\n const getCart = (): Cart | null => currentCart\n\n // Abonnement aux events panier pour mettre à jour le getter\n client.onQueueEvent(async (event: QueueEvent) => {\n if (event.type === 'flushed') {\n try {\n currentCart = await client.cart.getCart()\n } catch {\n // Panier indisponible après flush — on ignore silencieusement\n }\n }\n })\n\n if (handlebars) {\n registerPriceHelpers(handlebars, defaults)\n registerProductHelpers(handlebars, client.context.siteUrl ?? '')\n registerCartHelpers(handlebars, getCart, defaults)\n }\n\n // 4. Indicateur offline DOM\n const offlineEl = typeof document !== 'undefined'\n ? document.querySelector(offlineSelector)\n : null\n\n const updateOfflineClass = (): void => {\n offlineEl?.classList.toggle('altz-offline', client.isOffline)\n }\n\n // Synchronisation initiale\n updateOfflineClass()\n\n // Abonnement ConnectivityManager — retourne la fonction de désabonnement\n const unsubscribeConnectivity = client.connectivity?.subscribe(() => updateOfflineClass())\n\n // 5. Templates compilés (retournés pour usage dans le code applicatif)\n const noHbs = (): HandlebarsTemplateDelegate =>\n () => '<!-- handlebars non initialisé dans @altazion/commerce-sdk-htmx -->'\n\n const templates = handlebars\n ? {\n productCard: handlebars.compile(productCardTemplate),\n cartMini: handlebars.compile(cartMiniTemplate),\n productList: handlebars.compile(productListTemplate),\n }\n : {\n productCard: noHbs(),\n cartMini: noHbs(),\n productList: noHbs(),\n }\n\n return {\n templates,\n dispose(): void {\n unsubscribeConnectivity?.()\n },\n }\n}\n\n// Re-exports helpers pour usage à la carte\nexport { registerPriceHelpers } from './helpers/price'\nexport { registerProductHelpers } from './helpers/product'\nexport { registerCartHelpers } from './helpers/cart'\nexport { registerAltazionAuthExtension } from './extensions/altazion-auth'\n"],"names":[],"mappings":";;AA0BO,SAAS,8BAA8B,QAA8B;AAC1E,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;AACjD,YAAQ,KAAK,2EAA2E;AACxF;AAAA,EACF;AAEA,QAAM,UAAU,CAAC,UAAuB;AACtC,UAAM,IAAI;AACV,MAAE,OAAO,QAAQ,iBAAiB,IAAI,OAAO,QAAQ;AACrD,MAAE,OAAO,QAAQ,qBAAqB,IAAI,OAAO,QAAQ;AAAA,EAC3D;AAEA,SAAO,KAAK,GAAG,sBAAsB,OAAO;AAC9C;AC9BO,SAAS,qBACd,KACA,UACM;AACN,MAAI;AAAA,IACF;AAAA,IACA,SACE,OACA,UACA,QACQ;AACR,YAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,WAAW,OAAO,KAAK,CAAC;AAC3E,UAAI,MAAM,MAAM,EAAG,QAAO;AAE1B,YAAM,mBACJ,OAAO,aAAa,WAAW,WAAW,SAAS;AACrD,YAAM,iBACJ,OAAO,WAAW,WAAW,SAAS,SAAS;AAEjD,aAAO,IAAI,KAAK,aAAa,gBAAgB;AAAA,QAC3C,OAAO;AAAA,QACP,UAAU;AAAA,MAAA,CACX,EAAE,OAAO,MAAM;AAAA,IAClB;AAAA,EAAA;AAGF,MAAI;AAAA,IACF;AAAA,IACA,SAAU,OAAgB,UAAoB,QAA0B;AACtE,YAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,WAAW,OAAO,KAAK,CAAC;AAC3E,UAAI,MAAM,MAAM,EAAG,QAAO;AAE1B,YAAM,mBACJ,OAAO,aAAa,WAAW,WAAW,SAAS;AACrD,YAAM,iBACJ,OAAO,WAAW,WAAW,SAAS,SAAS;AAEjD,aAAO,IAAI,KAAK,aAAa,gBAAgB;AAAA,QAC3C,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU;AAAA,MAAA,CACX,EAAE,OAAO,MAAM;AAAA,IAClB;AAAA,EAAA;AAGF,MAAI;AAAA,IACF;AAAA,IACA,SAAU,UAAmB,YAA6B;AACxD,YAAM,OAAO,OAAO,aAAa,WAAW,WAAW,WAAW,OAAO,QAAQ,CAAC;AAClF,YAAM,OAAO,OAAO,eAAe,WAAW,aAAa,WAAW,OAAO,UAAU,CAAC;AACxF,UAAI,MAAM,IAAI,KAAK,MAAM,IAAI,KAAK,SAAS,EAAG,QAAO;AACrD,YAAM,MAAM,KAAK,OAAQ,OAAO,QAAQ,OAAQ,GAAG;AACnD,aAAO,IAAI,GAAG;AAAA,IAChB;AAAA,EAAA;AAEJ;ACpDO,SAAS,uBAAuB,KAAyB,SAAuB;AAErF,MAAI,eAAe,cAAc,SAAU,WAA4B;AACrE,QAAI,OAAO,cAAc,YAAY,OAAO,cAAc,SAAU,QAAO;AAC3E,UAAM,OAAO,QAAQ,QAAQ,OAAO,EAAE;AACtC,WAAO,GAAG,IAAI,YAAY,mBAAmB,OAAO,SAAS,CAAC,CAAC;AAAA,EACjE,CAAC;AAID,MAAI,eAAe,gBAAgB,SAAU,UAAmB,MAAwB;AACtF,QAAI,OAAO,aAAa,YAAY,CAAC,SAAU,QAAO;AACtD,UAAM,eAAe,OAAO,SAAS,WAAW,OAAO;AACvD,UAAM,YAAY,SAAS,SAAS,GAAG,IAAI,MAAM;AACjD,WAAO,GAAG,QAAQ,GAAG,SAAS,KAAK,YAAY;AAAA,EACjD,CAAC;AAGD,MAAI,eAAe,eAAe,SAAU,cAAgC;AAC1E,QAAI,iBAAiB,QAAQ,iBAAiB,OAAW,QAAO;AAChE,QAAI,OAAO,iBAAiB,UAAW,QAAO;AAC9C,QAAI,OAAO,iBAAiB,SAAU,QAAO,eAAe;AAC5D,QAAI,OAAO,iBAAiB,UAAU;AACpC,YAAM,QAAQ,aAAa,YAAA;AAC3B,aAAO,UAAU,eAAe,UAAU;AAAA,IAC5C;AACA,WAAO;AAAA,EACT,CAAC;AAGD,MAAI;AAAA,IACF;AAAA,IACA,SAEE,IACA,UACA,IACA,SACQ;AACR,UAAI,SAAS;AACb,cAAQ,UAAA;AAAA,QACN,KAAK;AAAO,mBAAS,MAAM;AAAI;AAAA,QAC/B,KAAK;AAAO,mBAAS,OAAO;AAAI;AAAA,QAChC,KAAK;AAAO,mBAAS,MAAM;AAAI;AAAA,QAC/B,KAAK;AAAO,mBAAS,OAAO;AAAI;AAAA,QAChC,KAAK;AAAO,mBAAU,KAAiB;AAAe;AAAA,QACtD,KAAK;AAAO,mBAAU,KAAiB;AAAe;AAAA,QACtD,KAAK;AAAO,mBAAU,MAAkB;AAAe;AAAA,QACvD,KAAK;AAAO,mBAAU,MAAkB;AAAe;AAAA,QACvD;AAAY,mBAAS;AAAA,MAAA;AAEvB,aAAO,SAAS,QAAQ,GAAG,IAAc,IAAI,QAAQ,QAAQ,IAAc;AAAA,IAC7E;AAAA,EAAA;AAEJ;ACpDO,SAAS,oBACd,KACA,SACA,UACM;AACN,MAAI,eAAe,aAAa,WAAoB;AAClD,UAAM,OAAO,QAAA;AACb,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,KAAK;AAAA,EACd,CAAC;AAED,MAAI,eAAe,aAAa,WAAoB;AAClD,UAAM,OAAO,QAAA;AACb,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,IAAI,KAAK,aAAa,SAAS,QAAQ;AAAA,MAC5C,OAAO;AAAA,MACP,UAAU,SAAS;AAAA,IAAA,CACpB,EAAE,OAAO,KAAK,kBAAkB;AAAA,EACnC,CAAC;AAED,MAAI,eAAe,gBAAgB,WAAoB;AACrD,UAAM,OAAO,QAAA;AACb,YAAO,6BAAM,uBAAsB;AAAA,EACrC,CAAC;AAED,MAAI,eAAe,gBAAgB,SAAyB,SAA2C;AACrG,UAAM,OAAO,QAAA;AACb,UAAM,WAAW,OAAO,KAAK,gBAAgB,IAAI;AACjD,WAAO,WAAW,QAAQ,GAAG,IAAc,IAAI,QAAQ,QAAQ,IAAc;AAAA,EAC/E,CAAC;AAED,MAAI,eAAe,iBAAiB,WAAoB;AACtD,UAAM,OAAO,QAAA;AACb,QAAI,EAAC,6BAAM,SAAS,QAAO;AAC3B,WAAO,KAAK,QAAQ,OAAO,CAAC,KAAa,UAAA;;AAAuB,sBAAO,WAAM,UAAN,mBAAa,WAAU;AAAA,OAAI,CAAC;AAAA,EACrG,CAAC;AACH;AClDA,MAAA,sBAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAf,MAAA,mBAAe;ACAf,MAAA,sBAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AC6DR,SAAS,KAAK,QAAkD;;AACrE,QAAM,EAAE,QAAQ,YAAY,kBAAkB,WAAW;AAGzD,gCAA8B,MAAM;AAGpC,MAAI,OAAO,WAAW,eAAe,OAAO,MAAM;AAEhD,WAAO,KAAK,SAAS,OAAO,KAAK,UAAU,CAAA;AAE3C,WAAO,KAAK,OAAO,kBAAkB;AAAA,EACvC;AAGA,QAAM,WAAW;AAAA,IACf,QAAQ,OAAO,QAAQ;AAAA,IACvB,UAAU,OAAO,QAAQ;AAAA,EAAA;AAI3B,MAAI,cAA2B;AAC/B,QAAM,UAAU,MAAmB;AAGnC,SAAO,aAAa,OAAO,UAAsB;AAC/C,QAAI,MAAM,SAAS,WAAW;AAC5B,UAAI;AACF,sBAAc,MAAM,OAAO,KAAK,QAAA;AAAA,MAClC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,YAAY;AACd,yBAAqB,YAAY,QAAQ;AACzC,2BAAuB,YAAY,OAAO,QAAQ,WAAW,EAAE;AAC/D,wBAAoB,YAAY,SAAS,QAAQ;AAAA,EACnD;AAGA,QAAM,YAAY,OAAO,aAAa,cAClC,SAAS,cAAc,eAAe,IACtC;AAEJ,QAAM,qBAAqB,MAAY;AACrC,2CAAW,UAAU,OAAO,gBAAgB,OAAO;AAAA,EACrD;AAGA,qBAAA;AAGA,QAAM,2BAA0B,YAAO,iBAAP,mBAAqB,UAAU,MAAM;AAGrE,QAAM,QAAQ,MACZ,MAAM;AAER,QAAM,YAAY,aACd;AAAA,IACE,aAAa,WAAW,QAAQ,mBAAmB;AAAA,IACnD,UAAU,WAAW,QAAQ,gBAAgB;AAAA,IAC7C,aAAa,WAAW,QAAQ,mBAAmB;AAAA,EAAA,IAErD;AAAA,IACE,aAAa,MAAA;AAAA,IACb,UAAU,MAAA;AAAA,IACV,aAAa,MAAA;AAAA,EAAM;AAGzB,SAAO;AAAA,IACL;AAAA,IACA,UAAgB;AACd;AAAA,IACF;AAAA,EAAA;AAEJ;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Cart } from '@altazion/commerce-sdk-core';
|
|
2
|
+
import { CommerceClient } from '@altazion/commerce-sdk-core';
|
|
3
|
+
import { default as default_2 } from 'handlebars';
|
|
4
|
+
|
|
5
|
+
export declare interface AltazionHtmxConfig {
|
|
6
|
+
/** Instance CommerceClient déjà initialisée */
|
|
7
|
+
client: CommerceClient;
|
|
8
|
+
/**
|
|
9
|
+
* Instance Handlebars (global ou importée).
|
|
10
|
+
* Si omis, les helpers ne sont pas enregistrés.
|
|
11
|
+
*/
|
|
12
|
+
handlebars?: typeof default_2;
|
|
13
|
+
/**
|
|
14
|
+
* Sélecteur CSS de l'élément DOM à basculer avec la classe `altz-offline`
|
|
15
|
+
* lorsque la connectivité est perdue/restaurée.
|
|
16
|
+
* @default 'body'
|
|
17
|
+
*/
|
|
18
|
+
offlineSelector?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export declare interface AltazionHtmxInstance {
|
|
22
|
+
/** Templates Handlebars compilés prêts à l'emploi */
|
|
23
|
+
templates: {
|
|
24
|
+
productCard: HandlebarsTemplateDelegate_2;
|
|
25
|
+
cartMini: HandlebarsTemplateDelegate_2;
|
|
26
|
+
productList: HandlebarsTemplateDelegate_2;
|
|
27
|
+
};
|
|
28
|
+
/** Détruit les listeners et libère les ressources */
|
|
29
|
+
dispose(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { Cart }
|
|
33
|
+
|
|
34
|
+
declare type HandlebarsInstance = typeof default_2;
|
|
35
|
+
|
|
36
|
+
declare type HandlebarsInstance_2 = typeof default_2;
|
|
37
|
+
|
|
38
|
+
declare type HandlebarsInstance_3 = typeof default_2;
|
|
39
|
+
|
|
40
|
+
declare type HandlebarsTemplateDelegate_2 = (context?: any) => string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Initialise le SDK HTMX Altazion.
|
|
44
|
+
*
|
|
45
|
+
* - Enregistre l'extension HTMX (headers contexte) si htmx est présent
|
|
46
|
+
* - Enregistre les helpers Handlebars (prix, produits, panier) si hbs est fourni
|
|
47
|
+
* - Active l'indicateur d'état offline sur le DOM
|
|
48
|
+
* - Retourne les templates compilés
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import Handlebars from 'handlebars'
|
|
53
|
+
* const sdk = init({ client, handlebars: Handlebars })
|
|
54
|
+
* const html = sdk.templates.productCard(product)
|
|
55
|
+
* document.getElementById('product')!.innerHTML = html
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function init(config: AltazionHtmxConfig): AltazionHtmxInstance;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extension HTMX qui injecte automatiquement les headers de contexte Altazion
|
|
62
|
+
* sur chaque requête HTMX.
|
|
63
|
+
*
|
|
64
|
+
* En modes browser et kiosk, les cookies sont transmis via credentials:'include'
|
|
65
|
+
* par le navigateur. Cette extension ajoute uniquement les headers de contexte
|
|
66
|
+
* (locale, currency) utiles côté serveur.
|
|
67
|
+
*/
|
|
68
|
+
export declare function registerAltazionAuthExtension(client: CommerceClient): void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Helpers Handlebars liés au panier.
|
|
72
|
+
*
|
|
73
|
+
* Nécessite un getter réactif sur le panier actuel.
|
|
74
|
+
* Usage :
|
|
75
|
+
* Nombre d'articles : {{cartCount}}
|
|
76
|
+
* Total : {{cartTotal}}
|
|
77
|
+
* {{#if cartHasItems}} ... {{/if}}
|
|
78
|
+
*/
|
|
79
|
+
export declare function registerCartHelpers(hbs: HandlebarsInstance_3, getCart: () => Cart | null, defaults: {
|
|
80
|
+
locale: string;
|
|
81
|
+
currency: string;
|
|
82
|
+
}): void;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Formate un montant selon la locale et la devise.
|
|
86
|
+
* Usage : {{formatPrice 19.99 "EUR" "fr-FR"}}
|
|
87
|
+
* Si locale/currency sont omis, utilise les valeurs passées au registre.
|
|
88
|
+
*/
|
|
89
|
+
export declare function registerPriceHelpers(hbs: HandlebarsInstance, defaults: {
|
|
90
|
+
locale: string;
|
|
91
|
+
currency: string;
|
|
92
|
+
}): void;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Helpers Handlebars liés aux produits.
|
|
96
|
+
*
|
|
97
|
+
* Usage :
|
|
98
|
+
* {{productUrl reference}}
|
|
99
|
+
* {{thumbnailUrl imageUrl 400}}
|
|
100
|
+
* {{#if (isAvailable availability)}} ... {{/if}}
|
|
101
|
+
*/
|
|
102
|
+
export declare function registerProductHelpers(hbs: HandlebarsInstance_2, siteUrl: string): void;
|
|
103
|
+
|
|
104
|
+
export { }
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
declare global {
|
|
108
|
+
interface Window {
|
|
109
|
+
htmx?: {
|
|
110
|
+
on(eventName: string, listener: (event: Event) => void): void;
|
|
111
|
+
off(eventName: string, listener: (event: Event) => void): void;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
var AltazionCommerceSdkHtmx = function(exports) {
|
|
2
|
+
"use strict";
|
|
3
|
+
function registerAltazionAuthExtension(client) {
|
|
4
|
+
if (typeof window === "undefined" || !window.htmx) {
|
|
5
|
+
console.warn("[AltazionHtmx] htmx non disponible sur window — extension non enregistrée");
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const handler = (event) => {
|
|
9
|
+
const e = event;
|
|
10
|
+
e.detail.headers["Accept-Language"] = client.context.locale;
|
|
11
|
+
e.detail.headers["X-Altazion-Currency"] = client.context.currency;
|
|
12
|
+
};
|
|
13
|
+
window.htmx.on("htmx:configRequest", handler);
|
|
14
|
+
}
|
|
15
|
+
function registerPriceHelpers(hbs, defaults) {
|
|
16
|
+
hbs.registerHelper(
|
|
17
|
+
"formatPrice",
|
|
18
|
+
function(value, currency, locale) {
|
|
19
|
+
const amount = typeof value === "number" ? value : parseFloat(String(value));
|
|
20
|
+
if (isNaN(amount)) return "";
|
|
21
|
+
const resolvedCurrency = typeof currency === "string" ? currency : defaults.currency;
|
|
22
|
+
const resolvedLocale = typeof locale === "string" ? locale : defaults.locale;
|
|
23
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
24
|
+
style: "currency",
|
|
25
|
+
currency: resolvedCurrency
|
|
26
|
+
}).format(amount);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
hbs.registerHelper(
|
|
30
|
+
"formatPriceCompact",
|
|
31
|
+
function(value, currency, locale) {
|
|
32
|
+
const amount = typeof value === "number" ? value : parseFloat(String(value));
|
|
33
|
+
if (isNaN(amount)) return "";
|
|
34
|
+
const resolvedCurrency = typeof currency === "string" ? currency : defaults.currency;
|
|
35
|
+
const resolvedLocale = typeof locale === "string" ? locale : defaults.locale;
|
|
36
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
37
|
+
style: "currency",
|
|
38
|
+
currency: resolvedCurrency,
|
|
39
|
+
notation: "compact"
|
|
40
|
+
}).format(amount);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
hbs.registerHelper(
|
|
44
|
+
"discountPercent",
|
|
45
|
+
function(original, discounted) {
|
|
46
|
+
const orig = typeof original === "number" ? original : parseFloat(String(original));
|
|
47
|
+
const disc = typeof discounted === "number" ? discounted : parseFloat(String(discounted));
|
|
48
|
+
if (isNaN(orig) || isNaN(disc) || orig === 0) return "";
|
|
49
|
+
const pct = Math.round((orig - disc) / orig * 100);
|
|
50
|
+
return `-${pct}%`;
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
function registerProductHelpers(hbs, siteUrl) {
|
|
55
|
+
hbs.registerHelper("productUrl", function(reference) {
|
|
56
|
+
if (typeof reference !== "string" && typeof reference !== "number") return "";
|
|
57
|
+
const base = siteUrl.replace(/\/$/, "");
|
|
58
|
+
return `${base}/product/${encodeURIComponent(String(reference))}`;
|
|
59
|
+
});
|
|
60
|
+
hbs.registerHelper("thumbnailUrl", function(imageUrl, size) {
|
|
61
|
+
if (typeof imageUrl !== "string" || !imageUrl) return "";
|
|
62
|
+
const resolvedSize = typeof size === "number" ? size : 300;
|
|
63
|
+
const separator = imageUrl.includes("?") ? "&" : "?";
|
|
64
|
+
return `${imageUrl}${separator}w=${resolvedSize}`;
|
|
65
|
+
});
|
|
66
|
+
hbs.registerHelper("isAvailable", function(availability) {
|
|
67
|
+
if (availability === null || availability === void 0) return true;
|
|
68
|
+
if (typeof availability === "boolean") return availability;
|
|
69
|
+
if (typeof availability === "number") return availability > 0;
|
|
70
|
+
if (typeof availability === "string") {
|
|
71
|
+
const lower = availability.toLowerCase();
|
|
72
|
+
return lower === "available" || lower === "instock";
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
});
|
|
76
|
+
hbs.registerHelper(
|
|
77
|
+
"ifCond",
|
|
78
|
+
function(v1, operator, v2, options) {
|
|
79
|
+
let result = false;
|
|
80
|
+
switch (operator) {
|
|
81
|
+
case "==":
|
|
82
|
+
result = v1 == v2;
|
|
83
|
+
break;
|
|
84
|
+
case "===":
|
|
85
|
+
result = v1 === v2;
|
|
86
|
+
break;
|
|
87
|
+
case "!=":
|
|
88
|
+
result = v1 != v2;
|
|
89
|
+
break;
|
|
90
|
+
case "!==":
|
|
91
|
+
result = v1 !== v2;
|
|
92
|
+
break;
|
|
93
|
+
case ">":
|
|
94
|
+
result = v1 > v2;
|
|
95
|
+
break;
|
|
96
|
+
case "<":
|
|
97
|
+
result = v1 < v2;
|
|
98
|
+
break;
|
|
99
|
+
case ">=":
|
|
100
|
+
result = v1 >= v2;
|
|
101
|
+
break;
|
|
102
|
+
case "<=":
|
|
103
|
+
result = v1 <= v2;
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
result = false;
|
|
107
|
+
}
|
|
108
|
+
return result ? options.fn(this) : options.inverse(this);
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function registerCartHelpers(hbs, getCart, defaults) {
|
|
113
|
+
hbs.registerHelper("cartCount", function() {
|
|
114
|
+
const cart = getCart();
|
|
115
|
+
if (!cart) return 0;
|
|
116
|
+
return cart.totalQuantity;
|
|
117
|
+
});
|
|
118
|
+
hbs.registerHelper("cartTotal", function() {
|
|
119
|
+
const cart = getCart();
|
|
120
|
+
if (!cart) return "";
|
|
121
|
+
return new Intl.NumberFormat(defaults.locale, {
|
|
122
|
+
style: "currency",
|
|
123
|
+
currency: defaults.currency
|
|
124
|
+
}).format(cart.totalAmountWithTax);
|
|
125
|
+
});
|
|
126
|
+
hbs.registerHelper("cartTotalRaw", function() {
|
|
127
|
+
const cart = getCart();
|
|
128
|
+
return (cart == null ? void 0 : cart.totalAmountWithTax) ?? 0;
|
|
129
|
+
});
|
|
130
|
+
hbs.registerHelper("cartHasItems", function(options) {
|
|
131
|
+
const cart = getCart();
|
|
132
|
+
const hasItems = cart ? cart.totalQuantity > 0 : false;
|
|
133
|
+
return hasItems ? options.fn(this) : options.inverse(this);
|
|
134
|
+
});
|
|
135
|
+
hbs.registerHelper("cartLineCount", function() {
|
|
136
|
+
const cart = getCart();
|
|
137
|
+
if (!(cart == null ? void 0 : cart.content)) return 0;
|
|
138
|
+
return cart.content.reduce((sum, group) => {
|
|
139
|
+
var _a;
|
|
140
|
+
return sum + (((_a = group.lines) == null ? void 0 : _a.length) ?? 0);
|
|
141
|
+
}, 0);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const productCardTemplate = `<article class="altz-product-card{{#unless (isAvailable availability)}} altz-product-card--unavailable{{/unless}}">
|
|
145
|
+
{{#if imageUrl}}
|
|
146
|
+
<a href="{{productUrl reference}}" class="altz-product-card__image-link" aria-label="{{name}}">
|
|
147
|
+
<img
|
|
148
|
+
src="{{thumbnailUrl imageUrl 400}}"
|
|
149
|
+
alt="{{name}}"
|
|
150
|
+
class="altz-product-card__image"
|
|
151
|
+
loading="lazy"
|
|
152
|
+
width="400"
|
|
153
|
+
/>
|
|
154
|
+
</a>
|
|
155
|
+
{{/if}}
|
|
156
|
+
|
|
157
|
+
<div class="altz-product-card__body">
|
|
158
|
+
<a href="{{productUrl reference}}" class="altz-product-card__title">
|
|
159
|
+
{{name}}
|
|
160
|
+
</a>
|
|
161
|
+
|
|
162
|
+
<div class="altz-product-card__pricing">
|
|
163
|
+
{{#if discount}}
|
|
164
|
+
<span class="altz-product-card__price altz-product-card__price--discounted">
|
|
165
|
+
{{formatPrice price}}
|
|
166
|
+
</span>
|
|
167
|
+
<span class="altz-product-card__price altz-product-card__price--original">
|
|
168
|
+
{{formatPrice originalPrice}}
|
|
169
|
+
</span>
|
|
170
|
+
<span class="altz-product-card__discount-badge">
|
|
171
|
+
{{discountPercent originalPrice price}}
|
|
172
|
+
</span>
|
|
173
|
+
{{else}}
|
|
174
|
+
<span class="altz-product-card__price">
|
|
175
|
+
{{formatPrice price}}
|
|
176
|
+
</span>
|
|
177
|
+
{{/if}}
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{{#unless (isAvailable availability)}}
|
|
181
|
+
<p class="altz-product-card__unavailable">{{unavailableLabel}}</p>
|
|
182
|
+
{{/unless}}
|
|
183
|
+
|
|
184
|
+
{{#if (isAvailable availability)}}
|
|
185
|
+
<button
|
|
186
|
+
class="altz-product-card__add-to-cart"
|
|
187
|
+
hx-post="/commerce/api/process/cart/lines"
|
|
188
|
+
hx-vals='{"productReference":"{{reference}}","quantity":1}'
|
|
189
|
+
hx-target="#altz-cart-summary"
|
|
190
|
+
hx-swap="outerHTML"
|
|
191
|
+
hx-indicator=".altz-spinner"
|
|
192
|
+
>
|
|
193
|
+
{{addToCartLabel}}
|
|
194
|
+
</button>
|
|
195
|
+
{{/if}}
|
|
196
|
+
</div>
|
|
197
|
+
</article>
|
|
198
|
+
`;
|
|
199
|
+
const cartMiniTemplate = '<div id="altz-cart-mini" class="altz-cart-mini">\n <div class="altz-cart-mini__header">\n <span class="altz-cart-mini__label">{{cartLabel}}</span>\n <span class="altz-cart-mini__count">{{cartCount}}</span>\n </div>\n\n {{#cartHasItems}}\n <ul class="altz-cart-mini__lines">\n {{#each lines}}\n <li class="altz-cart-mini__line">\n <span class="altz-cart-mini__line-name">{{this.productName}}</span>\n <span class="altz-cart-mini__line-qty">× {{this.quantity}}</span>\n <span class="altz-cart-mini__line-price">{{formatPrice this.unitPriceWithTax}}</span>\n </li>\n {{/each}}\n </ul>\n\n <div class="altz-cart-mini__total">\n <span>{{totalLabel}}</span>\n <strong>{{cartTotal}}</strong>\n </div>\n\n <a href="{{cartUrl}}" class="altz-cart-mini__cta">\n {{viewCartLabel}}\n </a>\n {{/cartHasItems}}\n\n {{^cartHasItems}}\n <p class="altz-cart-mini__empty">{{emptyLabel}}</p>\n {{/cartHasItems}}\n</div>\n';
|
|
200
|
+
const productListTemplate = `<section class="altz-product-list">
|
|
201
|
+
{{#if title}}
|
|
202
|
+
<h2 class="altz-product-list__title">{{title}}</h2>
|
|
203
|
+
{{/if}}
|
|
204
|
+
|
|
205
|
+
{{#if products.length}}
|
|
206
|
+
<ul class="altz-product-list__grid" role="list">
|
|
207
|
+
{{#each products}}
|
|
208
|
+
<li class="altz-product-list__item">
|
|
209
|
+
<article class="altz-product-card{{#unless (isAvailable this.availability)}} altz-product-card--unavailable{{/unless}}">
|
|
210
|
+
{{#if this.imageUrl}}
|
|
211
|
+
<a href="{{productUrl this.reference}}" class="altz-product-card__image-link" aria-label="{{this.name}}">
|
|
212
|
+
<img
|
|
213
|
+
src="{{thumbnailUrl this.imageUrl 300}}"
|
|
214
|
+
alt="{{this.name}}"
|
|
215
|
+
class="altz-product-card__image"
|
|
216
|
+
loading="lazy"
|
|
217
|
+
width="300"
|
|
218
|
+
/>
|
|
219
|
+
</a>
|
|
220
|
+
{{/if}}
|
|
221
|
+
|
|
222
|
+
<div class="altz-product-card__body">
|
|
223
|
+
<a href="{{productUrl this.reference}}" class="altz-product-card__title">
|
|
224
|
+
{{this.name}}
|
|
225
|
+
</a>
|
|
226
|
+
|
|
227
|
+
<div class="altz-product-card__pricing">
|
|
228
|
+
{{#if this.discount}}
|
|
229
|
+
<span class="altz-product-card__price altz-product-card__price--discounted">
|
|
230
|
+
{{formatPrice this.price}}
|
|
231
|
+
</span>
|
|
232
|
+
<span class="altz-product-card__price altz-product-card__price--original">
|
|
233
|
+
{{formatPrice this.originalPrice}}
|
|
234
|
+
</span>
|
|
235
|
+
<span class="altz-product-card__discount-badge">
|
|
236
|
+
{{discountPercent this.originalPrice this.price}}
|
|
237
|
+
</span>
|
|
238
|
+
{{else}}
|
|
239
|
+
<span class="altz-product-card__price">
|
|
240
|
+
{{formatPrice this.price}}
|
|
241
|
+
</span>
|
|
242
|
+
{{/if}}
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{{#if (isAvailable this.availability)}}
|
|
246
|
+
<button
|
|
247
|
+
class="altz-product-card__add-to-cart"
|
|
248
|
+
hx-post="/commerce/api/process/cart/lines"
|
|
249
|
+
hx-vals='{"productReference":"{{this.reference}}","quantity":1}'
|
|
250
|
+
hx-target="#altz-cart-summary"
|
|
251
|
+
hx-swap="outerHTML"
|
|
252
|
+
hx-indicator=".altz-spinner"
|
|
253
|
+
>
|
|
254
|
+
{{../addToCartLabel}}
|
|
255
|
+
</button>
|
|
256
|
+
{{/if}}
|
|
257
|
+
</div>
|
|
258
|
+
</article>
|
|
259
|
+
</li>
|
|
260
|
+
{{/each}}
|
|
261
|
+
</ul>
|
|
262
|
+
{{else}}
|
|
263
|
+
<p class="altz-product-list__empty">{{emptyLabel}}</p>
|
|
264
|
+
{{/if}}
|
|
265
|
+
</section>
|
|
266
|
+
`;
|
|
267
|
+
function init(config) {
|
|
268
|
+
var _a;
|
|
269
|
+
const { client, handlebars, offlineSelector = "body" } = config;
|
|
270
|
+
registerAltazionAuthExtension(client);
|
|
271
|
+
if (typeof window !== "undefined" && window.htmx) {
|
|
272
|
+
window.htmx.config = window.htmx.config ?? {};
|
|
273
|
+
window.htmx.config.withCredentials = true;
|
|
274
|
+
}
|
|
275
|
+
const defaults = {
|
|
276
|
+
locale: client.context.locale,
|
|
277
|
+
currency: client.context.currency
|
|
278
|
+
};
|
|
279
|
+
let currentCart = null;
|
|
280
|
+
const getCart = () => currentCart;
|
|
281
|
+
client.onQueueEvent(async (event) => {
|
|
282
|
+
if (event.type === "flushed") {
|
|
283
|
+
try {
|
|
284
|
+
currentCart = await client.cart.getCart();
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (handlebars) {
|
|
290
|
+
registerPriceHelpers(handlebars, defaults);
|
|
291
|
+
registerProductHelpers(handlebars, client.context.siteUrl ?? "");
|
|
292
|
+
registerCartHelpers(handlebars, getCart, defaults);
|
|
293
|
+
}
|
|
294
|
+
const offlineEl = typeof document !== "undefined" ? document.querySelector(offlineSelector) : null;
|
|
295
|
+
const updateOfflineClass = () => {
|
|
296
|
+
offlineEl == null ? void 0 : offlineEl.classList.toggle("altz-offline", client.isOffline);
|
|
297
|
+
};
|
|
298
|
+
updateOfflineClass();
|
|
299
|
+
const unsubscribeConnectivity = (_a = client.connectivity) == null ? void 0 : _a.subscribe(() => updateOfflineClass());
|
|
300
|
+
const noHbs = () => () => "<!-- handlebars non initialisé dans @altazion/commerce-sdk-htmx -->";
|
|
301
|
+
const templates = handlebars ? {
|
|
302
|
+
productCard: handlebars.compile(productCardTemplate),
|
|
303
|
+
cartMini: handlebars.compile(cartMiniTemplate),
|
|
304
|
+
productList: handlebars.compile(productListTemplate)
|
|
305
|
+
} : {
|
|
306
|
+
productCard: noHbs(),
|
|
307
|
+
cartMini: noHbs(),
|
|
308
|
+
productList: noHbs()
|
|
309
|
+
};
|
|
310
|
+
return {
|
|
311
|
+
templates,
|
|
312
|
+
dispose() {
|
|
313
|
+
unsubscribeConnectivity == null ? void 0 : unsubscribeConnectivity();
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
exports.init = init;
|
|
318
|
+
exports.registerAltazionAuthExtension = registerAltazionAuthExtension;
|
|
319
|
+
exports.registerCartHelpers = registerCartHelpers;
|
|
320
|
+
exports.registerPriceHelpers = registerPriceHelpers;
|
|
321
|
+
exports.registerProductHelpers = registerProductHelpers;
|
|
322
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
323
|
+
return exports;
|
|
324
|
+
}({});
|
|
325
|
+
//# sourceMappingURL=index.iife.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.iife.js","sources":["../src/extensions/altazion-auth.ts","../src/helpers/price.ts","../src/helpers/product.ts","../src/helpers/cart.ts","../src/templates/product-card.hbs?raw","../src/templates/cart-mini.hbs?raw","../src/templates/product-list.hbs?raw","../src/index.ts"],"sourcesContent":["import type { CommerceClient } from '@altazion/commerce-sdk-core'\n\ndeclare global {\n interface Window {\n htmx?: {\n on(eventName: string, listener: (event: Event) => void): void\n off(eventName: string, listener: (event: Event) => void): void\n }\n }\n}\n\ninterface HtmxConfigRequestEvent extends Event {\n detail: {\n headers: Record<string, string>\n elt: Element\n }\n}\n\n/**\n * Extension HTMX qui injecte automatiquement les headers de contexte Altazion\n * sur chaque requête HTMX.\n *\n * En modes browser et kiosk, les cookies sont transmis via credentials:'include'\n * par le navigateur. Cette extension ajoute uniquement les headers de contexte\n * (locale, currency) utiles côté serveur.\n */\nexport function registerAltazionAuthExtension(client: CommerceClient): void {\n if (typeof window === 'undefined' || !window.htmx) {\n console.warn('[AltazionHtmx] htmx non disponible sur window — extension non enregistrée')\n return\n }\n\n const handler = (event: Event): void => {\n const e = event as HtmxConfigRequestEvent\n e.detail.headers['Accept-Language'] = client.context.locale\n e.detail.headers['X-Altazion-Currency'] = client.context.currency\n }\n\n window.htmx.on('htmx:configRequest', handler)\n}\n","import type Handlebars from 'handlebars'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Formate un montant selon la locale et la devise.\n * Usage : {{formatPrice 19.99 \"EUR\" \"fr-FR\"}}\n * Si locale/currency sont omis, utilise les valeurs passées au registre.\n */\nexport function registerPriceHelpers(\n hbs: HandlebarsInstance,\n defaults: { locale: string; currency: string }\n): void {\n hbs.registerHelper(\n 'formatPrice',\n function (\n value: unknown,\n currency?: unknown,\n locale?: unknown\n ): string {\n const amount = typeof value === 'number' ? value : parseFloat(String(value))\n if (isNaN(amount)) return ''\n\n const resolvedCurrency =\n typeof currency === 'string' ? currency : defaults.currency\n const resolvedLocale =\n typeof locale === 'string' ? locale : defaults.locale\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency: resolvedCurrency,\n }).format(amount)\n }\n )\n\n hbs.registerHelper(\n 'formatPriceCompact',\n function (value: unknown, currency?: unknown, locale?: unknown): string {\n const amount = typeof value === 'number' ? value : parseFloat(String(value))\n if (isNaN(amount)) return ''\n\n const resolvedCurrency =\n typeof currency === 'string' ? currency : defaults.currency\n const resolvedLocale =\n typeof locale === 'string' ? locale : defaults.locale\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency: resolvedCurrency,\n notation: 'compact',\n }).format(amount)\n }\n )\n\n hbs.registerHelper(\n 'discountPercent',\n function (original: unknown, discounted: unknown): string {\n const orig = typeof original === 'number' ? original : parseFloat(String(original))\n const disc = typeof discounted === 'number' ? discounted : parseFloat(String(discounted))\n if (isNaN(orig) || isNaN(disc) || orig === 0) return ''\n const pct = Math.round(((orig - disc) / orig) * 100)\n return `-${pct}%`\n }\n )\n}\n","import type Handlebars from 'handlebars'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Helpers Handlebars liés aux produits.\n *\n * Usage :\n * {{productUrl reference}}\n * {{thumbnailUrl imageUrl 400}}\n * {{#if (isAvailable availability)}} ... {{/if}}\n */\nexport function registerProductHelpers(hbs: HandlebarsInstance, siteUrl: string): void {\n // URL de la fiche produit côté site vitrine\n hbs.registerHelper('productUrl', function (reference: unknown): string {\n if (typeof reference !== 'string' && typeof reference !== 'number') return ''\n const base = siteUrl.replace(/\\/$/, '')\n return `${base}/product/${encodeURIComponent(String(reference))}`\n })\n\n // Transforme une URL d'image en URL redimensionnée si le serveur le supporte\n // Pattern attendu : /media/images/{id} → /media/images/{id}?w={size}\n hbs.registerHelper('thumbnailUrl', function (imageUrl: unknown, size?: unknown): string {\n if (typeof imageUrl !== 'string' || !imageUrl) return ''\n const resolvedSize = typeof size === 'number' ? size : 300\n const separator = imageUrl.includes('?') ? '&' : '?'\n return `${imageUrl}${separator}w=${resolvedSize}`\n })\n\n // Retourne true si le produit est disponible (availability > 0 ou pas de stock géré)\n hbs.registerHelper('isAvailable', function (availability: unknown): boolean {\n if (availability === null || availability === undefined) return true\n if (typeof availability === 'boolean') return availability\n if (typeof availability === 'number') return availability > 0\n if (typeof availability === 'string') {\n const lower = availability.toLowerCase()\n return lower === 'available' || lower === 'instock'\n }\n return false\n })\n\n // Helper ifCond simple pour les comparaisons dans les templates\n hbs.registerHelper(\n 'ifCond',\n function (\n this: unknown,\n v1: unknown,\n operator: string,\n v2: unknown,\n options: Handlebars.HelperOptions\n ): string {\n let result = false\n switch (operator) {\n case '==': result = v1 == v2; break\n case '===': result = v1 === v2; break\n case '!=': result = v1 != v2; break\n case '!==': result = v1 !== v2; break\n case '>': result = (v1 as number) > (v2 as number); break\n case '<': result = (v1 as number) < (v2 as number); break\n case '>=': result = (v1 as number) >= (v2 as number); break\n case '<=': result = (v1 as number) <= (v2 as number); break\n default: result = false\n }\n return result ? options.fn(this as object) : options.inverse(this as object)\n }\n )\n}\n","import type Handlebars from 'handlebars'\nimport type { Cart, CartContent } from '@altazion/commerce-sdk-core'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Helpers Handlebars liés au panier.\n *\n * Nécessite un getter réactif sur le panier actuel.\n * Usage :\n * Nombre d'articles : {{cartCount}}\n * Total : {{cartTotal}}\n * {{#if cartHasItems}} ... {{/if}}\n */\nexport function registerCartHelpers(\n hbs: HandlebarsInstance,\n getCart: () => Cart | null,\n defaults: { locale: string; currency: string }\n): void {\n hbs.registerHelper('cartCount', function (): number {\n const cart = getCart()\n if (!cart) return 0\n return cart.totalQuantity\n })\n\n hbs.registerHelper('cartTotal', function (): string {\n const cart = getCart()\n if (!cart) return ''\n return new Intl.NumberFormat(defaults.locale, {\n style: 'currency',\n currency: defaults.currency,\n }).format(cart.totalAmountWithTax)\n })\n\n hbs.registerHelper('cartTotalRaw', function (): number {\n const cart = getCart()\n return cart?.totalAmountWithTax ?? 0\n })\n\n hbs.registerHelper('cartHasItems', function (this: unknown, options: Handlebars.HelperOptions): string {\n const cart = getCart()\n const hasItems = cart ? cart.totalQuantity > 0 : false\n return hasItems ? options.fn(this as object) : options.inverse(this as object)\n })\n\n hbs.registerHelper('cartLineCount', function (): number {\n const cart = getCart()\n if (!cart?.content) return 0\n return cart.content.reduce((sum: number, group: CartContent) => sum + (group.lines?.length ?? 0), 0)\n })\n}\n","export default \"<article class=\\\"altz-product-card{{#unless (isAvailable availability)}} altz-product-card--unavailable{{/unless}}\\\">\\n {{#if imageUrl}}\\n <a href=\\\"{{productUrl reference}}\\\" class=\\\"altz-product-card__image-link\\\" aria-label=\\\"{{name}}\\\">\\n <img\\n src=\\\"{{thumbnailUrl imageUrl 400}}\\\"\\n alt=\\\"{{name}}\\\"\\n class=\\\"altz-product-card__image\\\"\\n loading=\\\"lazy\\\"\\n width=\\\"400\\\"\\n />\\n </a>\\n {{/if}}\\n\\n <div class=\\\"altz-product-card__body\\\">\\n <a href=\\\"{{productUrl reference}}\\\" class=\\\"altz-product-card__title\\\">\\n {{name}}\\n </a>\\n\\n <div class=\\\"altz-product-card__pricing\\\">\\n {{#if discount}}\\n <span class=\\\"altz-product-card__price altz-product-card__price--discounted\\\">\\n {{formatPrice price}}\\n </span>\\n <span class=\\\"altz-product-card__price altz-product-card__price--original\\\">\\n {{formatPrice originalPrice}}\\n </span>\\n <span class=\\\"altz-product-card__discount-badge\\\">\\n {{discountPercent originalPrice price}}\\n </span>\\n {{else}}\\n <span class=\\\"altz-product-card__price\\\">\\n {{formatPrice price}}\\n </span>\\n {{/if}}\\n </div>\\n\\n {{#unless (isAvailable availability)}}\\n <p class=\\\"altz-product-card__unavailable\\\">{{unavailableLabel}}</p>\\n {{/unless}}\\n\\n {{#if (isAvailable availability)}}\\n <button\\n class=\\\"altz-product-card__add-to-cart\\\"\\n hx-post=\\\"/commerce/api/process/cart/lines\\\"\\n hx-vals='{\\\"productReference\\\":\\\"{{reference}}\\\",\\\"quantity\\\":1}'\\n hx-target=\\\"#altz-cart-summary\\\"\\n hx-swap=\\\"outerHTML\\\"\\n hx-indicator=\\\".altz-spinner\\\"\\n >\\n {{addToCartLabel}}\\n </button>\\n {{/if}}\\n </div>\\n</article>\\n\"","export default \"<div id=\\\"altz-cart-mini\\\" class=\\\"altz-cart-mini\\\">\\n <div class=\\\"altz-cart-mini__header\\\">\\n <span class=\\\"altz-cart-mini__label\\\">{{cartLabel}}</span>\\n <span class=\\\"altz-cart-mini__count\\\">{{cartCount}}</span>\\n </div>\\n\\n {{#cartHasItems}}\\n <ul class=\\\"altz-cart-mini__lines\\\">\\n {{#each lines}}\\n <li class=\\\"altz-cart-mini__line\\\">\\n <span class=\\\"altz-cart-mini__line-name\\\">{{this.productName}}</span>\\n <span class=\\\"altz-cart-mini__line-qty\\\">× {{this.quantity}}</span>\\n <span class=\\\"altz-cart-mini__line-price\\\">{{formatPrice this.unitPriceWithTax}}</span>\\n </li>\\n {{/each}}\\n </ul>\\n\\n <div class=\\\"altz-cart-mini__total\\\">\\n <span>{{totalLabel}}</span>\\n <strong>{{cartTotal}}</strong>\\n </div>\\n\\n <a href=\\\"{{cartUrl}}\\\" class=\\\"altz-cart-mini__cta\\\">\\n {{viewCartLabel}}\\n </a>\\n {{/cartHasItems}}\\n\\n {{^cartHasItems}}\\n <p class=\\\"altz-cart-mini__empty\\\">{{emptyLabel}}</p>\\n {{/cartHasItems}}\\n</div>\\n\"","export default \"<section class=\\\"altz-product-list\\\">\\n {{#if title}}\\n <h2 class=\\\"altz-product-list__title\\\">{{title}}</h2>\\n {{/if}}\\n\\n {{#if products.length}}\\n <ul class=\\\"altz-product-list__grid\\\" role=\\\"list\\\">\\n {{#each products}}\\n <li class=\\\"altz-product-list__item\\\">\\n <article class=\\\"altz-product-card{{#unless (isAvailable this.availability)}} altz-product-card--unavailable{{/unless}}\\\">\\n {{#if this.imageUrl}}\\n <a href=\\\"{{productUrl this.reference}}\\\" class=\\\"altz-product-card__image-link\\\" aria-label=\\\"{{this.name}}\\\">\\n <img\\n src=\\\"{{thumbnailUrl this.imageUrl 300}}\\\"\\n alt=\\\"{{this.name}}\\\"\\n class=\\\"altz-product-card__image\\\"\\n loading=\\\"lazy\\\"\\n width=\\\"300\\\"\\n />\\n </a>\\n {{/if}}\\n\\n <div class=\\\"altz-product-card__body\\\">\\n <a href=\\\"{{productUrl this.reference}}\\\" class=\\\"altz-product-card__title\\\">\\n {{this.name}}\\n </a>\\n\\n <div class=\\\"altz-product-card__pricing\\\">\\n {{#if this.discount}}\\n <span class=\\\"altz-product-card__price altz-product-card__price--discounted\\\">\\n {{formatPrice this.price}}\\n </span>\\n <span class=\\\"altz-product-card__price altz-product-card__price--original\\\">\\n {{formatPrice this.originalPrice}}\\n </span>\\n <span class=\\\"altz-product-card__discount-badge\\\">\\n {{discountPercent this.originalPrice this.price}}\\n </span>\\n {{else}}\\n <span class=\\\"altz-product-card__price\\\">\\n {{formatPrice this.price}}\\n </span>\\n {{/if}}\\n </div>\\n\\n {{#if (isAvailable this.availability)}}\\n <button\\n class=\\\"altz-product-card__add-to-cart\\\"\\n hx-post=\\\"/commerce/api/process/cart/lines\\\"\\n hx-vals='{\\\"productReference\\\":\\\"{{this.reference}}\\\",\\\"quantity\\\":1}'\\n hx-target=\\\"#altz-cart-summary\\\"\\n hx-swap=\\\"outerHTML\\\"\\n hx-indicator=\\\".altz-spinner\\\"\\n >\\n {{../addToCartLabel}}\\n </button>\\n {{/if}}\\n </div>\\n </article>\\n </li>\\n {{/each}}\\n </ul>\\n {{else}}\\n <p class=\\\"altz-product-list__empty\\\">{{emptyLabel}}</p>\\n {{/if}}\\n</section>\\n\"","import type { CommerceClient, Cart, QueueEvent } from '@altazion/commerce-sdk-core'\nimport type Handlebars from 'handlebars'\n\nimport { registerAltazionAuthExtension } from './extensions/altazion-auth'\nimport { registerPriceHelpers } from './helpers/price'\nimport { registerProductHelpers } from './helpers/product'\nimport { registerCartHelpers } from './helpers/cart'\n\n// Templates pré-compilés importés en tant que chaînes brutes\nimport productCardTemplate from './templates/product-card.hbs?raw'\nimport cartMiniTemplate from './templates/cart-mini.hbs?raw'\nimport productListTemplate from './templates/product-list.hbs?raw'\n\nexport type { Cart }\n\nexport interface AltazionHtmxConfig {\n /** Instance CommerceClient déjà initialisée */\n client: CommerceClient\n /**\n * Instance Handlebars (global ou importée).\n * Si omis, les helpers ne sont pas enregistrés.\n */\n handlebars?: typeof Handlebars\n /**\n * Sélecteur CSS de l'élément DOM à basculer avec la classe `altz-offline`\n * lorsque la connectivité est perdue/restaurée.\n * @default 'body'\n */\n offlineSelector?: string\n}\n\nexport interface AltazionHtmxInstance {\n /** Templates Handlebars compilés prêts à l'emploi */\n templates: {\n productCard: HandlebarsTemplateDelegate\n cartMini: HandlebarsTemplateDelegate\n productList: HandlebarsTemplateDelegate\n }\n /** Détruit les listeners et libère les ressources */\n dispose(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype HandlebarsTemplateDelegate = (context?: any) => string\n\n/**\n * Initialise le SDK HTMX Altazion.\n *\n * - Enregistre l'extension HTMX (headers contexte) si htmx est présent\n * - Enregistre les helpers Handlebars (prix, produits, panier) si hbs est fourni\n * - Active l'indicateur d'état offline sur le DOM\n * - Retourne les templates compilés\n *\n * @example\n * ```ts\n * import Handlebars from 'handlebars'\n * const sdk = init({ client, handlebars: Handlebars })\n * const html = sdk.templates.productCard(product)\n * document.getElementById('product')!.innerHTML = html\n * ```\n */\nexport function init(config: AltazionHtmxConfig): AltazionHtmxInstance {\n const { client, handlebars, offlineSelector = 'body' } = config\n\n // 1. Extension HTMX — headers de contexte sur chaque requête\n registerAltazionAuthExtension(client)\n\n // 2. HTMX config globale — transmettre les cookies sur les requêtes XHR HTMX\n if (typeof window !== 'undefined' && window.htmx) {\n // @ts-expect-error — htmx.config n'est pas dans les types window\n window.htmx.config = window.htmx.config ?? {}\n // @ts-expect-error\n window.htmx.config.withCredentials = true\n }\n\n // 3. Helpers Handlebars\n const defaults = {\n locale: client.context.locale,\n currency: client.context.currency,\n }\n\n // Getter réactif sur le panier (mis à jour lors du flush offline)\n let currentCart: Cart | null = null\n const getCart = (): Cart | null => currentCart\n\n // Abonnement aux events panier pour mettre à jour le getter\n client.onQueueEvent(async (event: QueueEvent) => {\n if (event.type === 'flushed') {\n try {\n currentCart = await client.cart.getCart()\n } catch {\n // Panier indisponible après flush — on ignore silencieusement\n }\n }\n })\n\n if (handlebars) {\n registerPriceHelpers(handlebars, defaults)\n registerProductHelpers(handlebars, client.context.siteUrl ?? '')\n registerCartHelpers(handlebars, getCart, defaults)\n }\n\n // 4. Indicateur offline DOM\n const offlineEl = typeof document !== 'undefined'\n ? document.querySelector(offlineSelector)\n : null\n\n const updateOfflineClass = (): void => {\n offlineEl?.classList.toggle('altz-offline', client.isOffline)\n }\n\n // Synchronisation initiale\n updateOfflineClass()\n\n // Abonnement ConnectivityManager — retourne la fonction de désabonnement\n const unsubscribeConnectivity = client.connectivity?.subscribe(() => updateOfflineClass())\n\n // 5. Templates compilés (retournés pour usage dans le code applicatif)\n const noHbs = (): HandlebarsTemplateDelegate =>\n () => '<!-- handlebars non initialisé dans @altazion/commerce-sdk-htmx -->'\n\n const templates = handlebars\n ? {\n productCard: handlebars.compile(productCardTemplate),\n cartMini: handlebars.compile(cartMiniTemplate),\n productList: handlebars.compile(productListTemplate),\n }\n : {\n productCard: noHbs(),\n cartMini: noHbs(),\n productList: noHbs(),\n }\n\n return {\n templates,\n dispose(): void {\n unsubscribeConnectivity?.()\n },\n }\n}\n\n// Re-exports helpers pour usage à la carte\nexport { registerPriceHelpers } from './helpers/price'\nexport { registerProductHelpers } from './helpers/product'\nexport { registerCartHelpers } from './helpers/cart'\nexport { registerAltazionAuthExtension } from './extensions/altazion-auth'\n"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
function registerAltazionAuthExtension(client) {
|
|
2
|
+
if (typeof window === "undefined" || !window.htmx) {
|
|
3
|
+
console.warn("[AltazionHtmx] htmx non disponible sur window — extension non enregistrée");
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const handler = (event) => {
|
|
7
|
+
const e = event;
|
|
8
|
+
e.detail.headers["Accept-Language"] = client.context.locale;
|
|
9
|
+
e.detail.headers["X-Altazion-Currency"] = client.context.currency;
|
|
10
|
+
};
|
|
11
|
+
window.htmx.on("htmx:configRequest", handler);
|
|
12
|
+
}
|
|
13
|
+
function registerPriceHelpers(hbs, defaults) {
|
|
14
|
+
hbs.registerHelper(
|
|
15
|
+
"formatPrice",
|
|
16
|
+
function(value, currency, locale) {
|
|
17
|
+
const amount = typeof value === "number" ? value : parseFloat(String(value));
|
|
18
|
+
if (isNaN(amount)) return "";
|
|
19
|
+
const resolvedCurrency = typeof currency === "string" ? currency : defaults.currency;
|
|
20
|
+
const resolvedLocale = typeof locale === "string" ? locale : defaults.locale;
|
|
21
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
22
|
+
style: "currency",
|
|
23
|
+
currency: resolvedCurrency
|
|
24
|
+
}).format(amount);
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
hbs.registerHelper(
|
|
28
|
+
"formatPriceCompact",
|
|
29
|
+
function(value, currency, locale) {
|
|
30
|
+
const amount = typeof value === "number" ? value : parseFloat(String(value));
|
|
31
|
+
if (isNaN(amount)) return "";
|
|
32
|
+
const resolvedCurrency = typeof currency === "string" ? currency : defaults.currency;
|
|
33
|
+
const resolvedLocale = typeof locale === "string" ? locale : defaults.locale;
|
|
34
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
35
|
+
style: "currency",
|
|
36
|
+
currency: resolvedCurrency,
|
|
37
|
+
notation: "compact"
|
|
38
|
+
}).format(amount);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
hbs.registerHelper(
|
|
42
|
+
"discountPercent",
|
|
43
|
+
function(original, discounted) {
|
|
44
|
+
const orig = typeof original === "number" ? original : parseFloat(String(original));
|
|
45
|
+
const disc = typeof discounted === "number" ? discounted : parseFloat(String(discounted));
|
|
46
|
+
if (isNaN(orig) || isNaN(disc) || orig === 0) return "";
|
|
47
|
+
const pct = Math.round((orig - disc) / orig * 100);
|
|
48
|
+
return `-${pct}%`;
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function registerProductHelpers(hbs, siteUrl) {
|
|
53
|
+
hbs.registerHelper("productUrl", function(reference) {
|
|
54
|
+
if (typeof reference !== "string" && typeof reference !== "number") return "";
|
|
55
|
+
const base = siteUrl.replace(/\/$/, "");
|
|
56
|
+
return `${base}/product/${encodeURIComponent(String(reference))}`;
|
|
57
|
+
});
|
|
58
|
+
hbs.registerHelper("thumbnailUrl", function(imageUrl, size) {
|
|
59
|
+
if (typeof imageUrl !== "string" || !imageUrl) return "";
|
|
60
|
+
const resolvedSize = typeof size === "number" ? size : 300;
|
|
61
|
+
const separator = imageUrl.includes("?") ? "&" : "?";
|
|
62
|
+
return `${imageUrl}${separator}w=${resolvedSize}`;
|
|
63
|
+
});
|
|
64
|
+
hbs.registerHelper("isAvailable", function(availability) {
|
|
65
|
+
if (availability === null || availability === void 0) return true;
|
|
66
|
+
if (typeof availability === "boolean") return availability;
|
|
67
|
+
if (typeof availability === "number") return availability > 0;
|
|
68
|
+
if (typeof availability === "string") {
|
|
69
|
+
const lower = availability.toLowerCase();
|
|
70
|
+
return lower === "available" || lower === "instock";
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
});
|
|
74
|
+
hbs.registerHelper(
|
|
75
|
+
"ifCond",
|
|
76
|
+
function(v1, operator, v2, options) {
|
|
77
|
+
let result = false;
|
|
78
|
+
switch (operator) {
|
|
79
|
+
case "==":
|
|
80
|
+
result = v1 == v2;
|
|
81
|
+
break;
|
|
82
|
+
case "===":
|
|
83
|
+
result = v1 === v2;
|
|
84
|
+
break;
|
|
85
|
+
case "!=":
|
|
86
|
+
result = v1 != v2;
|
|
87
|
+
break;
|
|
88
|
+
case "!==":
|
|
89
|
+
result = v1 !== v2;
|
|
90
|
+
break;
|
|
91
|
+
case ">":
|
|
92
|
+
result = v1 > v2;
|
|
93
|
+
break;
|
|
94
|
+
case "<":
|
|
95
|
+
result = v1 < v2;
|
|
96
|
+
break;
|
|
97
|
+
case ">=":
|
|
98
|
+
result = v1 >= v2;
|
|
99
|
+
break;
|
|
100
|
+
case "<=":
|
|
101
|
+
result = v1 <= v2;
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
result = false;
|
|
105
|
+
}
|
|
106
|
+
return result ? options.fn(this) : options.inverse(this);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
function registerCartHelpers(hbs, getCart, defaults) {
|
|
111
|
+
hbs.registerHelper("cartCount", function() {
|
|
112
|
+
const cart = getCart();
|
|
113
|
+
if (!cart) return 0;
|
|
114
|
+
return cart.totalQuantity;
|
|
115
|
+
});
|
|
116
|
+
hbs.registerHelper("cartTotal", function() {
|
|
117
|
+
const cart = getCart();
|
|
118
|
+
if (!cart) return "";
|
|
119
|
+
return new Intl.NumberFormat(defaults.locale, {
|
|
120
|
+
style: "currency",
|
|
121
|
+
currency: defaults.currency
|
|
122
|
+
}).format(cart.totalAmountWithTax);
|
|
123
|
+
});
|
|
124
|
+
hbs.registerHelper("cartTotalRaw", function() {
|
|
125
|
+
const cart = getCart();
|
|
126
|
+
return (cart == null ? void 0 : cart.totalAmountWithTax) ?? 0;
|
|
127
|
+
});
|
|
128
|
+
hbs.registerHelper("cartHasItems", function(options) {
|
|
129
|
+
const cart = getCart();
|
|
130
|
+
const hasItems = cart ? cart.totalQuantity > 0 : false;
|
|
131
|
+
return hasItems ? options.fn(this) : options.inverse(this);
|
|
132
|
+
});
|
|
133
|
+
hbs.registerHelper("cartLineCount", function() {
|
|
134
|
+
const cart = getCart();
|
|
135
|
+
if (!(cart == null ? void 0 : cart.content)) return 0;
|
|
136
|
+
return cart.content.reduce((sum, group) => {
|
|
137
|
+
var _a;
|
|
138
|
+
return sum + (((_a = group.lines) == null ? void 0 : _a.length) ?? 0);
|
|
139
|
+
}, 0);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const productCardTemplate = `<article class="altz-product-card{{#unless (isAvailable availability)}} altz-product-card--unavailable{{/unless}}">
|
|
143
|
+
{{#if imageUrl}}
|
|
144
|
+
<a href="{{productUrl reference}}" class="altz-product-card__image-link" aria-label="{{name}}">
|
|
145
|
+
<img
|
|
146
|
+
src="{{thumbnailUrl imageUrl 400}}"
|
|
147
|
+
alt="{{name}}"
|
|
148
|
+
class="altz-product-card__image"
|
|
149
|
+
loading="lazy"
|
|
150
|
+
width="400"
|
|
151
|
+
/>
|
|
152
|
+
</a>
|
|
153
|
+
{{/if}}
|
|
154
|
+
|
|
155
|
+
<div class="altz-product-card__body">
|
|
156
|
+
<a href="{{productUrl reference}}" class="altz-product-card__title">
|
|
157
|
+
{{name}}
|
|
158
|
+
</a>
|
|
159
|
+
|
|
160
|
+
<div class="altz-product-card__pricing">
|
|
161
|
+
{{#if discount}}
|
|
162
|
+
<span class="altz-product-card__price altz-product-card__price--discounted">
|
|
163
|
+
{{formatPrice price}}
|
|
164
|
+
</span>
|
|
165
|
+
<span class="altz-product-card__price altz-product-card__price--original">
|
|
166
|
+
{{formatPrice originalPrice}}
|
|
167
|
+
</span>
|
|
168
|
+
<span class="altz-product-card__discount-badge">
|
|
169
|
+
{{discountPercent originalPrice price}}
|
|
170
|
+
</span>
|
|
171
|
+
{{else}}
|
|
172
|
+
<span class="altz-product-card__price">
|
|
173
|
+
{{formatPrice price}}
|
|
174
|
+
</span>
|
|
175
|
+
{{/if}}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{{#unless (isAvailable availability)}}
|
|
179
|
+
<p class="altz-product-card__unavailable">{{unavailableLabel}}</p>
|
|
180
|
+
{{/unless}}
|
|
181
|
+
|
|
182
|
+
{{#if (isAvailable availability)}}
|
|
183
|
+
<button
|
|
184
|
+
class="altz-product-card__add-to-cart"
|
|
185
|
+
hx-post="/commerce/api/process/cart/lines"
|
|
186
|
+
hx-vals='{"productReference":"{{reference}}","quantity":1}'
|
|
187
|
+
hx-target="#altz-cart-summary"
|
|
188
|
+
hx-swap="outerHTML"
|
|
189
|
+
hx-indicator=".altz-spinner"
|
|
190
|
+
>
|
|
191
|
+
{{addToCartLabel}}
|
|
192
|
+
</button>
|
|
193
|
+
{{/if}}
|
|
194
|
+
</div>
|
|
195
|
+
</article>
|
|
196
|
+
`;
|
|
197
|
+
const cartMiniTemplate = '<div id="altz-cart-mini" class="altz-cart-mini">\n <div class="altz-cart-mini__header">\n <span class="altz-cart-mini__label">{{cartLabel}}</span>\n <span class="altz-cart-mini__count">{{cartCount}}</span>\n </div>\n\n {{#cartHasItems}}\n <ul class="altz-cart-mini__lines">\n {{#each lines}}\n <li class="altz-cart-mini__line">\n <span class="altz-cart-mini__line-name">{{this.productName}}</span>\n <span class="altz-cart-mini__line-qty">× {{this.quantity}}</span>\n <span class="altz-cart-mini__line-price">{{formatPrice this.unitPriceWithTax}}</span>\n </li>\n {{/each}}\n </ul>\n\n <div class="altz-cart-mini__total">\n <span>{{totalLabel}}</span>\n <strong>{{cartTotal}}</strong>\n </div>\n\n <a href="{{cartUrl}}" class="altz-cart-mini__cta">\n {{viewCartLabel}}\n </a>\n {{/cartHasItems}}\n\n {{^cartHasItems}}\n <p class="altz-cart-mini__empty">{{emptyLabel}}</p>\n {{/cartHasItems}}\n</div>\n';
|
|
198
|
+
const productListTemplate = `<section class="altz-product-list">
|
|
199
|
+
{{#if title}}
|
|
200
|
+
<h2 class="altz-product-list__title">{{title}}</h2>
|
|
201
|
+
{{/if}}
|
|
202
|
+
|
|
203
|
+
{{#if products.length}}
|
|
204
|
+
<ul class="altz-product-list__grid" role="list">
|
|
205
|
+
{{#each products}}
|
|
206
|
+
<li class="altz-product-list__item">
|
|
207
|
+
<article class="altz-product-card{{#unless (isAvailable this.availability)}} altz-product-card--unavailable{{/unless}}">
|
|
208
|
+
{{#if this.imageUrl}}
|
|
209
|
+
<a href="{{productUrl this.reference}}" class="altz-product-card__image-link" aria-label="{{this.name}}">
|
|
210
|
+
<img
|
|
211
|
+
src="{{thumbnailUrl this.imageUrl 300}}"
|
|
212
|
+
alt="{{this.name}}"
|
|
213
|
+
class="altz-product-card__image"
|
|
214
|
+
loading="lazy"
|
|
215
|
+
width="300"
|
|
216
|
+
/>
|
|
217
|
+
</a>
|
|
218
|
+
{{/if}}
|
|
219
|
+
|
|
220
|
+
<div class="altz-product-card__body">
|
|
221
|
+
<a href="{{productUrl this.reference}}" class="altz-product-card__title">
|
|
222
|
+
{{this.name}}
|
|
223
|
+
</a>
|
|
224
|
+
|
|
225
|
+
<div class="altz-product-card__pricing">
|
|
226
|
+
{{#if this.discount}}
|
|
227
|
+
<span class="altz-product-card__price altz-product-card__price--discounted">
|
|
228
|
+
{{formatPrice this.price}}
|
|
229
|
+
</span>
|
|
230
|
+
<span class="altz-product-card__price altz-product-card__price--original">
|
|
231
|
+
{{formatPrice this.originalPrice}}
|
|
232
|
+
</span>
|
|
233
|
+
<span class="altz-product-card__discount-badge">
|
|
234
|
+
{{discountPercent this.originalPrice this.price}}
|
|
235
|
+
</span>
|
|
236
|
+
{{else}}
|
|
237
|
+
<span class="altz-product-card__price">
|
|
238
|
+
{{formatPrice this.price}}
|
|
239
|
+
</span>
|
|
240
|
+
{{/if}}
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
{{#if (isAvailable this.availability)}}
|
|
244
|
+
<button
|
|
245
|
+
class="altz-product-card__add-to-cart"
|
|
246
|
+
hx-post="/commerce/api/process/cart/lines"
|
|
247
|
+
hx-vals='{"productReference":"{{this.reference}}","quantity":1}'
|
|
248
|
+
hx-target="#altz-cart-summary"
|
|
249
|
+
hx-swap="outerHTML"
|
|
250
|
+
hx-indicator=".altz-spinner"
|
|
251
|
+
>
|
|
252
|
+
{{../addToCartLabel}}
|
|
253
|
+
</button>
|
|
254
|
+
{{/if}}
|
|
255
|
+
</div>
|
|
256
|
+
</article>
|
|
257
|
+
</li>
|
|
258
|
+
{{/each}}
|
|
259
|
+
</ul>
|
|
260
|
+
{{else}}
|
|
261
|
+
<p class="altz-product-list__empty">{{emptyLabel}}</p>
|
|
262
|
+
{{/if}}
|
|
263
|
+
</section>
|
|
264
|
+
`;
|
|
265
|
+
function init(config) {
|
|
266
|
+
var _a;
|
|
267
|
+
const { client, handlebars, offlineSelector = "body" } = config;
|
|
268
|
+
registerAltazionAuthExtension(client);
|
|
269
|
+
if (typeof window !== "undefined" && window.htmx) {
|
|
270
|
+
window.htmx.config = window.htmx.config ?? {};
|
|
271
|
+
window.htmx.config.withCredentials = true;
|
|
272
|
+
}
|
|
273
|
+
const defaults = {
|
|
274
|
+
locale: client.context.locale,
|
|
275
|
+
currency: client.context.currency
|
|
276
|
+
};
|
|
277
|
+
let currentCart = null;
|
|
278
|
+
const getCart = () => currentCart;
|
|
279
|
+
client.onQueueEvent(async (event) => {
|
|
280
|
+
if (event.type === "flushed") {
|
|
281
|
+
try {
|
|
282
|
+
currentCart = await client.cart.getCart();
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
if (handlebars) {
|
|
288
|
+
registerPriceHelpers(handlebars, defaults);
|
|
289
|
+
registerProductHelpers(handlebars, client.context.siteUrl ?? "");
|
|
290
|
+
registerCartHelpers(handlebars, getCart, defaults);
|
|
291
|
+
}
|
|
292
|
+
const offlineEl = typeof document !== "undefined" ? document.querySelector(offlineSelector) : null;
|
|
293
|
+
const updateOfflineClass = () => {
|
|
294
|
+
offlineEl == null ? void 0 : offlineEl.classList.toggle("altz-offline", client.isOffline);
|
|
295
|
+
};
|
|
296
|
+
updateOfflineClass();
|
|
297
|
+
const unsubscribeConnectivity = (_a = client.connectivity) == null ? void 0 : _a.subscribe(() => updateOfflineClass());
|
|
298
|
+
const noHbs = () => () => "<!-- handlebars non initialisé dans @altazion/commerce-sdk-htmx -->";
|
|
299
|
+
const templates = handlebars ? {
|
|
300
|
+
productCard: handlebars.compile(productCardTemplate),
|
|
301
|
+
cartMini: handlebars.compile(cartMiniTemplate),
|
|
302
|
+
productList: handlebars.compile(productListTemplate)
|
|
303
|
+
} : {
|
|
304
|
+
productCard: noHbs(),
|
|
305
|
+
cartMini: noHbs(),
|
|
306
|
+
productList: noHbs()
|
|
307
|
+
};
|
|
308
|
+
return {
|
|
309
|
+
templates,
|
|
310
|
+
dispose() {
|
|
311
|
+
unsubscribeConnectivity == null ? void 0 : unsubscribeConnectivity();
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
export {
|
|
316
|
+
init,
|
|
317
|
+
registerAltazionAuthExtension,
|
|
318
|
+
registerCartHelpers,
|
|
319
|
+
registerPriceHelpers,
|
|
320
|
+
registerProductHelpers
|
|
321
|
+
};
|
|
322
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/extensions/altazion-auth.ts","../src/helpers/price.ts","../src/helpers/product.ts","../src/helpers/cart.ts","../src/templates/product-card.hbs?raw","../src/templates/cart-mini.hbs?raw","../src/templates/product-list.hbs?raw","../src/index.ts"],"sourcesContent":["import type { CommerceClient } from '@altazion/commerce-sdk-core'\n\ndeclare global {\n interface Window {\n htmx?: {\n on(eventName: string, listener: (event: Event) => void): void\n off(eventName: string, listener: (event: Event) => void): void\n }\n }\n}\n\ninterface HtmxConfigRequestEvent extends Event {\n detail: {\n headers: Record<string, string>\n elt: Element\n }\n}\n\n/**\n * Extension HTMX qui injecte automatiquement les headers de contexte Altazion\n * sur chaque requête HTMX.\n *\n * En modes browser et kiosk, les cookies sont transmis via credentials:'include'\n * par le navigateur. Cette extension ajoute uniquement les headers de contexte\n * (locale, currency) utiles côté serveur.\n */\nexport function registerAltazionAuthExtension(client: CommerceClient): void {\n if (typeof window === 'undefined' || !window.htmx) {\n console.warn('[AltazionHtmx] htmx non disponible sur window — extension non enregistrée')\n return\n }\n\n const handler = (event: Event): void => {\n const e = event as HtmxConfigRequestEvent\n e.detail.headers['Accept-Language'] = client.context.locale\n e.detail.headers['X-Altazion-Currency'] = client.context.currency\n }\n\n window.htmx.on('htmx:configRequest', handler)\n}\n","import type Handlebars from 'handlebars'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Formate un montant selon la locale et la devise.\n * Usage : {{formatPrice 19.99 \"EUR\" \"fr-FR\"}}\n * Si locale/currency sont omis, utilise les valeurs passées au registre.\n */\nexport function registerPriceHelpers(\n hbs: HandlebarsInstance,\n defaults: { locale: string; currency: string }\n): void {\n hbs.registerHelper(\n 'formatPrice',\n function (\n value: unknown,\n currency?: unknown,\n locale?: unknown\n ): string {\n const amount = typeof value === 'number' ? value : parseFloat(String(value))\n if (isNaN(amount)) return ''\n\n const resolvedCurrency =\n typeof currency === 'string' ? currency : defaults.currency\n const resolvedLocale =\n typeof locale === 'string' ? locale : defaults.locale\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency: resolvedCurrency,\n }).format(amount)\n }\n )\n\n hbs.registerHelper(\n 'formatPriceCompact',\n function (value: unknown, currency?: unknown, locale?: unknown): string {\n const amount = typeof value === 'number' ? value : parseFloat(String(value))\n if (isNaN(amount)) return ''\n\n const resolvedCurrency =\n typeof currency === 'string' ? currency : defaults.currency\n const resolvedLocale =\n typeof locale === 'string' ? locale : defaults.locale\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency: resolvedCurrency,\n notation: 'compact',\n }).format(amount)\n }\n )\n\n hbs.registerHelper(\n 'discountPercent',\n function (original: unknown, discounted: unknown): string {\n const orig = typeof original === 'number' ? original : parseFloat(String(original))\n const disc = typeof discounted === 'number' ? discounted : parseFloat(String(discounted))\n if (isNaN(orig) || isNaN(disc) || orig === 0) return ''\n const pct = Math.round(((orig - disc) / orig) * 100)\n return `-${pct}%`\n }\n )\n}\n","import type Handlebars from 'handlebars'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Helpers Handlebars liés aux produits.\n *\n * Usage :\n * {{productUrl reference}}\n * {{thumbnailUrl imageUrl 400}}\n * {{#if (isAvailable availability)}} ... {{/if}}\n */\nexport function registerProductHelpers(hbs: HandlebarsInstance, siteUrl: string): void {\n // URL de la fiche produit côté site vitrine\n hbs.registerHelper('productUrl', function (reference: unknown): string {\n if (typeof reference !== 'string' && typeof reference !== 'number') return ''\n const base = siteUrl.replace(/\\/$/, '')\n return `${base}/product/${encodeURIComponent(String(reference))}`\n })\n\n // Transforme une URL d'image en URL redimensionnée si le serveur le supporte\n // Pattern attendu : /media/images/{id} → /media/images/{id}?w={size}\n hbs.registerHelper('thumbnailUrl', function (imageUrl: unknown, size?: unknown): string {\n if (typeof imageUrl !== 'string' || !imageUrl) return ''\n const resolvedSize = typeof size === 'number' ? size : 300\n const separator = imageUrl.includes('?') ? '&' : '?'\n return `${imageUrl}${separator}w=${resolvedSize}`\n })\n\n // Retourne true si le produit est disponible (availability > 0 ou pas de stock géré)\n hbs.registerHelper('isAvailable', function (availability: unknown): boolean {\n if (availability === null || availability === undefined) return true\n if (typeof availability === 'boolean') return availability\n if (typeof availability === 'number') return availability > 0\n if (typeof availability === 'string') {\n const lower = availability.toLowerCase()\n return lower === 'available' || lower === 'instock'\n }\n return false\n })\n\n // Helper ifCond simple pour les comparaisons dans les templates\n hbs.registerHelper(\n 'ifCond',\n function (\n this: unknown,\n v1: unknown,\n operator: string,\n v2: unknown,\n options: Handlebars.HelperOptions\n ): string {\n let result = false\n switch (operator) {\n case '==': result = v1 == v2; break\n case '===': result = v1 === v2; break\n case '!=': result = v1 != v2; break\n case '!==': result = v1 !== v2; break\n case '>': result = (v1 as number) > (v2 as number); break\n case '<': result = (v1 as number) < (v2 as number); break\n case '>=': result = (v1 as number) >= (v2 as number); break\n case '<=': result = (v1 as number) <= (v2 as number); break\n default: result = false\n }\n return result ? options.fn(this as object) : options.inverse(this as object)\n }\n )\n}\n","import type Handlebars from 'handlebars'\nimport type { Cart, CartContent } from '@altazion/commerce-sdk-core'\n\ntype HandlebarsInstance = typeof Handlebars\n\n/**\n * Helpers Handlebars liés au panier.\n *\n * Nécessite un getter réactif sur le panier actuel.\n * Usage :\n * Nombre d'articles : {{cartCount}}\n * Total : {{cartTotal}}\n * {{#if cartHasItems}} ... {{/if}}\n */\nexport function registerCartHelpers(\n hbs: HandlebarsInstance,\n getCart: () => Cart | null,\n defaults: { locale: string; currency: string }\n): void {\n hbs.registerHelper('cartCount', function (): number {\n const cart = getCart()\n if (!cart) return 0\n return cart.totalQuantity\n })\n\n hbs.registerHelper('cartTotal', function (): string {\n const cart = getCart()\n if (!cart) return ''\n return new Intl.NumberFormat(defaults.locale, {\n style: 'currency',\n currency: defaults.currency,\n }).format(cart.totalAmountWithTax)\n })\n\n hbs.registerHelper('cartTotalRaw', function (): number {\n const cart = getCart()\n return cart?.totalAmountWithTax ?? 0\n })\n\n hbs.registerHelper('cartHasItems', function (this: unknown, options: Handlebars.HelperOptions): string {\n const cart = getCart()\n const hasItems = cart ? cart.totalQuantity > 0 : false\n return hasItems ? options.fn(this as object) : options.inverse(this as object)\n })\n\n hbs.registerHelper('cartLineCount', function (): number {\n const cart = getCart()\n if (!cart?.content) return 0\n return cart.content.reduce((sum: number, group: CartContent) => sum + (group.lines?.length ?? 0), 0)\n })\n}\n","export default \"<article class=\\\"altz-product-card{{#unless (isAvailable availability)}} altz-product-card--unavailable{{/unless}}\\\">\\n {{#if imageUrl}}\\n <a href=\\\"{{productUrl reference}}\\\" class=\\\"altz-product-card__image-link\\\" aria-label=\\\"{{name}}\\\">\\n <img\\n src=\\\"{{thumbnailUrl imageUrl 400}}\\\"\\n alt=\\\"{{name}}\\\"\\n class=\\\"altz-product-card__image\\\"\\n loading=\\\"lazy\\\"\\n width=\\\"400\\\"\\n />\\n </a>\\n {{/if}}\\n\\n <div class=\\\"altz-product-card__body\\\">\\n <a href=\\\"{{productUrl reference}}\\\" class=\\\"altz-product-card__title\\\">\\n {{name}}\\n </a>\\n\\n <div class=\\\"altz-product-card__pricing\\\">\\n {{#if discount}}\\n <span class=\\\"altz-product-card__price altz-product-card__price--discounted\\\">\\n {{formatPrice price}}\\n </span>\\n <span class=\\\"altz-product-card__price altz-product-card__price--original\\\">\\n {{formatPrice originalPrice}}\\n </span>\\n <span class=\\\"altz-product-card__discount-badge\\\">\\n {{discountPercent originalPrice price}}\\n </span>\\n {{else}}\\n <span class=\\\"altz-product-card__price\\\">\\n {{formatPrice price}}\\n </span>\\n {{/if}}\\n </div>\\n\\n {{#unless (isAvailable availability)}}\\n <p class=\\\"altz-product-card__unavailable\\\">{{unavailableLabel}}</p>\\n {{/unless}}\\n\\n {{#if (isAvailable availability)}}\\n <button\\n class=\\\"altz-product-card__add-to-cart\\\"\\n hx-post=\\\"/commerce/api/process/cart/lines\\\"\\n hx-vals='{\\\"productReference\\\":\\\"{{reference}}\\\",\\\"quantity\\\":1}'\\n hx-target=\\\"#altz-cart-summary\\\"\\n hx-swap=\\\"outerHTML\\\"\\n hx-indicator=\\\".altz-spinner\\\"\\n >\\n {{addToCartLabel}}\\n </button>\\n {{/if}}\\n </div>\\n</article>\\n\"","export default \"<div id=\\\"altz-cart-mini\\\" class=\\\"altz-cart-mini\\\">\\n <div class=\\\"altz-cart-mini__header\\\">\\n <span class=\\\"altz-cart-mini__label\\\">{{cartLabel}}</span>\\n <span class=\\\"altz-cart-mini__count\\\">{{cartCount}}</span>\\n </div>\\n\\n {{#cartHasItems}}\\n <ul class=\\\"altz-cart-mini__lines\\\">\\n {{#each lines}}\\n <li class=\\\"altz-cart-mini__line\\\">\\n <span class=\\\"altz-cart-mini__line-name\\\">{{this.productName}}</span>\\n <span class=\\\"altz-cart-mini__line-qty\\\">× {{this.quantity}}</span>\\n <span class=\\\"altz-cart-mini__line-price\\\">{{formatPrice this.unitPriceWithTax}}</span>\\n </li>\\n {{/each}}\\n </ul>\\n\\n <div class=\\\"altz-cart-mini__total\\\">\\n <span>{{totalLabel}}</span>\\n <strong>{{cartTotal}}</strong>\\n </div>\\n\\n <a href=\\\"{{cartUrl}}\\\" class=\\\"altz-cart-mini__cta\\\">\\n {{viewCartLabel}}\\n </a>\\n {{/cartHasItems}}\\n\\n {{^cartHasItems}}\\n <p class=\\\"altz-cart-mini__empty\\\">{{emptyLabel}}</p>\\n {{/cartHasItems}}\\n</div>\\n\"","export default \"<section class=\\\"altz-product-list\\\">\\n {{#if title}}\\n <h2 class=\\\"altz-product-list__title\\\">{{title}}</h2>\\n {{/if}}\\n\\n {{#if products.length}}\\n <ul class=\\\"altz-product-list__grid\\\" role=\\\"list\\\">\\n {{#each products}}\\n <li class=\\\"altz-product-list__item\\\">\\n <article class=\\\"altz-product-card{{#unless (isAvailable this.availability)}} altz-product-card--unavailable{{/unless}}\\\">\\n {{#if this.imageUrl}}\\n <a href=\\\"{{productUrl this.reference}}\\\" class=\\\"altz-product-card__image-link\\\" aria-label=\\\"{{this.name}}\\\">\\n <img\\n src=\\\"{{thumbnailUrl this.imageUrl 300}}\\\"\\n alt=\\\"{{this.name}}\\\"\\n class=\\\"altz-product-card__image\\\"\\n loading=\\\"lazy\\\"\\n width=\\\"300\\\"\\n />\\n </a>\\n {{/if}}\\n\\n <div class=\\\"altz-product-card__body\\\">\\n <a href=\\\"{{productUrl this.reference}}\\\" class=\\\"altz-product-card__title\\\">\\n {{this.name}}\\n </a>\\n\\n <div class=\\\"altz-product-card__pricing\\\">\\n {{#if this.discount}}\\n <span class=\\\"altz-product-card__price altz-product-card__price--discounted\\\">\\n {{formatPrice this.price}}\\n </span>\\n <span class=\\\"altz-product-card__price altz-product-card__price--original\\\">\\n {{formatPrice this.originalPrice}}\\n </span>\\n <span class=\\\"altz-product-card__discount-badge\\\">\\n {{discountPercent this.originalPrice this.price}}\\n </span>\\n {{else}}\\n <span class=\\\"altz-product-card__price\\\">\\n {{formatPrice this.price}}\\n </span>\\n {{/if}}\\n </div>\\n\\n {{#if (isAvailable this.availability)}}\\n <button\\n class=\\\"altz-product-card__add-to-cart\\\"\\n hx-post=\\\"/commerce/api/process/cart/lines\\\"\\n hx-vals='{\\\"productReference\\\":\\\"{{this.reference}}\\\",\\\"quantity\\\":1}'\\n hx-target=\\\"#altz-cart-summary\\\"\\n hx-swap=\\\"outerHTML\\\"\\n hx-indicator=\\\".altz-spinner\\\"\\n >\\n {{../addToCartLabel}}\\n </button>\\n {{/if}}\\n </div>\\n </article>\\n </li>\\n {{/each}}\\n </ul>\\n {{else}}\\n <p class=\\\"altz-product-list__empty\\\">{{emptyLabel}}</p>\\n {{/if}}\\n</section>\\n\"","import type { CommerceClient, Cart, QueueEvent } from '@altazion/commerce-sdk-core'\nimport type Handlebars from 'handlebars'\n\nimport { registerAltazionAuthExtension } from './extensions/altazion-auth'\nimport { registerPriceHelpers } from './helpers/price'\nimport { registerProductHelpers } from './helpers/product'\nimport { registerCartHelpers } from './helpers/cart'\n\n// Templates pré-compilés importés en tant que chaînes brutes\nimport productCardTemplate from './templates/product-card.hbs?raw'\nimport cartMiniTemplate from './templates/cart-mini.hbs?raw'\nimport productListTemplate from './templates/product-list.hbs?raw'\n\nexport type { Cart }\n\nexport interface AltazionHtmxConfig {\n /** Instance CommerceClient déjà initialisée */\n client: CommerceClient\n /**\n * Instance Handlebars (global ou importée).\n * Si omis, les helpers ne sont pas enregistrés.\n */\n handlebars?: typeof Handlebars\n /**\n * Sélecteur CSS de l'élément DOM à basculer avec la classe `altz-offline`\n * lorsque la connectivité est perdue/restaurée.\n * @default 'body'\n */\n offlineSelector?: string\n}\n\nexport interface AltazionHtmxInstance {\n /** Templates Handlebars compilés prêts à l'emploi */\n templates: {\n productCard: HandlebarsTemplateDelegate\n cartMini: HandlebarsTemplateDelegate\n productList: HandlebarsTemplateDelegate\n }\n /** Détruit les listeners et libère les ressources */\n dispose(): void\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype HandlebarsTemplateDelegate = (context?: any) => string\n\n/**\n * Initialise le SDK HTMX Altazion.\n *\n * - Enregistre l'extension HTMX (headers contexte) si htmx est présent\n * - Enregistre les helpers Handlebars (prix, produits, panier) si hbs est fourni\n * - Active l'indicateur d'état offline sur le DOM\n * - Retourne les templates compilés\n *\n * @example\n * ```ts\n * import Handlebars from 'handlebars'\n * const sdk = init({ client, handlebars: Handlebars })\n * const html = sdk.templates.productCard(product)\n * document.getElementById('product')!.innerHTML = html\n * ```\n */\nexport function init(config: AltazionHtmxConfig): AltazionHtmxInstance {\n const { client, handlebars, offlineSelector = 'body' } = config\n\n // 1. Extension HTMX — headers de contexte sur chaque requête\n registerAltazionAuthExtension(client)\n\n // 2. HTMX config globale — transmettre les cookies sur les requêtes XHR HTMX\n if (typeof window !== 'undefined' && window.htmx) {\n // @ts-expect-error — htmx.config n'est pas dans les types window\n window.htmx.config = window.htmx.config ?? {}\n // @ts-expect-error\n window.htmx.config.withCredentials = true\n }\n\n // 3. Helpers Handlebars\n const defaults = {\n locale: client.context.locale,\n currency: client.context.currency,\n }\n\n // Getter réactif sur le panier (mis à jour lors du flush offline)\n let currentCart: Cart | null = null\n const getCart = (): Cart | null => currentCart\n\n // Abonnement aux events panier pour mettre à jour le getter\n client.onQueueEvent(async (event: QueueEvent) => {\n if (event.type === 'flushed') {\n try {\n currentCart = await client.cart.getCart()\n } catch {\n // Panier indisponible après flush — on ignore silencieusement\n }\n }\n })\n\n if (handlebars) {\n registerPriceHelpers(handlebars, defaults)\n registerProductHelpers(handlebars, client.context.siteUrl ?? '')\n registerCartHelpers(handlebars, getCart, defaults)\n }\n\n // 4. Indicateur offline DOM\n const offlineEl = typeof document !== 'undefined'\n ? document.querySelector(offlineSelector)\n : null\n\n const updateOfflineClass = (): void => {\n offlineEl?.classList.toggle('altz-offline', client.isOffline)\n }\n\n // Synchronisation initiale\n updateOfflineClass()\n\n // Abonnement ConnectivityManager — retourne la fonction de désabonnement\n const unsubscribeConnectivity = client.connectivity?.subscribe(() => updateOfflineClass())\n\n // 5. Templates compilés (retournés pour usage dans le code applicatif)\n const noHbs = (): HandlebarsTemplateDelegate =>\n () => '<!-- handlebars non initialisé dans @altazion/commerce-sdk-htmx -->'\n\n const templates = handlebars\n ? {\n productCard: handlebars.compile(productCardTemplate),\n cartMini: handlebars.compile(cartMiniTemplate),\n productList: handlebars.compile(productListTemplate),\n }\n : {\n productCard: noHbs(),\n cartMini: noHbs(),\n productList: noHbs(),\n }\n\n return {\n templates,\n dispose(): void {\n unsubscribeConnectivity?.()\n },\n }\n}\n\n// Re-exports helpers pour usage à la carte\nexport { registerPriceHelpers } from './helpers/price'\nexport { registerProductHelpers } from './helpers/product'\nexport { registerCartHelpers } from './helpers/cart'\nexport { registerAltazionAuthExtension } from './extensions/altazion-auth'\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@altazion/commerce-sdk-htmx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Altazion Commerce SDK — Extension HTMX + helpers Handlebars",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "vite build",
|
|
19
|
+
"dev": "vite build --watch",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\""
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@altazion/commerce-sdk-core": "*"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"handlebars": "^4.7.0",
|
|
28
|
+
"htmx.org": "^2.0.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"handlebars": { "optional": true },
|
|
32
|
+
"htmx.org": { "optional": true }
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"handlebars": "^4.7.0",
|
|
36
|
+
"htmx.org": "^2.0.0",
|
|
37
|
+
"typescript": "^5.4.0",
|
|
38
|
+
"vite": "^5.2.0",
|
|
39
|
+
"vite-plugin-dts": "^3.9.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
}
|
|
44
|
+
}
|