@nymbalhq/web-components 0.2.0 → 1.1.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.d.ts +55 -4
- package/dist/index.js +369 -57
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { NymbalClient } from '@nymbalhq/sdk';
|
|
1
|
+
import { NymbalClient, PaymentConfirmContext, PaymentConfirmation } from '@nymbalhq/sdk';
|
|
2
2
|
|
|
3
3
|
declare function registerClient(client: NymbalClient): void;
|
|
4
4
|
declare function getClient(): NymbalClient;
|
|
5
5
|
|
|
6
|
+
/** `HTMLElement`, or a stand-in on a server, where the module is imported but no element is ever made. */
|
|
6
7
|
declare const HTMLElementBase$1: {
|
|
7
8
|
new (): HTMLElement;
|
|
8
9
|
prototype: HTMLElement;
|
|
@@ -53,10 +54,8 @@ declare class NymbalMiniCart extends NymbalElement {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
declare class NymbalVariantSelector extends NymbalElement {
|
|
56
|
-
static get observedAttributes(): string[];
|
|
57
57
|
protected setup(): void;
|
|
58
58
|
protected render(): void;
|
|
59
|
-
attributeChangedCallback(): void;
|
|
60
59
|
private getOptionGroups;
|
|
61
60
|
private esc;
|
|
62
61
|
}
|
|
@@ -65,6 +64,7 @@ declare class NymbalQuantitySelector extends NymbalElement {
|
|
|
65
64
|
static get observedAttributes(): string[];
|
|
66
65
|
private _value;
|
|
67
66
|
protected setup(): void;
|
|
67
|
+
private bounds;
|
|
68
68
|
protected render(): void;
|
|
69
69
|
attributeChangedCallback(name: string): void;
|
|
70
70
|
private emitChange;
|
|
@@ -84,10 +84,17 @@ declare class NymbalSearchBar extends NymbalElement {
|
|
|
84
84
|
private formatPrice;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The facet filter. By default it reads the `facets` attribute and filters the product list. With `source="search"`
|
|
89
|
+
* it renders the search's own facets and narrows the search to one category: choosing an option selects it, choosing
|
|
90
|
+
* the selected option again clears it.
|
|
91
|
+
*/
|
|
87
92
|
declare class NymbalProductFilter extends NymbalElement {
|
|
88
93
|
static get observedAttributes(): string[];
|
|
89
94
|
protected setup(): void;
|
|
90
95
|
protected render(): void;
|
|
96
|
+
private renderSearch;
|
|
97
|
+
private isSearch;
|
|
91
98
|
attributeChangedCallback(): void;
|
|
92
99
|
private getFacets;
|
|
93
100
|
private esc;
|
|
@@ -105,6 +112,50 @@ declare class NymbalToast extends HTMLElementBase {
|
|
|
105
112
|
static showGlobal(message: string, type?: 'success' | 'error' | 'warning' | 'info'): void;
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
/** `pending` while a driver mounts; `error` when no payment can be taken, which a checkout must not submit past. */
|
|
116
|
+
type PaymentElementStatus = 'pending' | 'ready' | 'error';
|
|
117
|
+
/**
|
|
118
|
+
* <nymbal-payment-element client='<json>'>: renders the payments adapter's client half (GET /api/capabilities'
|
|
119
|
+
* `payments.client`) through the driver the SDK registry holds under its `driver` name, and confirms a payment
|
|
120
|
+
* through it. It emits `nymbal:payment:status` whenever `status` changes.
|
|
121
|
+
*/
|
|
122
|
+
declare class NymbalPaymentElement extends HTMLElementBase$1 {
|
|
123
|
+
static get observedAttributes(): string[];
|
|
124
|
+
private _status;
|
|
125
|
+
private _error;
|
|
126
|
+
private _driver;
|
|
127
|
+
private _mounted;
|
|
128
|
+
private _rendered;
|
|
129
|
+
private _generation;
|
|
130
|
+
get status(): PaymentElementStatus;
|
|
131
|
+
connectedCallback(): void;
|
|
132
|
+
attributeChangedCallback(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Completes a payment the API left `requires_action`, once the driver has mounted, handing the driver the
|
|
135
|
+
* checkout's `context`; the driver's answer as is.
|
|
136
|
+
*/
|
|
137
|
+
confirm(clientSecret: string, context?: PaymentConfirmContext): Promise<PaymentConfirmation>;
|
|
138
|
+
private render;
|
|
139
|
+
private fail;
|
|
140
|
+
private setStatus;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* <nymbal-sort-dropdown sort-keys='[…]' value default-sort>: one option per sort key the store reports, in its order,
|
|
145
|
+
* labelled from PRODUCT_SORT_LABELS. A `value` the keys lack shows `default-sort`. Choosing an option emits
|
|
146
|
+
* `nymbal:sort:changed` with `{ sort }`. A `<select>` already inside it (a server render's) is the one it fills.
|
|
147
|
+
*/
|
|
148
|
+
declare class NymbalSortDropdown extends HTMLElementBase$1 {
|
|
149
|
+
static get observedAttributes(): string[];
|
|
150
|
+
private _select;
|
|
151
|
+
/** The key the dropdown shows. */
|
|
152
|
+
get value(): string;
|
|
153
|
+
connectedCallback(): void;
|
|
154
|
+
attributeChangedCallback(): void;
|
|
155
|
+
private selectElement;
|
|
156
|
+
private render;
|
|
157
|
+
}
|
|
158
|
+
|
|
108
159
|
declare function registerComponents(): void;
|
|
109
160
|
|
|
110
|
-
export { NymbalAddToCart, NymbalCartDrawer, NymbalElement, NymbalMiniCart, NymbalProductFilter, NymbalQuantitySelector, NymbalSearchBar, NymbalToast, NymbalVariantSelector, getClient, injectStyles, registerClient, registerComponents };
|
|
161
|
+
export { NymbalAddToCart, NymbalCartDrawer, NymbalElement, NymbalMiniCart, NymbalPaymentElement, NymbalProductFilter, NymbalQuantitySelector, NymbalSearchBar, NymbalSortDropdown, NymbalToast, NymbalVariantSelector, type PaymentElementStatus, getClient, injectStyles, registerClient, registerComponents };
|
package/dist/index.js
CHANGED
|
@@ -79,9 +79,16 @@ nymbal-add-to-cart button:disabled {
|
|
|
79
79
|
cursor: not-allowed;
|
|
80
80
|
}
|
|
81
81
|
`;
|
|
82
|
+
var MIN_QTY = 1;
|
|
83
|
+
var MAX_QTY = 99;
|
|
84
|
+
function quantityOf(attr) {
|
|
85
|
+
const n = attr === null || attr.trim() === "" ? Number.NaN : Number(attr);
|
|
86
|
+
const whole = Number.isInteger(n) ? n : MIN_QTY;
|
|
87
|
+
return Math.min(MAX_QTY, Math.max(MIN_QTY, whole));
|
|
88
|
+
}
|
|
82
89
|
var NymbalAddToCart = class extends NymbalElement {
|
|
83
90
|
static get observedAttributes() {
|
|
84
|
-
return ["variant-id", "disabled"];
|
|
91
|
+
return ["variant-id", "qty", "disabled"];
|
|
85
92
|
}
|
|
86
93
|
button;
|
|
87
94
|
setup() {
|
|
@@ -110,13 +117,14 @@ var NymbalAddToCart = class extends NymbalElement {
|
|
|
110
117
|
handleClick = async () => {
|
|
111
118
|
const variantId = this.getAttribute("variant-id");
|
|
112
119
|
if (!variantId) return;
|
|
120
|
+
const qty = quantityOf(this.getAttribute("qty"));
|
|
113
121
|
try {
|
|
114
|
-
await this.client.cart.addItem(variantId);
|
|
122
|
+
await this.client.cart.addItem(variantId, qty);
|
|
115
123
|
this.dispatchEvent(
|
|
116
124
|
new CustomEvent("nymbal:cart:item-added", {
|
|
117
125
|
bubbles: true,
|
|
118
126
|
composed: true,
|
|
119
|
-
detail: { variantId }
|
|
127
|
+
detail: { variantId, qty }
|
|
120
128
|
})
|
|
121
129
|
);
|
|
122
130
|
} catch {
|
|
@@ -449,10 +457,10 @@ nymbal-variant-selector .nymbal-option-btn:disabled {
|
|
|
449
457
|
cursor: not-allowed;
|
|
450
458
|
}
|
|
451
459
|
`;
|
|
460
|
+
function optionsOf(variant) {
|
|
461
|
+
return Array.isArray(variant.options) ? variant.options : [];
|
|
462
|
+
}
|
|
452
463
|
var NymbalVariantSelector = class extends NymbalElement {
|
|
453
|
-
static get observedAttributes() {
|
|
454
|
-
return ["options"];
|
|
455
|
-
}
|
|
456
464
|
setup() {
|
|
457
465
|
injectStyles("nymbal-variant-selector", STYLES4);
|
|
458
466
|
this.subscribeToStore(this.client.product);
|
|
@@ -471,9 +479,7 @@ var NymbalVariantSelector = class extends NymbalElement {
|
|
|
471
479
|
<span class="nymbal-option-label">${this.esc(group.name)}</span>
|
|
472
480
|
<div class="nymbal-option-buttons">
|
|
473
481
|
${group.values.map((value) => {
|
|
474
|
-
const isSelected = selected
|
|
475
|
-
(o) => o.name === group.name && o.value === value
|
|
476
|
-
) ?? false;
|
|
482
|
+
const isSelected = selected !== null && optionsOf(selected).some((o) => o.name === group.name && o.value === value);
|
|
477
483
|
return `<button
|
|
478
484
|
class="nymbal-option-btn"
|
|
479
485
|
data-testid="variant-option-${this.esc(group.name)}-${this.esc(value)}"
|
|
@@ -493,26 +499,14 @@ var NymbalVariantSelector = class extends NymbalElement {
|
|
|
493
499
|
if (!optionName || !optionValue || !product) return;
|
|
494
500
|
const currentSelections = /* @__PURE__ */ new Map();
|
|
495
501
|
if (selected) {
|
|
496
|
-
for (const opt of selected
|
|
502
|
+
for (const opt of optionsOf(selected)) {
|
|
497
503
|
currentSelections.set(opt.name, opt.value);
|
|
498
504
|
}
|
|
499
505
|
}
|
|
500
506
|
currentSelections.set(optionName, optionValue);
|
|
501
|
-
const match = product.variants.find(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if (typeof vRaw === "string") {
|
|
505
|
-
try {
|
|
506
|
-
vOpts = JSON.parse(vRaw);
|
|
507
|
-
} catch (e) {
|
|
508
|
-
console.warn("[variant-selector] malformed variant options JSON", e);
|
|
509
|
-
vOpts = [];
|
|
510
|
-
}
|
|
511
|
-
} else if (Array.isArray(vRaw)) {
|
|
512
|
-
vOpts = vRaw;
|
|
513
|
-
}
|
|
514
|
-
return vOpts.every((o) => currentSelections.get(o.name) === o.value);
|
|
515
|
-
});
|
|
507
|
+
const match = product.variants.find(
|
|
508
|
+
(v) => optionsOf(v).every((o) => currentSelections.get(o.name) === o.value)
|
|
509
|
+
);
|
|
516
510
|
if (match) {
|
|
517
511
|
this.client.product.selectVariant(match);
|
|
518
512
|
this.dispatchEvent(
|
|
@@ -526,33 +520,10 @@ var NymbalVariantSelector = class extends NymbalElement {
|
|
|
526
520
|
});
|
|
527
521
|
});
|
|
528
522
|
}
|
|
529
|
-
attributeChangedCallback() {
|
|
530
|
-
if (this.isConnected) this.requestRender();
|
|
531
|
-
}
|
|
532
523
|
getOptionGroups(product) {
|
|
533
|
-
const optionsAttr = this.getAttribute("options");
|
|
534
|
-
if (optionsAttr) {
|
|
535
|
-
try {
|
|
536
|
-
return JSON.parse(optionsAttr);
|
|
537
|
-
} catch (e) {
|
|
538
|
-
console.warn("[variant-selector] malformed options attribute, falling back to variant derivation", e);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
524
|
const groups = /* @__PURE__ */ new Map();
|
|
542
525
|
for (const variant of product.variants) {
|
|
543
|
-
const
|
|
544
|
-
let opts = [];
|
|
545
|
-
if (typeof raw === "string") {
|
|
546
|
-
try {
|
|
547
|
-
opts = JSON.parse(raw);
|
|
548
|
-
} catch (e) {
|
|
549
|
-
console.warn("[variant-selector] malformed variant options in getOptionGroups", e);
|
|
550
|
-
opts = [];
|
|
551
|
-
}
|
|
552
|
-
} else if (Array.isArray(raw)) {
|
|
553
|
-
opts = raw;
|
|
554
|
-
}
|
|
555
|
-
for (const opt of opts) {
|
|
526
|
+
for (const opt of optionsOf(variant)) {
|
|
556
527
|
let values = groups.get(opt.name);
|
|
557
528
|
if (!values) {
|
|
558
529
|
values = /* @__PURE__ */ new Set();
|
|
@@ -624,6 +595,11 @@ nymbal-quantity-selector input::-webkit-inner-spin-button {
|
|
|
624
595
|
margin: 0;
|
|
625
596
|
}
|
|
626
597
|
`;
|
|
598
|
+
function wholeQuantity(raw, min, max) {
|
|
599
|
+
const n = raw === null || raw.trim() === "" ? Number.NaN : Number(raw);
|
|
600
|
+
if (Number.isNaN(n)) return min;
|
|
601
|
+
return Math.min(max, Math.max(min, Math.trunc(n)));
|
|
602
|
+
}
|
|
627
603
|
var NymbalQuantitySelector = class extends NymbalElement {
|
|
628
604
|
static get observedAttributes() {
|
|
629
605
|
return ["value", "min", "max"];
|
|
@@ -631,11 +607,13 @@ var NymbalQuantitySelector = class extends NymbalElement {
|
|
|
631
607
|
_value = 1;
|
|
632
608
|
setup() {
|
|
633
609
|
injectStyles("nymbal-quantity-selector", STYLES5);
|
|
634
|
-
this._value =
|
|
610
|
+
this._value = wholeQuantity(this.getAttribute("value"), ...this.bounds());
|
|
611
|
+
}
|
|
612
|
+
bounds() {
|
|
613
|
+
return [Number(this.getAttribute("min")) || 1, Number(this.getAttribute("max")) || 99];
|
|
635
614
|
}
|
|
636
615
|
render() {
|
|
637
|
-
const min =
|
|
638
|
-
const max = Number(this.getAttribute("max")) || 99;
|
|
616
|
+
const [min, max] = this.bounds();
|
|
639
617
|
this.innerHTML = `
|
|
640
618
|
<button data-testid="qty-decrement" aria-label="Decrease quantity" ${this._value <= min ? "disabled" : ""}>−</button>
|
|
641
619
|
<input data-testid="qty-input" type="number" value="${this._value}" min="${min}" max="${max}" aria-label="Quantity">
|
|
@@ -657,15 +635,14 @@ var NymbalQuantitySelector = class extends NymbalElement {
|
|
|
657
635
|
});
|
|
658
636
|
const input = this.querySelector("input");
|
|
659
637
|
input?.addEventListener("change", () => {
|
|
660
|
-
|
|
661
|
-
this._value = val;
|
|
638
|
+
this._value = wholeQuantity(input.value, min, max);
|
|
662
639
|
this.emitChange();
|
|
663
640
|
this.render();
|
|
664
641
|
});
|
|
665
642
|
}
|
|
666
643
|
attributeChangedCallback(name) {
|
|
667
644
|
if (name === "value") {
|
|
668
|
-
this._value =
|
|
645
|
+
this._value = wholeQuantity(this.getAttribute("value"), ...this.bounds());
|
|
669
646
|
}
|
|
670
647
|
if (this.isConnected) this.requestRender();
|
|
671
648
|
}
|
|
@@ -885,13 +862,18 @@ nymbal-product-filter .nymbal-filter-count {
|
|
|
885
862
|
`;
|
|
886
863
|
var NymbalProductFilter = class extends NymbalElement {
|
|
887
864
|
static get observedAttributes() {
|
|
888
|
-
return ["facets"];
|
|
865
|
+
return ["facets", "source"];
|
|
889
866
|
}
|
|
890
867
|
setup() {
|
|
891
868
|
injectStyles("nymbal-product-filter", STYLES7);
|
|
892
869
|
this.subscribeToStore(this.client.productList);
|
|
870
|
+
this.subscribeToStore(this.client.search);
|
|
893
871
|
}
|
|
894
872
|
render() {
|
|
873
|
+
if (this.isSearch()) {
|
|
874
|
+
this.renderSearch();
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
895
877
|
const state = this.client.productList.getState();
|
|
896
878
|
const facets = this.getFacets();
|
|
897
879
|
if (facets.length === 0) {
|
|
@@ -945,6 +927,42 @@ var NymbalProductFilter = class extends NymbalElement {
|
|
|
945
927
|
});
|
|
946
928
|
});
|
|
947
929
|
}
|
|
930
|
+
renderSearch() {
|
|
931
|
+
const { facets, category } = this.client.search.getState();
|
|
932
|
+
const shown = facets.filter((f) => f.field === "category");
|
|
933
|
+
this.innerHTML = shown.map(
|
|
934
|
+
(facet) => `
|
|
935
|
+
<div class="nymbal-filter-group" data-testid="filter-group-${this.esc(facet.field)}">
|
|
936
|
+
<span class="nymbal-filter-label">${this.esc(facet.label)}</span>
|
|
937
|
+
${facet.values.map(
|
|
938
|
+
(v) => `
|
|
939
|
+
<label class="nymbal-filter-option" data-testid="filter-option-${this.esc(v.value)}">
|
|
940
|
+
<input type="checkbox" data-filter-value="${this.esc(v.value)}" ${v.value === category ? "checked" : ""}>
|
|
941
|
+
<span>${this.esc(v.label ?? v.value)}</span>
|
|
942
|
+
${v.count !== void 0 ? `<span class="nymbal-filter-count">(${v.count})</span>` : ""}
|
|
943
|
+
</label>
|
|
944
|
+
`
|
|
945
|
+
).join("")}
|
|
946
|
+
</div>
|
|
947
|
+
`
|
|
948
|
+
).join("");
|
|
949
|
+
this.querySelectorAll('input[type="checkbox"]').forEach((input) => {
|
|
950
|
+
input.addEventListener("change", () => {
|
|
951
|
+
const value = input.dataset["filterValue"];
|
|
952
|
+
void this.client.search.setCategory(input.checked ? value : null);
|
|
953
|
+
this.dispatchEvent(
|
|
954
|
+
new CustomEvent("nymbal:filter:changed", {
|
|
955
|
+
bubbles: true,
|
|
956
|
+
composed: true,
|
|
957
|
+
detail: { name: "category", value: input.checked ? value : "" }
|
|
958
|
+
})
|
|
959
|
+
);
|
|
960
|
+
});
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
isSearch() {
|
|
964
|
+
return this.getAttribute("source") === "search";
|
|
965
|
+
}
|
|
948
966
|
attributeChangedCallback() {
|
|
949
967
|
if (this.isConnected) this.requestRender();
|
|
950
968
|
}
|
|
@@ -1077,6 +1095,289 @@ var NymbalToast = class extends HTMLElementBase2 {
|
|
|
1077
1095
|
}
|
|
1078
1096
|
};
|
|
1079
1097
|
|
|
1098
|
+
// src/components/payment-element.ts
|
|
1099
|
+
import {
|
|
1100
|
+
getPaymentDriver
|
|
1101
|
+
} from "@nymbalhq/sdk";
|
|
1102
|
+
var STYLES9 = `
|
|
1103
|
+
nymbal-payment-element {
|
|
1104
|
+
display: block;
|
|
1105
|
+
font-family: var(--nymbal-font-family);
|
|
1106
|
+
}
|
|
1107
|
+
nymbal-payment-element .nymbal-payment-note {
|
|
1108
|
+
margin: 0;
|
|
1109
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
1110
|
+
color: var(--nymbal-color-text-muted, #71717a);
|
|
1111
|
+
}
|
|
1112
|
+
nymbal-payment-element .nymbal-payment-card {
|
|
1113
|
+
padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);
|
|
1114
|
+
border: 1px solid var(--nymbal-color-border, #e4e4e7);
|
|
1115
|
+
border-radius: var(--nymbal-radius-md, 0.5rem);
|
|
1116
|
+
background: var(--nymbal-color-surface, #fff);
|
|
1117
|
+
min-height: 40px;
|
|
1118
|
+
}
|
|
1119
|
+
nymbal-payment-element .nymbal-payment-error {
|
|
1120
|
+
margin: 0;
|
|
1121
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
1122
|
+
color: var(--nymbal-color-error, #dc2626);
|
|
1123
|
+
}
|
|
1124
|
+
`;
|
|
1125
|
+
var NO_PAYMENT_STEP = { ok: true, confirmed: false };
|
|
1126
|
+
function parseClient(value) {
|
|
1127
|
+
if (value === null || value === "" || value === "null") return null;
|
|
1128
|
+
try {
|
|
1129
|
+
const parsed = JSON.parse(value);
|
|
1130
|
+
if (parsed === null) return null;
|
|
1131
|
+
if (typeof parsed.driver !== "string" || typeof parsed.options !== "object") return void 0;
|
|
1132
|
+
return { driver: parsed.driver, options: { ...parsed.options ?? {} } };
|
|
1133
|
+
} catch {
|
|
1134
|
+
return void 0;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
var NymbalPaymentElement = class extends HTMLElementBase {
|
|
1138
|
+
static get observedAttributes() {
|
|
1139
|
+
return ["client"];
|
|
1140
|
+
}
|
|
1141
|
+
_status = "pending";
|
|
1142
|
+
_error = "";
|
|
1143
|
+
_driver = null;
|
|
1144
|
+
_mounted = Promise.resolve(true);
|
|
1145
|
+
_rendered = void 0;
|
|
1146
|
+
_generation = 0;
|
|
1147
|
+
get status() {
|
|
1148
|
+
return this._status;
|
|
1149
|
+
}
|
|
1150
|
+
connectedCallback() {
|
|
1151
|
+
injectStyles("nymbal-payment-element", STYLES9);
|
|
1152
|
+
this.render();
|
|
1153
|
+
}
|
|
1154
|
+
attributeChangedCallback() {
|
|
1155
|
+
if (this.isConnected) this.render();
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Completes a payment the API left `requires_action`, once the driver has mounted, handing the driver the
|
|
1159
|
+
* checkout's `context`; the driver's answer as is.
|
|
1160
|
+
*/
|
|
1161
|
+
async confirm(clientSecret, context) {
|
|
1162
|
+
const driver = this._driver;
|
|
1163
|
+
if (this._status === "error") return { ok: false, error: this._error };
|
|
1164
|
+
if (driver === null) return NO_PAYMENT_STEP;
|
|
1165
|
+
if (!await this._mounted) return { ok: false, error: this._error };
|
|
1166
|
+
return driver.confirm(clientSecret, context);
|
|
1167
|
+
}
|
|
1168
|
+
render() {
|
|
1169
|
+
const attribute = this.getAttribute("client");
|
|
1170
|
+
if (attribute === this._rendered) return;
|
|
1171
|
+
this._rendered = attribute;
|
|
1172
|
+
const generation = ++this._generation;
|
|
1173
|
+
this._driver = null;
|
|
1174
|
+
this._mounted = Promise.resolve(true);
|
|
1175
|
+
this.replaceChildren();
|
|
1176
|
+
const client = parseClient(attribute);
|
|
1177
|
+
if (client === void 0)
|
|
1178
|
+
return this.fail("The store sent a payment client this page cannot read.");
|
|
1179
|
+
if (client === null) return this.setStatus("ready");
|
|
1180
|
+
const driver = getPaymentDriver(client.driver);
|
|
1181
|
+
if (driver === void 0) {
|
|
1182
|
+
return this.fail(`Payment method "${client.driver}" is not available on this storefront.`);
|
|
1183
|
+
}
|
|
1184
|
+
this._driver = driver;
|
|
1185
|
+
this.setStatus("pending");
|
|
1186
|
+
this._mounted = Promise.resolve().then(() => driver.mount(this, client.options)).then(
|
|
1187
|
+
() => {
|
|
1188
|
+
if (generation === this._generation) this.setStatus("ready");
|
|
1189
|
+
return true;
|
|
1190
|
+
},
|
|
1191
|
+
(err) => {
|
|
1192
|
+
if (generation === this._generation) {
|
|
1193
|
+
this.fail(err instanceof Error ? err.message : "The payment form could not be loaded.");
|
|
1194
|
+
}
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
fail(message) {
|
|
1200
|
+
this._error = message;
|
|
1201
|
+
const p = document.createElement("p");
|
|
1202
|
+
p.className = "nymbal-payment-error";
|
|
1203
|
+
p.setAttribute("data-testid", "payment-element-error");
|
|
1204
|
+
p.setAttribute("role", "alert");
|
|
1205
|
+
p.textContent = message;
|
|
1206
|
+
this.replaceChildren(p);
|
|
1207
|
+
this.setStatus("error");
|
|
1208
|
+
}
|
|
1209
|
+
setStatus(status) {
|
|
1210
|
+
if (status === this._status) return;
|
|
1211
|
+
this._status = status;
|
|
1212
|
+
this.dispatchEvent(
|
|
1213
|
+
new CustomEvent("nymbal:payment:status", {
|
|
1214
|
+
bubbles: true,
|
|
1215
|
+
composed: true,
|
|
1216
|
+
detail: { status }
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
// src/components/sort-dropdown.ts
|
|
1223
|
+
import { DEFAULT_PRODUCT_SORT, PRODUCT_SORT_LABELS } from "@nymbalhq/types";
|
|
1224
|
+
function parseKeys(value) {
|
|
1225
|
+
if (!value) return [];
|
|
1226
|
+
try {
|
|
1227
|
+
const parsed = JSON.parse(value);
|
|
1228
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
console.error("[nymbal-sort-dropdown] failed to parse sort-keys attribute", err);
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
var NymbalSortDropdown = class extends HTMLElementBase {
|
|
1235
|
+
static get observedAttributes() {
|
|
1236
|
+
return ["sort-keys", "value", "default-sort"];
|
|
1237
|
+
}
|
|
1238
|
+
_select = null;
|
|
1239
|
+
/** The key the dropdown shows. */
|
|
1240
|
+
get value() {
|
|
1241
|
+
return this._select?.value ?? "";
|
|
1242
|
+
}
|
|
1243
|
+
connectedCallback() {
|
|
1244
|
+
this.render();
|
|
1245
|
+
}
|
|
1246
|
+
attributeChangedCallback() {
|
|
1247
|
+
if (this.isConnected) this.render();
|
|
1248
|
+
}
|
|
1249
|
+
selectElement() {
|
|
1250
|
+
if (this._select !== null && this.contains(this._select)) return this._select;
|
|
1251
|
+
let select = this.querySelector("select");
|
|
1252
|
+
if (select === null) {
|
|
1253
|
+
select = document.createElement("select");
|
|
1254
|
+
select.setAttribute("aria-label", "Sort products");
|
|
1255
|
+
this.appendChild(select);
|
|
1256
|
+
}
|
|
1257
|
+
if (!select.hasAttribute("data-testid")) select.setAttribute("data-testid", "sort-dropdown");
|
|
1258
|
+
select.addEventListener("change", () => {
|
|
1259
|
+
this.dispatchEvent(
|
|
1260
|
+
new CustomEvent("nymbal:sort:changed", {
|
|
1261
|
+
bubbles: true,
|
|
1262
|
+
composed: true,
|
|
1263
|
+
detail: { sort: select.value }
|
|
1264
|
+
})
|
|
1265
|
+
);
|
|
1266
|
+
});
|
|
1267
|
+
this._select = select;
|
|
1268
|
+
return select;
|
|
1269
|
+
}
|
|
1270
|
+
render() {
|
|
1271
|
+
const keys = parseKeys(this.getAttribute("sort-keys"));
|
|
1272
|
+
const wanted = this.getAttribute("value") ?? "";
|
|
1273
|
+
const fallback = this.getAttribute("default-sort") ?? DEFAULT_PRODUCT_SORT;
|
|
1274
|
+
const selected = keys.includes(wanted) ? wanted : keys.includes(fallback) ? fallback : keys[0];
|
|
1275
|
+
const select = this.selectElement();
|
|
1276
|
+
select.replaceChildren(
|
|
1277
|
+
...keys.map((key) => {
|
|
1278
|
+
const option = document.createElement("option");
|
|
1279
|
+
option.value = key;
|
|
1280
|
+
option.textContent = PRODUCT_SORT_LABELS[key] ?? key;
|
|
1281
|
+
return option;
|
|
1282
|
+
})
|
|
1283
|
+
);
|
|
1284
|
+
if (selected !== void 0) select.value = selected;
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
// src/index.ts
|
|
1289
|
+
import { getPaymentDriver as getPaymentDriver2, registerPaymentDriver } from "@nymbalhq/sdk";
|
|
1290
|
+
|
|
1291
|
+
// src/payment-drivers/test-mode.ts
|
|
1292
|
+
var testModeDriver = {
|
|
1293
|
+
mount(el) {
|
|
1294
|
+
el.innerHTML = '<p class="nymbal-payment-note">Test mode \u2014 payment skipped</p>';
|
|
1295
|
+
},
|
|
1296
|
+
confirm() {
|
|
1297
|
+
return Promise.resolve({ ok: true, confirmed: false });
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
// src/payment-drivers/stripe.ts
|
|
1302
|
+
var STRIPE_JS = "https://js.stripe.com/v3/";
|
|
1303
|
+
function billingDetails(context) {
|
|
1304
|
+
const details = {};
|
|
1305
|
+
if (context?.email) details.email = context.email;
|
|
1306
|
+
const billing = context?.billing;
|
|
1307
|
+
if (billing) {
|
|
1308
|
+
const name = `${billing.firstName} ${billing.lastName}`.trim();
|
|
1309
|
+
if (name) details.name = name;
|
|
1310
|
+
details.address = {
|
|
1311
|
+
line1: billing.addressLine1,
|
|
1312
|
+
...billing.addressLine2 ? { line2: billing.addressLine2 } : {},
|
|
1313
|
+
city: billing.city,
|
|
1314
|
+
state: billing.region,
|
|
1315
|
+
postal_code: billing.postalCode,
|
|
1316
|
+
country: billing.country
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
return Object.keys(details).length > 0 ? details : void 0;
|
|
1320
|
+
}
|
|
1321
|
+
var loading = null;
|
|
1322
|
+
function loadStripeJs() {
|
|
1323
|
+
const win = window;
|
|
1324
|
+
if (win.Stripe) return Promise.resolve(win.Stripe);
|
|
1325
|
+
loading ??= new Promise((resolve, reject) => {
|
|
1326
|
+
const script = document.createElement("script");
|
|
1327
|
+
script.src = STRIPE_JS;
|
|
1328
|
+
script.async = true;
|
|
1329
|
+
script.addEventListener("load", () => {
|
|
1330
|
+
if (win.Stripe) resolve(win.Stripe);
|
|
1331
|
+
else reject(new Error("Stripe.js loaded but window.Stripe is missing."));
|
|
1332
|
+
});
|
|
1333
|
+
script.addEventListener("error", () => {
|
|
1334
|
+
loading = null;
|
|
1335
|
+
script.remove();
|
|
1336
|
+
reject(new Error("Stripe.js could not be loaded."));
|
|
1337
|
+
});
|
|
1338
|
+
document.head.appendChild(script);
|
|
1339
|
+
});
|
|
1340
|
+
return loading;
|
|
1341
|
+
}
|
|
1342
|
+
var instances = /* @__PURE__ */ new Map();
|
|
1343
|
+
var mounted = [];
|
|
1344
|
+
var stripeDriver = {
|
|
1345
|
+
async mount(el, options) {
|
|
1346
|
+
const key = options["publishableKey"];
|
|
1347
|
+
if (!key)
|
|
1348
|
+
throw new Error("Card payments are not configured: the store has no Stripe publishable key.");
|
|
1349
|
+
const factory = await loadStripeJs();
|
|
1350
|
+
let stripe = instances.get(key);
|
|
1351
|
+
if (stripe === void 0) {
|
|
1352
|
+
stripe = factory(key);
|
|
1353
|
+
instances.set(key, stripe);
|
|
1354
|
+
}
|
|
1355
|
+
const target = document.createElement("div");
|
|
1356
|
+
target.className = "nymbal-payment-card";
|
|
1357
|
+
el.replaceChildren(target);
|
|
1358
|
+
const card = stripe.elements().create("card", {
|
|
1359
|
+
style: {
|
|
1360
|
+
base: { fontSize: "16px", color: "#1a1a1a", "::placeholder": { color: "#9ca3af" } }
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
card.mount(target);
|
|
1364
|
+
mounted = [
|
|
1365
|
+
...mounted.filter((m) => m.host.isConnected && m.host !== el),
|
|
1366
|
+
{ host: el, stripe, card }
|
|
1367
|
+
];
|
|
1368
|
+
},
|
|
1369
|
+
async confirm(clientSecret, context) {
|
|
1370
|
+
const current = mounted.findLast((m) => m.host.isConnected) ?? mounted.at(-1);
|
|
1371
|
+
if (current === void 0) return { ok: false, error: "The card form is not ready." };
|
|
1372
|
+
const billing_details = billingDetails(context);
|
|
1373
|
+
const result = await current.stripe.confirmCardPayment(clientSecret, {
|
|
1374
|
+
payment_method: { card: current.card, ...billing_details ? { billing_details } : {} }
|
|
1375
|
+
});
|
|
1376
|
+
if (result.error) return { ok: false, error: result.error.message ?? "Payment failed." };
|
|
1377
|
+
return { ok: true, confirmed: result.paymentIntent?.status === "succeeded" };
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1080
1381
|
// src/index.ts
|
|
1081
1382
|
function registerComponents() {
|
|
1082
1383
|
const components = [
|
|
@@ -1087,8 +1388,17 @@ function registerComponents() {
|
|
|
1087
1388
|
["nymbal-quantity-selector", NymbalQuantitySelector],
|
|
1088
1389
|
["nymbal-search-bar", NymbalSearchBar],
|
|
1089
1390
|
["nymbal-product-filter", NymbalProductFilter],
|
|
1090
|
-
["nymbal-toast", NymbalToast]
|
|
1391
|
+
["nymbal-toast", NymbalToast],
|
|
1392
|
+
["nymbal-payment-element", NymbalPaymentElement],
|
|
1393
|
+
["nymbal-sort-dropdown", NymbalSortDropdown]
|
|
1394
|
+
];
|
|
1395
|
+
const drivers = [
|
|
1396
|
+
["test-mode", testModeDriver],
|
|
1397
|
+
["stripe", stripeDriver]
|
|
1091
1398
|
];
|
|
1399
|
+
for (const [name, driver] of drivers) {
|
|
1400
|
+
if (getPaymentDriver2(name) === void 0) registerPaymentDriver(name, driver);
|
|
1401
|
+
}
|
|
1092
1402
|
for (const [name, ctor] of components) {
|
|
1093
1403
|
if (!customElements.get(name)) {
|
|
1094
1404
|
customElements.define(name, ctor);
|
|
@@ -1100,9 +1410,11 @@ export {
|
|
|
1100
1410
|
NymbalCartDrawer,
|
|
1101
1411
|
NymbalElement,
|
|
1102
1412
|
NymbalMiniCart,
|
|
1413
|
+
NymbalPaymentElement,
|
|
1103
1414
|
NymbalProductFilter,
|
|
1104
1415
|
NymbalQuantitySelector,
|
|
1105
1416
|
NymbalSearchBar,
|
|
1417
|
+
NymbalSortDropdown,
|
|
1106
1418
|
NymbalToast,
|
|
1107
1419
|
NymbalVariantSelector,
|
|
1108
1420
|
getClient,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/registry.ts","../src/base-element.ts","../src/styles.ts","../src/components/add-to-cart.ts","../src/components/cart-drawer.ts","../src/components/mini-cart.ts","../src/components/variant-selector.ts","../src/components/quantity-selector.ts","../src/components/search-bar.ts","../src/components/product-filter.ts","../src/components/toast.ts","../src/index.ts"],"sourcesContent":["import type { NymbalClient } from '@nymbalhq/sdk'\n\nconst CLIENT_KEY = Symbol.for('nymbal.client')\n\nexport function registerClient(client: NymbalClient): void {\n (globalThis as Record<symbol, unknown>)[CLIENT_KEY] = client\n}\n\nexport function getClient(): NymbalClient {\n const client = (globalThis as Record<symbol, unknown>)[CLIENT_KEY] as NymbalClient | undefined\n if (!client) {\n throw new Error('NymbalClient not registered. Call registerClient() before using components.')\n }\n return client\n}\n","import type { NymbalClient } from '@nymbalhq/sdk'\nimport { getClient } from './registry.js'\n\nconst HTMLElementBase =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement)\n\nexport abstract class NymbalElement extends HTMLElementBase {\n private _unsubs: Array<() => void> = []\n private _renderQueued = false\n\n protected get client(): NymbalClient {\n return getClient()\n }\n\n protected subscribeToStore(store: { subscribe(fn: () => void): () => void }): void {\n this._unsubs.push(store.subscribe(() => this.requestRender()))\n }\n\n protected requestRender(): void {\n if (this._renderQueued) return\n this._renderQueued = true\n queueMicrotask(() => {\n this._renderQueued = false\n this.render()\n })\n }\n\n connectedCallback(): void {\n this.setup()\n this.render()\n }\n\n disconnectedCallback(): void {\n for (const fn of this._unsubs) fn()\n this._unsubs = []\n }\n\n protected abstract setup(): void\n protected abstract render(): void\n}\n","const injected = new Set<string>()\n\nexport function injectStyles(tagName: string, css: string): void {\n if (injected.has(tagName)) return\n if (typeof document === 'undefined') return\n const style = document.createElement('style')\n style.setAttribute('data-nymbal', tagName)\n style.textContent = css\n document.head.appendChild(style)\n injected.add(tagName)\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-add-to-cart {\n display: inline-block;\n}\nnymbal-add-to-cart button {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n font-weight: var(--nymbal-font-weight-medium);\n background: var(--nymbal-color-primary);\n color: var(--nymbal-color-text-inverse);\n border: none;\n border-radius: var(--nymbal-radius-md);\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-lg);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast);\n min-width: 140px;\n}\nnymbal-add-to-cart button:hover:not(:disabled) {\n background: var(--nymbal-color-primary-hover);\n}\nnymbal-add-to-cart button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n`\n\nexport class NymbalAddToCart extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['variant-id', 'disabled']\n }\n\n private button!: HTMLButtonElement\n\n protected setup(): void {\n injectStyles('nymbal-add-to-cart', STYLES)\n this.subscribeToStore(this.client.cart)\n }\n\n protected render(): void {\n const variantId = this.getAttribute('variant-id')\n const isDisabled = this.hasAttribute('disabled')\n const cartState = this.client.cart.getState()\n\n if (!this.button) {\n this.button = document.createElement('button')\n this.button.setAttribute('type', 'button')\n this.button.setAttribute('data-testid', 'add-to-cart')\n this.button.addEventListener('click', this.handleClick)\n this.innerHTML = ''\n this.appendChild(this.button)\n }\n\n this.button.disabled = isDisabled || cartState.loading || !variantId\n this.button.setAttribute('aria-busy', String(cartState.loading))\n this.button.textContent = cartState.loading ? 'Adding...' : 'Add to Cart'\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private handleClick = async (): Promise<void> => {\n const variantId = this.getAttribute('variant-id')\n if (!variantId) return\n try {\n await this.client.cart.addItem(variantId)\n this.dispatchEvent(\n new CustomEvent('nymbal:cart:item-added', {\n bubbles: true,\n composed: true,\n detail: { variantId },\n }),\n )\n } catch {\n // error is stored in cart state\n }\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-cart-drawer {\n display: block;\n}\nnymbal-cart-drawer .nymbal-drawer-overlay {\n position: fixed;\n inset: 0;\n background: var(--nymbal-color-overlay);\n z-index: var(--nymbal-z-drawer);\n opacity: 0;\n visibility: hidden;\n transition: opacity var(--nymbal-transition-normal), visibility var(--nymbal-transition-normal);\n}\nnymbal-cart-drawer[open] .nymbal-drawer-overlay {\n opacity: 1;\n visibility: visible;\n}\nnymbal-cart-drawer .nymbal-drawer-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(400px, 90vw);\n background: var(--nymbal-color-surface);\n z-index: var(--nymbal-z-drawer);\n transform: translateX(100%);\n transition: transform var(--nymbal-transition-normal);\n display: flex;\n flex-direction: column;\n font-family: var(--nymbal-font-family);\n}\nnymbal-cart-drawer[open] .nymbal-drawer-panel {\n transform: translateX(0);\n}\nnymbal-cart-drawer .nymbal-drawer-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: var(--nymbal-spacing-md) var(--nymbal-spacing-lg);\n border-bottom: 1px solid var(--nymbal-color-border);\n}\nnymbal-cart-drawer .nymbal-drawer-header h2 {\n margin: 0;\n font-size: var(--nymbal-font-size-lg);\n font-weight: var(--nymbal-font-weight-bold);\n}\nnymbal-cart-drawer .nymbal-drawer-close {\n background: none;\n border: none;\n font-size: var(--nymbal-font-size-xl);\n cursor: pointer;\n padding: var(--nymbal-spacing-xs);\n color: var(--nymbal-color-text-muted);\n}\nnymbal-cart-drawer .nymbal-drawer-items {\n flex: 1;\n overflow-y: auto;\n padding: var(--nymbal-spacing-md) var(--nymbal-spacing-lg);\n}\nnymbal-cart-drawer .nymbal-cart-item {\n display: flex;\n gap: var(--nymbal-spacing-md);\n padding: var(--nymbal-spacing-md) 0;\n border-bottom: 1px solid var(--nymbal-color-border);\n}\nnymbal-cart-drawer .nymbal-cart-item-info {\n flex: 1;\n}\nnymbal-cart-drawer .nymbal-cart-item-name {\n font-weight: var(--nymbal-font-weight-medium);\n margin-bottom: var(--nymbal-spacing-xs);\n}\nnymbal-cart-drawer .nymbal-cart-item-variant {\n font-size: var(--nymbal-font-size-sm);\n color: var(--nymbal-color-text-muted);\n}\nnymbal-cart-drawer .nymbal-cart-item-actions {\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm);\n}\nnymbal-cart-drawer .nymbal-cart-item-remove {\n background: none;\n border: none;\n color: var(--nymbal-color-error);\n cursor: pointer;\n font-size: var(--nymbal-font-size-sm);\n padding: var(--nymbal-spacing-xs);\n}\nnymbal-cart-drawer .nymbal-drawer-footer {\n padding: var(--nymbal-spacing-md) var(--nymbal-spacing-lg);\n border-top: 1px solid var(--nymbal-color-border);\n}\nnymbal-cart-drawer .nymbal-cart-subtotal {\n display: flex;\n justify-content: space-between;\n font-weight: var(--nymbal-font-weight-bold);\n margin-bottom: var(--nymbal-spacing-md);\n}\nnymbal-cart-drawer .nymbal-checkout-btn {\n width: 100%;\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n font-weight: var(--nymbal-font-weight-medium);\n background: var(--nymbal-color-primary);\n color: var(--nymbal-color-text-inverse);\n border: none;\n border-radius: var(--nymbal-radius-md);\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-lg);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast);\n}\nnymbal-cart-drawer .nymbal-checkout-btn:hover {\n background: var(--nymbal-color-primary-hover);\n}\nnymbal-cart-drawer .nymbal-drawer-empty {\n text-align: center;\n color: var(--nymbal-color-text-muted);\n padding: var(--nymbal-spacing-xl) 0;\n}\n`\n\nexport class NymbalCartDrawer extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['open']\n }\n\n private readonly handleItemAdded = (): void => {\n this.open()\n }\n\n private readonly handleMiniCartClicked = (): void => {\n this.toggle()\n }\n\n protected setup(): void {\n injectStyles('nymbal-cart-drawer', STYLES)\n this.subscribeToStore(this.client.cart)\n document.addEventListener('nymbal:cart:item-added', this.handleItemAdded)\n document.addEventListener('nymbal:mini-cart:clicked', this.handleMiniCartClicked)\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n document.removeEventListener('nymbal:cart:item-added', this.handleItemAdded)\n document.removeEventListener('nymbal:mini-cart:clicked', this.handleMiniCartClicked)\n }\n\n open(): void {\n this.setAttribute('open', '')\n window.dispatchEvent(new CustomEvent('nymbal:cart:open'))\n }\n\n close(): void {\n this.removeAttribute('open')\n window.dispatchEvent(new CustomEvent('nymbal:cart:close'))\n }\n\n toggle(): void {\n if (this.hasAttribute('open')) {\n this.close()\n } else {\n this.open()\n }\n }\n\n protected render(): void {\n const state = this.client.cart.getState()\n const currency = state.currency\n\n const itemsHtml = state.items.length === 0\n ? `<div class=\"nymbal-drawer-empty\">Your cart is empty</div>`\n : state.items.map((item) => `\n <div class=\"nymbal-cart-item\" data-testid=\"cart-item-${item.variantId}\">\n <div class=\"nymbal-cart-item-info\">\n <div class=\"nymbal-cart-item-name\">${this.esc(item.productName)}</div>\n <div class=\"nymbal-cart-item-variant\">${this.esc(item.variantName)}</div>\n <div class=\"nymbal-cart-item-qty\">Qty: ${item.qty}</div>\n </div>\n <div class=\"nymbal-cart-item-actions\">\n <span>${this.formatPrice(item.priceMinor * item.qty, currency)}</span>\n <button type=\"button\" class=\"nymbal-cart-item-remove\" data-testid=\"remove-item-${item.variantId}\" data-variant-id=\"${item.variantId}\">Remove</button>\n </div>\n </div>\n `).join('')\n\n this.innerHTML = `\n <div class=\"nymbal-drawer-overlay\" data-testid=\"cart-drawer-overlay\"></div>\n <div class=\"nymbal-drawer-panel\" role=\"dialog\" aria-label=\"Shopping cart\" aria-modal=\"true\" data-testid=\"cart-drawer\">\n <div class=\"nymbal-drawer-header\">\n <h2>Cart (${state.itemCount})</h2>\n <button type=\"button\" class=\"nymbal-drawer-close\" data-testid=\"cart-drawer-close\" aria-label=\"Close cart\">×</button>\n </div>\n <div class=\"nymbal-drawer-items\">${itemsHtml}</div>\n ${state.items.length > 0 ? `\n <div class=\"nymbal-drawer-footer\">\n <div class=\"nymbal-cart-subtotal\">\n <span>Subtotal</span>\n <span>${this.formatPrice(state.subtotalMinor, currency)}</span>\n </div>\n <button type=\"button\" class=\"nymbal-checkout-btn\" data-testid=\"cart-checkout-btn\">Checkout</button>\n </div>\n ` : ''}\n </div>\n `\n\n this.querySelector('.nymbal-drawer-overlay')?.addEventListener('click', () => this.close())\n this.querySelector('.nymbal-drawer-close')?.addEventListener('click', () => this.close())\n this.querySelector('.nymbal-checkout-btn')?.addEventListener('click', () => {\n this.dispatchEvent(new CustomEvent('nymbal:cart:checkout-clicked', { bubbles: true, composed: true }))\n })\n this.querySelectorAll('.nymbal-cart-item-remove').forEach((btn) => {\n btn.addEventListener('click', () => {\n const variantId = (btn as HTMLElement).dataset['variantId']\n if (variantId) this.client.cart.removeItem(variantId)\n })\n })\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private esc(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n\n private formatPrice(minor: number, currency: string): string {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n }).format(minor / 100)\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-mini-cart {\n display: inline-block;\n position: relative;\n}\nnymbal-mini-cart button {\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n padding: var(--nymbal-spacing-sm);\n font-family: var(--nymbal-font-family);\n color: var(--nymbal-color-text);\n}\nnymbal-mini-cart .nymbal-mini-cart-icon {\n width: 24px;\n height: 24px;\n}\nnymbal-mini-cart .nymbal-mini-cart-badge {\n position: absolute;\n top: 0;\n right: 0;\n background: var(--nymbal-color-accent);\n color: var(--nymbal-color-text-inverse);\n font-size: var(--nymbal-font-size-xs);\n font-weight: var(--nymbal-font-weight-bold);\n min-width: 18px;\n height: 18px;\n border-radius: var(--nymbal-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n}\nnymbal-mini-cart .nymbal-mini-cart-badge:empty {\n display: none;\n}\n`\n\nconst CART_SVG = `<svg class=\"nymbal-mini-cart-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"9\" cy=\"21\" r=\"1\"/><circle cx=\"20\" cy=\"21\" r=\"1\"/><path d=\"M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6\"/></svg>`\n\nexport class NymbalMiniCart extends NymbalElement {\n protected setup(): void {\n injectStyles('nymbal-mini-cart', STYLES)\n this.subscribeToStore(this.client.cart)\n }\n\n protected render(): void {\n const { itemCount } = this.client.cart.getState()\n\n this.innerHTML = `\n <button type=\"button\" data-testid=\"mini-cart\" aria-label=\"Cart (${itemCount} items)\">\n ${CART_SVG}\n <span class=\"nymbal-mini-cart-badge\" data-testid=\"mini-cart-badge\">${itemCount > 0 ? itemCount : ''}</span>\n </button>\n `\n\n this.querySelector('button')?.addEventListener('click', () => {\n this.dispatchEvent(new CustomEvent('nymbal:mini-cart:clicked', { bubbles: true, composed: true }))\n })\n }\n}\n","import type { DenormalisedVariant, DenormalisedProduct } from '@nymbalhq/sdk'\nimport type { VariantOption } from '@nymbalhq/types'\nimport { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-variant-selector {\n display: block;\n font-family: var(--nymbal-font-family);\n}\nnymbal-variant-selector .nymbal-option-group {\n margin-bottom: var(--nymbal-spacing-md);\n}\nnymbal-variant-selector .nymbal-option-label {\n display: block;\n font-size: var(--nymbal-font-size-sm);\n font-weight: var(--nymbal-font-weight-medium);\n margin-bottom: var(--nymbal-spacing-sm);\n color: var(--nymbal-color-text);\n}\nnymbal-variant-selector .nymbal-option-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: var(--nymbal-spacing-sm);\n}\nnymbal-variant-selector .nymbal-option-btn {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-sm);\n background: var(--nymbal-color-surface);\n color: var(--nymbal-color-text);\n border: 1px solid var(--nymbal-color-border);\n border-radius: var(--nymbal-radius-md);\n padding: var(--nymbal-spacing-xs) var(--nymbal-spacing-md);\n cursor: pointer;\n transition: border-color var(--nymbal-transition-fast), background var(--nymbal-transition-fast);\n}\nnymbal-variant-selector .nymbal-option-btn:hover {\n border-color: var(--nymbal-color-primary);\n}\nnymbal-variant-selector .nymbal-option-btn[aria-pressed=\"true\"] {\n background: var(--nymbal-color-primary);\n color: var(--nymbal-color-text-inverse);\n border-color: var(--nymbal-color-primary);\n}\nnymbal-variant-selector .nymbal-option-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n`\n\ninterface OptionGroup {\n name: string\n values: string[]\n}\n\nexport class NymbalVariantSelector extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['options']\n }\n\n protected setup(): void {\n injectStyles('nymbal-variant-selector', STYLES)\n this.subscribeToStore(this.client.product)\n }\n\n protected render(): void {\n const state = this.client.product.getState()\n const product = state.product\n if (!product) {\n this.innerHTML = ''\n return\n }\n\n const optionGroups = this.getOptionGroups(product)\n const selected = state.selectedVariant\n\n this.innerHTML = optionGroups.map((group) => `\n <div class=\"nymbal-option-group\">\n <span class=\"nymbal-option-label\">${this.esc(group.name)}</span>\n <div class=\"nymbal-option-buttons\">\n ${group.values.map((value) => {\n const isSelected = selected?.options.some(\n (o: VariantOption) => o.name === group.name && o.value === value,\n ) ?? false\n return `<button\n class=\"nymbal-option-btn\"\n data-testid=\"variant-option-${this.esc(group.name)}-${this.esc(value)}\"\n data-option-name=\"${this.esc(group.name)}\"\n data-option-value=\"${this.esc(value)}\"\n aria-pressed=\"${isSelected}\"\n >${this.esc(value)}</button>`\n }).join('')}\n </div>\n </div>\n `).join('')\n\n this.querySelectorAll('.nymbal-option-btn').forEach((btn) => {\n btn.addEventListener('click', () => {\n const el = btn as HTMLElement\n const optionName = el.dataset['optionName']\n const optionValue = el.dataset['optionValue']\n if (!optionName || !optionValue || !product) return\n\n const currentSelections = new Map<string, string>()\n if (selected) {\n for (const opt of selected.options as VariantOption[]) {\n currentSelections.set(opt.name, opt.value)\n }\n }\n currentSelections.set(optionName, optionValue)\n\n const match = product.variants.find((v: DenormalisedVariant) => {\n const vRaw = v.options\n let vOpts: VariantOption[] = []\n if (typeof vRaw === 'string') {\n try { vOpts = JSON.parse(vRaw) } catch (e) { console.warn('[variant-selector] malformed variant options JSON', e); vOpts = [] }\n } else if (Array.isArray(vRaw)) {\n vOpts = vRaw\n }\n return vOpts.every((o) => currentSelections.get(o.name) === o.value)\n })\n\n if (match) {\n this.client.product.selectVariant(match)\n this.dispatchEvent(\n new CustomEvent('nymbal:variant:selected', {\n bubbles: true,\n composed: true,\n detail: { variant: match },\n }),\n )\n }\n })\n })\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private getOptionGroups(product: DenormalisedProduct): OptionGroup[] {\n const optionsAttr = this.getAttribute('options')\n if (optionsAttr) {\n try {\n return JSON.parse(optionsAttr) as OptionGroup[]\n } catch (e) {\n console.warn('[variant-selector] malformed options attribute, falling back to variant derivation', e)\n }\n }\n\n const groups = new Map<string, Set<string>>()\n for (const variant of product.variants) {\n const raw = variant.options\n let opts: VariantOption[] = []\n if (typeof raw === 'string') {\n try { opts = JSON.parse(raw) } catch (e) { console.warn('[variant-selector] malformed variant options in getOptionGroups', e); opts = [] }\n } else if (Array.isArray(raw)) {\n opts = raw\n }\n for (const opt of opts) {\n let values = groups.get(opt.name)\n if (!values) {\n values = new Set()\n groups.set(opt.name, values)\n }\n values.add(opt.value)\n }\n }\n return Array.from(groups.entries()).map(([name, values]) => ({\n name,\n values: Array.from(values),\n }))\n }\n\n private esc(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-quantity-selector {\n display: inline-flex;\n align-items: center;\n font-family: var(--nymbal-font-family);\n}\nnymbal-quantity-selector button {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n background: var(--nymbal-color-surface);\n color: var(--nymbal-color-text);\n border: 1px solid var(--nymbal-color-border);\n width: 36px;\n height: 36px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--nymbal-transition-fast);\n}\nnymbal-quantity-selector button:hover:not(:disabled) {\n background: var(--nymbal-color-secondary, #f4f4f5);\n}\nnymbal-quantity-selector button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\nnymbal-quantity-selector button:first-child {\n border-radius: var(--nymbal-radius-md) 0 0 var(--nymbal-radius-md);\n}\nnymbal-quantity-selector button:last-child {\n border-radius: 0 var(--nymbal-radius-md) var(--nymbal-radius-md) 0;\n}\nnymbal-quantity-selector input {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n width: 48px;\n height: 36px;\n text-align: center;\n border: 1px solid var(--nymbal-color-border);\n border-left: none;\n border-right: none;\n -moz-appearance: textfield;\n}\nnymbal-quantity-selector input::-webkit-outer-spin-button,\nnymbal-quantity-selector input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n`\n\nexport class NymbalQuantitySelector extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['value', 'min', 'max']\n }\n\n private _value = 1\n\n protected setup(): void {\n injectStyles('nymbal-quantity-selector', STYLES)\n this._value = Number(this.getAttribute('value')) || 1\n }\n\n protected render(): void {\n const min = Number(this.getAttribute('min')) || 1\n const max = Number(this.getAttribute('max')) || 99\n\n this.innerHTML = `\n <button data-testid=\"qty-decrement\" aria-label=\"Decrease quantity\" ${this._value <= min ? 'disabled' : ''}>−</button>\n <input data-testid=\"qty-input\" type=\"number\" value=\"${this._value}\" min=\"${min}\" max=\"${max}\" aria-label=\"Quantity\">\n <button data-testid=\"qty-increment\" aria-label=\"Increase quantity\" ${this._value >= max ? 'disabled' : ''}>+</button>\n `\n\n this.querySelector('[data-testid=\"qty-decrement\"]')?.addEventListener('click', () => {\n if (this._value > min) {\n this._value--\n this.emitChange()\n this.render()\n }\n })\n\n this.querySelector('[data-testid=\"qty-increment\"]')?.addEventListener('click', () => {\n if (this._value < max) {\n this._value++\n this.emitChange()\n this.render()\n }\n })\n\n const input = this.querySelector('input')\n input?.addEventListener('change', () => {\n const val = Math.min(max, Math.max(min, Number(input.value) || min))\n this._value = val\n this.emitChange()\n this.render()\n })\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'value') {\n this._value = Number(this.getAttribute('value')) || 1\n }\n if (this.isConnected) this.requestRender()\n }\n\n private emitChange(): void {\n this.dispatchEvent(\n new CustomEvent('nymbal:quantity:changed', {\n bubbles: true,\n composed: true,\n detail: { value: this._value },\n }),\n )\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\nimport type { DenormalisedProduct } from '@nymbalhq/sdk'\n\nconst STYLES = `\nnymbal-search-bar {\n display: block;\n position: relative;\n font-family: var(--nymbal-font-family);\n}\nnymbal-search-bar input {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n width: 100%;\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-md);\n border: 1px solid var(--nymbal-color-border);\n border-radius: var(--nymbal-radius-md);\n outline: none;\n transition: border-color var(--nymbal-transition-fast);\n box-sizing: border-box;\n}\nnymbal-search-bar input:focus {\n border-color: var(--nymbal-color-accent);\n}\nnymbal-search-bar .nymbal-search-results {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background: var(--nymbal-color-surface);\n border: 1px solid var(--nymbal-color-border);\n border-top: none;\n border-radius: 0 0 var(--nymbal-radius-md) var(--nymbal-radius-md);\n box-shadow: var(--nymbal-shadow-md);\n z-index: var(--nymbal-z-dropdown);\n max-height: 300px;\n overflow-y: auto;\n}\nnymbal-search-bar .nymbal-search-result-item {\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-md);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast);\n}\nnymbal-search-bar .nymbal-search-result-item:hover,\nnymbal-search-bar .nymbal-search-result-item[aria-selected=\"true\"] {\n background: var(--nymbal-color-secondary, #f4f4f5);\n}\nnymbal-search-bar .nymbal-search-result-name {\n font-weight: var(--nymbal-font-weight-medium);\n}\nnymbal-search-bar .nymbal-search-result-price {\n font-size: var(--nymbal-font-size-sm);\n color: var(--nymbal-color-text-muted);\n}\nnymbal-search-bar .nymbal-search-loading {\n padding: var(--nymbal-spacing-md);\n text-align: center;\n color: var(--nymbal-color-text-muted);\n font-size: var(--nymbal-font-size-sm);\n}\n`\n\nexport class NymbalSearchBar extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'debounce-ms']\n }\n\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n private selectedIndex = -1\n\n protected setup(): void {\n injectStyles('nymbal-search-bar', STYLES)\n this.subscribeToStore(this.client.search)\n }\n\n protected render(): void {\n const state = this.client.search.getState()\n const placeholder = this.getAttribute('placeholder') ?? 'Search products...'\n\n let resultsHtml = ''\n if (state.query) {\n if (state.loading) {\n resultsHtml = `<div class=\"nymbal-search-results\" data-testid=\"search-results\"><div class=\"nymbal-search-loading\">Searching...</div></div>`\n } else if (state.results.length > 0) {\n resultsHtml = `<div class=\"nymbal-search-results\" data-testid=\"search-results\">\n ${state.results\n .map(\n (product, i) => `\n <div class=\"nymbal-search-result-item\" data-testid=\"search-result-${this.esc(product.slug)}\" data-slug=\"${this.esc(product.slug)}\" aria-selected=\"${i === this.selectedIndex}\">\n <div class=\"nymbal-search-result-name\">${this.esc(product.name)}</div>\n ${product.priceMinor !== null ? `<div class=\"nymbal-search-result-price\">${this.formatPrice(product.priceMinor, product.currency ?? 'USD')}</div>` : ''}\n </div>\n `,\n )\n .join('')}\n </div>`\n }\n }\n\n this.innerHTML = `\n <input data-testid=\"search-input\" type=\"search\" placeholder=\"${this.esc(placeholder)}\" value=\"${this.esc(state.query)}\" autocomplete=\"off\" role=\"combobox\" aria-expanded=\"${state.results.length > 0}\" aria-haspopup=\"listbox\">\n ${resultsHtml}\n `\n\n this.querySelectorAll<HTMLElement>('.nymbal-search-result-item').forEach((item, i) => {\n item.addEventListener('click', () => {\n const product = state.results[i]\n if (product) this.select(product)\n })\n })\n\n const input = this.querySelector('input')!\n input.addEventListener('input', () => {\n const query = input.value\n const debounceMs = Number(this.getAttribute('debounce-ms')) || 300\n if (this.debounceTimer !== null) clearTimeout(this.debounceTimer)\n this.debounceTimer = setTimeout(() => {\n this.selectedIndex = -1\n if (query.trim()) {\n this.client.search.search(query)\n } else {\n this.client.search.clearSearch()\n }\n }, debounceMs)\n })\n\n input.addEventListener('keydown', (e) => {\n const results = state.results\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n this.selectedIndex = Math.min(this.selectedIndex + 1, results.length - 1)\n this.render()\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n this.selectedIndex = Math.max(this.selectedIndex - 1, -1)\n this.render()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n const selected = this.selectedIndex >= 0 ? results[this.selectedIndex] : undefined\n if (selected) {\n this.select(selected)\n } else {\n const query = input.value.trim()\n if (query) {\n window.location.href = `/search?q=${encodeURIComponent(query)}`\n }\n }\n } else if (e.key === 'Escape') {\n this.client.search.clearSearch()\n this.selectedIndex = -1\n }\n })\n }\n\n private select(product: DenormalisedProduct): void {\n this.dispatchEvent(\n new CustomEvent('nymbal:search:selected', {\n bubbles: true,\n composed: true,\n detail: { product, query: product.name },\n }),\n )\n this.client.search.clearSearch()\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n }\n\n /** Escapes a value for both text and quoted-attribute contexts. */\n private esc(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n }\n\n private formatPrice(minor: number, currency: string): string {\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(minor / 100)\n }\n}\n","import type { Facet } from '@nymbalhq/types'\nimport { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-product-filter {\n display: block;\n font-family: var(--nymbal-font-family);\n}\nnymbal-product-filter .nymbal-filter-group {\n margin-bottom: var(--nymbal-spacing-lg);\n}\nnymbal-product-filter .nymbal-filter-label {\n display: block;\n font-size: var(--nymbal-font-size-sm);\n font-weight: var(--nymbal-font-weight-bold);\n margin-bottom: var(--nymbal-spacing-sm);\n color: var(--nymbal-color-text);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\nnymbal-product-filter .nymbal-filter-option {\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm);\n padding: var(--nymbal-spacing-xs) 0;\n cursor: pointer;\n font-size: var(--nymbal-font-size-sm);\n color: var(--nymbal-color-text);\n}\nnymbal-product-filter .nymbal-filter-option input[type=\"checkbox\"] {\n accent-color: var(--nymbal-color-primary);\n}\nnymbal-product-filter .nymbal-filter-count {\n color: var(--nymbal-color-text-muted);\n font-size: var(--nymbal-font-size-xs);\n}\n`\n\n\nexport class NymbalProductFilter extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['facets']\n }\n\n protected setup(): void {\n injectStyles('nymbal-product-filter', STYLES)\n this.subscribeToStore(this.client.productList)\n }\n\n protected render(): void {\n const state = this.client.productList.getState()\n const facets = this.getFacets()\n\n if (facets.length === 0) {\n this.innerHTML = ''\n return\n }\n\n this.innerHTML = facets.map((facet) => {\n const activeFilter = state.filters[facet.field]\n const activeValues = Array.isArray(activeFilter)\n ? activeFilter\n : (activeFilter ? [activeFilter] : [])\n\n return `\n <div class=\"nymbal-filter-group\" data-testid=\"filter-group-${facet.field}\">\n <span class=\"nymbal-filter-label\">${this.esc(facet.label)}</span>\n ${facet.values.map((v) => {\n const checked = activeValues.includes(v.value)\n return `\n <label class=\"nymbal-filter-option\" data-testid=\"filter-option-${v.value}\">\n <input type=\"checkbox\" data-filter-name=\"${facet.field}\" data-filter-value=\"${v.value}\" ${checked ? 'checked' : ''}>\n <span>${this.esc(v.label ?? v.value)}</span>\n ${v.count !== undefined ? `<span class=\"nymbal-filter-count\">(${v.count})</span>` : ''}\n </label>\n `\n }).join('')}\n </div>\n `\n }).join('')\n\n this.querySelectorAll('input[type=\"checkbox\"]').forEach((input) => {\n input.addEventListener('change', () => {\n const el = input as HTMLInputElement\n const name = el.dataset['filterName']!\n const value = el.dataset['filterValue']!\n if (el.checked) {\n const current = state.filters[name]\n const values = Array.isArray(current) ? [...current, value] : (current ? [current, value] : value)\n this.client.productList.applyFilter(name, values)\n } else {\n const current = state.filters[name]\n if (Array.isArray(current)) {\n const remaining = current.filter((v) => v !== value)\n if (remaining.length > 0) {\n this.client.productList.applyFilter(name, remaining)\n } else {\n this.client.productList.removeFilter(name)\n }\n } else {\n this.client.productList.removeFilter(name)\n }\n }\n this.dispatchEvent(\n new CustomEvent('nymbal:filter:changed', { bubbles: true, composed: true, detail: { name, value } }),\n )\n })\n })\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private getFacets(): Facet[] {\n const attr = this.getAttribute('facets')\n if (!attr) return []\n try {\n return JSON.parse(attr) as Facet[]\n } catch (err) {\n console.error('[nymbal-product-filter] failed to parse facets attribute', err)\n return []\n }\n }\n\n private esc(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n}\n","import { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-toast {\n position: fixed;\n z-index: var(--nymbal-z-toast);\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-sm);\n pointer-events: none;\n font-family: var(--nymbal-font-family);\n}\nnymbal-toast[position=\"top-right\"],\nnymbal-toast:not([position]) {\n top: var(--nymbal-spacing-lg);\n right: var(--nymbal-spacing-lg);\n}\nnymbal-toast[position=\"top-left\"] {\n top: var(--nymbal-spacing-lg);\n left: var(--nymbal-spacing-lg);\n}\nnymbal-toast[position=\"bottom-right\"] {\n bottom: var(--nymbal-spacing-lg);\n right: var(--nymbal-spacing-lg);\n}\nnymbal-toast[position=\"bottom-left\"] {\n bottom: var(--nymbal-spacing-lg);\n left: var(--nymbal-spacing-lg);\n}\nnymbal-toast .nymbal-toast-item {\n pointer-events: auto;\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm);\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-md);\n border-radius: var(--nymbal-radius-md);\n box-shadow: var(--nymbal-shadow-lg);\n font-size: var(--nymbal-font-size-sm);\n background: var(--nymbal-color-surface);\n color: var(--nymbal-color-text);\n border: 1px solid var(--nymbal-color-border);\n animation: nymbal-toast-in var(--nymbal-transition-normal) forwards;\n max-width: 360px;\n}\nnymbal-toast .nymbal-toast-item[data-type=\"success\"] {\n border-left: 3px solid var(--nymbal-color-success);\n}\nnymbal-toast .nymbal-toast-item[data-type=\"error\"] {\n border-left: 3px solid var(--nymbal-color-error);\n}\nnymbal-toast .nymbal-toast-item[data-type=\"warning\"] {\n border-left: 3px solid var(--nymbal-color-warning);\n}\nnymbal-toast .nymbal-toast-item[data-type=\"info\"] {\n border-left: 3px solid var(--nymbal-color-accent);\n}\nnymbal-toast .nymbal-toast-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--nymbal-color-text-muted);\n font-size: var(--nymbal-font-size-base);\n padding: 0;\n margin-left: auto;\n}\n@keyframes nymbal-toast-in {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n`\n\nconst HTMLElementBase =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement)\n\nexport class NymbalToast extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['position', 'duration-ms']\n }\n\n connectedCallback(): void {\n injectStyles('nymbal-toast', STYLES)\n this.setAttribute('data-testid', 'toast')\n }\n\n show(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n const durationMs = Number(this.getAttribute('duration-ms')) || 4000\n\n const item = document.createElement('div')\n item.className = 'nymbal-toast-item'\n item.setAttribute('data-type', type)\n item.setAttribute('role', 'alert')\n\n const text = document.createElement('span')\n text.textContent = message\n\n const close = document.createElement('button')\n close.className = 'nymbal-toast-close'\n close.setAttribute('aria-label', 'Dismiss')\n close.textContent = '×'\n close.addEventListener('click', () => this.dismiss(item))\n\n item.appendChild(text)\n item.appendChild(close)\n this.appendChild(item)\n\n setTimeout(() => this.dismiss(item), durationMs)\n }\n\n private dismiss(item: HTMLElement): void {\n if (item.parentNode === this) {\n this.removeChild(item)\n }\n }\n\n static showGlobal(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n let toastEl = document.querySelector('nymbal-toast') as NymbalToast | null\n if (!toastEl) {\n toastEl = document.createElement('nymbal-toast') as NymbalToast\n document.body.appendChild(toastEl)\n }\n toastEl.show(message, type)\n }\n}\n","export { registerClient, getClient } from './registry.js'\nexport { NymbalElement } from './base-element.js'\nexport { injectStyles } from './styles.js'\n\nimport { NymbalAddToCart } from './components/add-to-cart.js'\nimport { NymbalCartDrawer } from './components/cart-drawer.js'\nimport { NymbalMiniCart } from './components/mini-cart.js'\nimport { NymbalVariantSelector } from './components/variant-selector.js'\nimport { NymbalQuantitySelector } from './components/quantity-selector.js'\nimport { NymbalSearchBar } from './components/search-bar.js'\nimport { NymbalProductFilter } from './components/product-filter.js'\nimport { NymbalToast } from './components/toast.js'\n\nexport {\n NymbalAddToCart,\n NymbalCartDrawer,\n NymbalMiniCart,\n NymbalVariantSelector,\n NymbalQuantitySelector,\n NymbalSearchBar,\n NymbalProductFilter,\n NymbalToast,\n}\n\nexport function registerComponents(): void {\n const components: Array<[string, CustomElementConstructor]> = [\n ['nymbal-add-to-cart', NymbalAddToCart],\n ['nymbal-cart-drawer', NymbalCartDrawer],\n ['nymbal-mini-cart', NymbalMiniCart],\n ['nymbal-variant-selector', NymbalVariantSelector],\n ['nymbal-quantity-selector', NymbalQuantitySelector],\n ['nymbal-search-bar', NymbalSearchBar],\n ['nymbal-product-filter', NymbalProductFilter],\n ['nymbal-toast', NymbalToast],\n ]\n\n for (const [name, ctor] of components) {\n if (!customElements.get(name)) {\n customElements.define(name, ctor)\n }\n }\n}\n"],"mappings":"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","names":["STYLES","STYLES","STYLES","STYLES","STYLES","STYLES","STYLES","HTMLElementBase"]}
|
|
1
|
+
{"version":3,"sources":["../src/registry.ts","../src/base-element.ts","../src/styles.ts","../src/components/add-to-cart.ts","../src/components/cart-drawer.ts","../src/components/mini-cart.ts","../src/components/variant-selector.ts","../src/components/quantity-selector.ts","../src/components/search-bar.ts","../src/components/product-filter.ts","../src/components/toast.ts","../src/components/payment-element.ts","../src/components/sort-dropdown.ts","../src/index.ts","../src/payment-drivers/test-mode.ts","../src/payment-drivers/stripe.ts"],"sourcesContent":["import type { NymbalClient } from '@nymbalhq/sdk'\n\nconst CLIENT_KEY = Symbol.for('nymbal.client')\n\nexport function registerClient(client: NymbalClient): void {\n (globalThis as Record<symbol, unknown>)[CLIENT_KEY] = client\n}\n\nexport function getClient(): NymbalClient {\n const client = (globalThis as Record<symbol, unknown>)[CLIENT_KEY] as NymbalClient | undefined\n if (!client) {\n throw new Error('NymbalClient not registered. Call registerClient() before using components.')\n }\n return client\n}\n","import type { NymbalClient } from '@nymbalhq/sdk'\nimport { getClient } from './registry.js'\n\n/** `HTMLElement`, or a stand-in on a server, where the module is imported but no element is ever made. */\nexport const HTMLElementBase =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement)\n\nexport abstract class NymbalElement extends HTMLElementBase {\n private _unsubs: Array<() => void> = []\n private _renderQueued = false\n\n protected get client(): NymbalClient {\n return getClient()\n }\n\n protected subscribeToStore(store: { subscribe(fn: () => void): () => void }): void {\n this._unsubs.push(store.subscribe(() => this.requestRender()))\n }\n\n protected requestRender(): void {\n if (this._renderQueued) return\n this._renderQueued = true\n queueMicrotask(() => {\n this._renderQueued = false\n this.render()\n })\n }\n\n connectedCallback(): void {\n this.setup()\n this.render()\n }\n\n disconnectedCallback(): void {\n for (const fn of this._unsubs) fn()\n this._unsubs = []\n }\n\n protected abstract setup(): void\n protected abstract render(): void\n}\n","const injected = new Set<string>()\n\nexport function injectStyles(tagName: string, css: string): void {\n if (injected.has(tagName)) return\n if (typeof document === 'undefined') return\n const style = document.createElement('style')\n style.setAttribute('data-nymbal', tagName)\n style.textContent = css\n document.head.appendChild(style)\n injected.add(tagName)\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-add-to-cart {\n display: inline-block;\n}\nnymbal-add-to-cart button {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n font-weight: var(--nymbal-font-weight-medium);\n background: var(--nymbal-color-primary);\n color: var(--nymbal-color-text-inverse);\n border: none;\n border-radius: var(--nymbal-radius-md);\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-lg);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast);\n min-width: 140px;\n}\nnymbal-add-to-cart button:hover:not(:disabled) {\n background: var(--nymbal-color-primary-hover);\n}\nnymbal-add-to-cart button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n`\n\nconst MIN_QTY = 1\nconst MAX_QTY = 99\n\n/** The `qty` attribute as a whole number of units: a non-integer is 1, and the result is clamped to 1-99. */\nfunction quantityOf(attr: string | null): number {\n const n = attr === null || attr.trim() === '' ? Number.NaN : Number(attr)\n const whole = Number.isInteger(n) ? n : MIN_QTY\n return Math.min(MAX_QTY, Math.max(MIN_QTY, whole))\n}\n\nexport class NymbalAddToCart extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['variant-id', 'qty', 'disabled']\n }\n\n private button!: HTMLButtonElement\n\n protected setup(): void {\n injectStyles('nymbal-add-to-cart', STYLES)\n this.subscribeToStore(this.client.cart)\n }\n\n protected render(): void {\n const variantId = this.getAttribute('variant-id')\n const isDisabled = this.hasAttribute('disabled')\n const cartState = this.client.cart.getState()\n\n if (!this.button) {\n this.button = document.createElement('button')\n this.button.setAttribute('type', 'button')\n this.button.setAttribute('data-testid', 'add-to-cart')\n this.button.addEventListener('click', this.handleClick)\n this.innerHTML = ''\n this.appendChild(this.button)\n }\n\n this.button.disabled = isDisabled || cartState.loading || !variantId\n this.button.setAttribute('aria-busy', String(cartState.loading))\n this.button.textContent = cartState.loading ? 'Adding...' : 'Add to Cart'\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private handleClick = async (): Promise<void> => {\n const variantId = this.getAttribute('variant-id')\n if (!variantId) return\n const qty = quantityOf(this.getAttribute('qty'))\n try {\n await this.client.cart.addItem(variantId, qty)\n this.dispatchEvent(\n new CustomEvent('nymbal:cart:item-added', {\n bubbles: true,\n composed: true,\n detail: { variantId, qty },\n }),\n )\n } catch {\n // error is stored in cart state\n }\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-cart-drawer {\n display: block;\n}\nnymbal-cart-drawer .nymbal-drawer-overlay {\n position: fixed;\n inset: 0;\n background: var(--nymbal-color-overlay);\n z-index: var(--nymbal-z-drawer);\n opacity: 0;\n visibility: hidden;\n transition: opacity var(--nymbal-transition-normal), visibility var(--nymbal-transition-normal);\n}\nnymbal-cart-drawer[open] .nymbal-drawer-overlay {\n opacity: 1;\n visibility: visible;\n}\nnymbal-cart-drawer .nymbal-drawer-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(400px, 90vw);\n background: var(--nymbal-color-surface);\n z-index: var(--nymbal-z-drawer);\n transform: translateX(100%);\n transition: transform var(--nymbal-transition-normal);\n display: flex;\n flex-direction: column;\n font-family: var(--nymbal-font-family);\n}\nnymbal-cart-drawer[open] .nymbal-drawer-panel {\n transform: translateX(0);\n}\nnymbal-cart-drawer .nymbal-drawer-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: var(--nymbal-spacing-md) var(--nymbal-spacing-lg);\n border-bottom: 1px solid var(--nymbal-color-border);\n}\nnymbal-cart-drawer .nymbal-drawer-header h2 {\n margin: 0;\n font-size: var(--nymbal-font-size-lg);\n font-weight: var(--nymbal-font-weight-bold);\n}\nnymbal-cart-drawer .nymbal-drawer-close {\n background: none;\n border: none;\n font-size: var(--nymbal-font-size-xl);\n cursor: pointer;\n padding: var(--nymbal-spacing-xs);\n color: var(--nymbal-color-text-muted);\n}\nnymbal-cart-drawer .nymbal-drawer-items {\n flex: 1;\n overflow-y: auto;\n padding: var(--nymbal-spacing-md) var(--nymbal-spacing-lg);\n}\nnymbal-cart-drawer .nymbal-cart-item {\n display: flex;\n gap: var(--nymbal-spacing-md);\n padding: var(--nymbal-spacing-md) 0;\n border-bottom: 1px solid var(--nymbal-color-border);\n}\nnymbal-cart-drawer .nymbal-cart-item-info {\n flex: 1;\n}\nnymbal-cart-drawer .nymbal-cart-item-name {\n font-weight: var(--nymbal-font-weight-medium);\n margin-bottom: var(--nymbal-spacing-xs);\n}\nnymbal-cart-drawer .nymbal-cart-item-variant {\n font-size: var(--nymbal-font-size-sm);\n color: var(--nymbal-color-text-muted);\n}\nnymbal-cart-drawer .nymbal-cart-item-actions {\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm);\n}\nnymbal-cart-drawer .nymbal-cart-item-remove {\n background: none;\n border: none;\n color: var(--nymbal-color-error);\n cursor: pointer;\n font-size: var(--nymbal-font-size-sm);\n padding: var(--nymbal-spacing-xs);\n}\nnymbal-cart-drawer .nymbal-drawer-footer {\n padding: var(--nymbal-spacing-md) var(--nymbal-spacing-lg);\n border-top: 1px solid var(--nymbal-color-border);\n}\nnymbal-cart-drawer .nymbal-cart-subtotal {\n display: flex;\n justify-content: space-between;\n font-weight: var(--nymbal-font-weight-bold);\n margin-bottom: var(--nymbal-spacing-md);\n}\nnymbal-cart-drawer .nymbal-checkout-btn {\n width: 100%;\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n font-weight: var(--nymbal-font-weight-medium);\n background: var(--nymbal-color-primary);\n color: var(--nymbal-color-text-inverse);\n border: none;\n border-radius: var(--nymbal-radius-md);\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-lg);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast);\n}\nnymbal-cart-drawer .nymbal-checkout-btn:hover {\n background: var(--nymbal-color-primary-hover);\n}\nnymbal-cart-drawer .nymbal-drawer-empty {\n text-align: center;\n color: var(--nymbal-color-text-muted);\n padding: var(--nymbal-spacing-xl) 0;\n}\n`\n\nexport class NymbalCartDrawer extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['open']\n }\n\n private readonly handleItemAdded = (): void => {\n this.open()\n }\n\n private readonly handleMiniCartClicked = (): void => {\n this.toggle()\n }\n\n protected setup(): void {\n injectStyles('nymbal-cart-drawer', STYLES)\n this.subscribeToStore(this.client.cart)\n document.addEventListener('nymbal:cart:item-added', this.handleItemAdded)\n document.addEventListener('nymbal:mini-cart:clicked', this.handleMiniCartClicked)\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n document.removeEventListener('nymbal:cart:item-added', this.handleItemAdded)\n document.removeEventListener('nymbal:mini-cart:clicked', this.handleMiniCartClicked)\n }\n\n open(): void {\n this.setAttribute('open', '')\n window.dispatchEvent(new CustomEvent('nymbal:cart:open'))\n }\n\n close(): void {\n this.removeAttribute('open')\n window.dispatchEvent(new CustomEvent('nymbal:cart:close'))\n }\n\n toggle(): void {\n if (this.hasAttribute('open')) {\n this.close()\n } else {\n this.open()\n }\n }\n\n protected render(): void {\n const state = this.client.cart.getState()\n const currency = state.currency\n\n const itemsHtml = state.items.length === 0\n ? `<div class=\"nymbal-drawer-empty\">Your cart is empty</div>`\n : state.items.map((item) => `\n <div class=\"nymbal-cart-item\" data-testid=\"cart-item-${item.variantId}\">\n <div class=\"nymbal-cart-item-info\">\n <div class=\"nymbal-cart-item-name\">${this.esc(item.productName)}</div>\n <div class=\"nymbal-cart-item-variant\">${this.esc(item.variantName)}</div>\n <div class=\"nymbal-cart-item-qty\">Qty: ${item.qty}</div>\n </div>\n <div class=\"nymbal-cart-item-actions\">\n <span>${this.formatPrice(item.priceMinor * item.qty, currency)}</span>\n <button type=\"button\" class=\"nymbal-cart-item-remove\" data-testid=\"remove-item-${item.variantId}\" data-variant-id=\"${item.variantId}\">Remove</button>\n </div>\n </div>\n `).join('')\n\n this.innerHTML = `\n <div class=\"nymbal-drawer-overlay\" data-testid=\"cart-drawer-overlay\"></div>\n <div class=\"nymbal-drawer-panel\" role=\"dialog\" aria-label=\"Shopping cart\" aria-modal=\"true\" data-testid=\"cart-drawer\">\n <div class=\"nymbal-drawer-header\">\n <h2>Cart (${state.itemCount})</h2>\n <button type=\"button\" class=\"nymbal-drawer-close\" data-testid=\"cart-drawer-close\" aria-label=\"Close cart\">×</button>\n </div>\n <div class=\"nymbal-drawer-items\">${itemsHtml}</div>\n ${state.items.length > 0 ? `\n <div class=\"nymbal-drawer-footer\">\n <div class=\"nymbal-cart-subtotal\">\n <span>Subtotal</span>\n <span>${this.formatPrice(state.subtotalMinor, currency)}</span>\n </div>\n <button type=\"button\" class=\"nymbal-checkout-btn\" data-testid=\"cart-checkout-btn\">Checkout</button>\n </div>\n ` : ''}\n </div>\n `\n\n this.querySelector('.nymbal-drawer-overlay')?.addEventListener('click', () => this.close())\n this.querySelector('.nymbal-drawer-close')?.addEventListener('click', () => this.close())\n this.querySelector('.nymbal-checkout-btn')?.addEventListener('click', () => {\n this.dispatchEvent(new CustomEvent('nymbal:cart:checkout-clicked', { bubbles: true, composed: true }))\n })\n this.querySelectorAll('.nymbal-cart-item-remove').forEach((btn) => {\n btn.addEventListener('click', () => {\n const variantId = (btn as HTMLElement).dataset['variantId']\n if (variantId) this.client.cart.removeItem(variantId)\n })\n })\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private esc(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n\n private formatPrice(minor: number, currency: string): string {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n }).format(minor / 100)\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-mini-cart {\n display: inline-block;\n position: relative;\n}\nnymbal-mini-cart button {\n background: none;\n border: none;\n cursor: pointer;\n position: relative;\n padding: var(--nymbal-spacing-sm);\n font-family: var(--nymbal-font-family);\n color: var(--nymbal-color-text);\n}\nnymbal-mini-cart .nymbal-mini-cart-icon {\n width: 24px;\n height: 24px;\n}\nnymbal-mini-cart .nymbal-mini-cart-badge {\n position: absolute;\n top: 0;\n right: 0;\n background: var(--nymbal-color-accent);\n color: var(--nymbal-color-text-inverse);\n font-size: var(--nymbal-font-size-xs);\n font-weight: var(--nymbal-font-weight-bold);\n min-width: 18px;\n height: 18px;\n border-radius: var(--nymbal-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n}\nnymbal-mini-cart .nymbal-mini-cart-badge:empty {\n display: none;\n}\n`\n\nconst CART_SVG = `<svg class=\"nymbal-mini-cart-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"9\" cy=\"21\" r=\"1\"/><circle cx=\"20\" cy=\"21\" r=\"1\"/><path d=\"M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6\"/></svg>`\n\nexport class NymbalMiniCart extends NymbalElement {\n protected setup(): void {\n injectStyles('nymbal-mini-cart', STYLES)\n this.subscribeToStore(this.client.cart)\n }\n\n protected render(): void {\n const { itemCount } = this.client.cart.getState()\n\n this.innerHTML = `\n <button type=\"button\" data-testid=\"mini-cart\" aria-label=\"Cart (${itemCount} items)\">\n ${CART_SVG}\n <span class=\"nymbal-mini-cart-badge\" data-testid=\"mini-cart-badge\">${itemCount > 0 ? itemCount : ''}</span>\n </button>\n `\n\n this.querySelector('button')?.addEventListener('click', () => {\n this.dispatchEvent(new CustomEvent('nymbal:mini-cart:clicked', { bubbles: true, composed: true }))\n })\n }\n}\n","import type { DenormalisedVariant, DenormalisedProduct } from '@nymbalhq/sdk'\nimport type { VariantOption } from '@nymbalhq/types'\nimport { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-variant-selector {\n display: block;\n font-family: var(--nymbal-font-family);\n}\nnymbal-variant-selector .nymbal-option-group {\n margin-bottom: var(--nymbal-spacing-md);\n}\nnymbal-variant-selector .nymbal-option-label {\n display: block;\n font-size: var(--nymbal-font-size-sm);\n font-weight: var(--nymbal-font-weight-medium);\n margin-bottom: var(--nymbal-spacing-sm);\n color: var(--nymbal-color-text);\n}\nnymbal-variant-selector .nymbal-option-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: var(--nymbal-spacing-sm);\n}\nnymbal-variant-selector .nymbal-option-btn {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-sm);\n background: var(--nymbal-color-surface);\n color: var(--nymbal-color-text);\n border: 1px solid var(--nymbal-color-border);\n border-radius: var(--nymbal-radius-md);\n padding: var(--nymbal-spacing-xs) var(--nymbal-spacing-md);\n cursor: pointer;\n transition: border-color var(--nymbal-transition-fast), background var(--nymbal-transition-fast);\n}\nnymbal-variant-selector .nymbal-option-btn:hover {\n border-color: var(--nymbal-color-primary);\n}\nnymbal-variant-selector .nymbal-option-btn[aria-pressed=\"true\"] {\n background: var(--nymbal-color-primary);\n color: var(--nymbal-color-text-inverse);\n border-color: var(--nymbal-color-primary);\n}\nnymbal-variant-selector .nymbal-option-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n`\n\ninterface OptionGroup {\n name: string\n values: string[]\n}\n\n/** A variant's options when they are the `VariantOption[]` the read model serves; any other shape offers none. */\nfunction optionsOf(variant: DenormalisedVariant): VariantOption[] {\n return Array.isArray(variant.options) ? variant.options : []\n}\n\nexport class NymbalVariantSelector extends NymbalElement {\n protected setup(): void {\n injectStyles('nymbal-variant-selector', STYLES)\n this.subscribeToStore(this.client.product)\n }\n\n protected render(): void {\n const state = this.client.product.getState()\n const product = state.product\n if (!product) {\n this.innerHTML = ''\n return\n }\n\n const optionGroups = this.getOptionGroups(product)\n const selected = state.selectedVariant\n\n this.innerHTML = optionGroups.map((group) => `\n <div class=\"nymbal-option-group\">\n <span class=\"nymbal-option-label\">${this.esc(group.name)}</span>\n <div class=\"nymbal-option-buttons\">\n ${group.values.map((value) => {\n const isSelected =\n selected !== null &&\n optionsOf(selected).some((o) => o.name === group.name && o.value === value)\n return `<button\n class=\"nymbal-option-btn\"\n data-testid=\"variant-option-${this.esc(group.name)}-${this.esc(value)}\"\n data-option-name=\"${this.esc(group.name)}\"\n data-option-value=\"${this.esc(value)}\"\n aria-pressed=\"${isSelected}\"\n >${this.esc(value)}</button>`\n }).join('')}\n </div>\n </div>\n `).join('')\n\n this.querySelectorAll('.nymbal-option-btn').forEach((btn) => {\n btn.addEventListener('click', () => {\n const el = btn as HTMLElement\n const optionName = el.dataset['optionName']\n const optionValue = el.dataset['optionValue']\n if (!optionName || !optionValue || !product) return\n\n const currentSelections = new Map<string, string>()\n if (selected) {\n for (const opt of optionsOf(selected)) {\n currentSelections.set(opt.name, opt.value)\n }\n }\n currentSelections.set(optionName, optionValue)\n\n const match = product.variants.find((v: DenormalisedVariant) =>\n optionsOf(v).every((o) => currentSelections.get(o.name) === o.value),\n )\n\n if (match) {\n this.client.product.selectVariant(match)\n this.dispatchEvent(\n new CustomEvent('nymbal:variant:selected', {\n bubbles: true,\n composed: true,\n detail: { variant: match },\n }),\n )\n }\n })\n })\n }\n\n private getOptionGroups(product: DenormalisedProduct): OptionGroup[] {\n const groups = new Map<string, Set<string>>()\n for (const variant of product.variants) {\n for (const opt of optionsOf(variant)) {\n let values = groups.get(opt.name)\n if (!values) {\n values = new Set()\n groups.set(opt.name, values)\n }\n values.add(opt.value)\n }\n }\n return Array.from(groups.entries()).map(([name, values]) => ({\n name,\n values: Array.from(values),\n }))\n }\n\n private esc(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-quantity-selector {\n display: inline-flex;\n align-items: center;\n font-family: var(--nymbal-font-family);\n}\nnymbal-quantity-selector button {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n background: var(--nymbal-color-surface);\n color: var(--nymbal-color-text);\n border: 1px solid var(--nymbal-color-border);\n width: 36px;\n height: 36px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--nymbal-transition-fast);\n}\nnymbal-quantity-selector button:hover:not(:disabled) {\n background: var(--nymbal-color-secondary, #f4f4f5);\n}\nnymbal-quantity-selector button:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\nnymbal-quantity-selector button:first-child {\n border-radius: var(--nymbal-radius-md) 0 0 var(--nymbal-radius-md);\n}\nnymbal-quantity-selector button:last-child {\n border-radius: 0 var(--nymbal-radius-md) var(--nymbal-radius-md) 0;\n}\nnymbal-quantity-selector input {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n width: 48px;\n height: 36px;\n text-align: center;\n border: 1px solid var(--nymbal-color-border);\n border-left: none;\n border-right: none;\n -moz-appearance: textfield;\n}\nnymbal-quantity-selector input::-webkit-outer-spin-button,\nnymbal-quantity-selector input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n`\n\n/**\n * A quantity as a whole number of units in `min`-`max`: a number is truncated toward zero and clamped, anything else is\n * `min`. Add-to-cart takes only integers, so the selector shows and emits the same integer.\n */\nfunction wholeQuantity(raw: string | null, min: number, max: number): number {\n const n = raw === null || raw.trim() === '' ? Number.NaN : Number(raw)\n if (Number.isNaN(n)) return min\n return Math.min(max, Math.max(min, Math.trunc(n)))\n}\n\nexport class NymbalQuantitySelector extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['value', 'min', 'max']\n }\n\n private _value = 1\n\n protected setup(): void {\n injectStyles('nymbal-quantity-selector', STYLES)\n this._value = wholeQuantity(this.getAttribute('value'), ...this.bounds())\n }\n\n private bounds(): [number, number] {\n return [Number(this.getAttribute('min')) || 1, Number(this.getAttribute('max')) || 99]\n }\n\n protected render(): void {\n const [min, max] = this.bounds()\n\n this.innerHTML = `\n <button data-testid=\"qty-decrement\" aria-label=\"Decrease quantity\" ${this._value <= min ? 'disabled' : ''}>−</button>\n <input data-testid=\"qty-input\" type=\"number\" value=\"${this._value}\" min=\"${min}\" max=\"${max}\" aria-label=\"Quantity\">\n <button data-testid=\"qty-increment\" aria-label=\"Increase quantity\" ${this._value >= max ? 'disabled' : ''}>+</button>\n `\n\n this.querySelector('[data-testid=\"qty-decrement\"]')?.addEventListener('click', () => {\n if (this._value > min) {\n this._value--\n this.emitChange()\n this.render()\n }\n })\n\n this.querySelector('[data-testid=\"qty-increment\"]')?.addEventListener('click', () => {\n if (this._value < max) {\n this._value++\n this.emitChange()\n this.render()\n }\n })\n\n const input = this.querySelector('input')\n input?.addEventListener('change', () => {\n this._value = wholeQuantity(input.value, min, max)\n this.emitChange()\n this.render()\n })\n }\n\n attributeChangedCallback(name: string): void {\n if (name === 'value') {\n this._value = wholeQuantity(this.getAttribute('value'), ...this.bounds())\n }\n if (this.isConnected) this.requestRender()\n }\n\n private emitChange(): void {\n this.dispatchEvent(\n new CustomEvent('nymbal:quantity:changed', {\n bubbles: true,\n composed: true,\n detail: { value: this._value },\n }),\n )\n }\n}\n","import { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\nimport type { DenormalisedProduct } from '@nymbalhq/sdk'\n\nconst STYLES = `\nnymbal-search-bar {\n display: block;\n position: relative;\n font-family: var(--nymbal-font-family);\n}\nnymbal-search-bar input {\n font-family: var(--nymbal-font-family);\n font-size: var(--nymbal-font-size-base);\n width: 100%;\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-md);\n border: 1px solid var(--nymbal-color-border);\n border-radius: var(--nymbal-radius-md);\n outline: none;\n transition: border-color var(--nymbal-transition-fast);\n box-sizing: border-box;\n}\nnymbal-search-bar input:focus {\n border-color: var(--nymbal-color-accent);\n}\nnymbal-search-bar .nymbal-search-results {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background: var(--nymbal-color-surface);\n border: 1px solid var(--nymbal-color-border);\n border-top: none;\n border-radius: 0 0 var(--nymbal-radius-md) var(--nymbal-radius-md);\n box-shadow: var(--nymbal-shadow-md);\n z-index: var(--nymbal-z-dropdown);\n max-height: 300px;\n overflow-y: auto;\n}\nnymbal-search-bar .nymbal-search-result-item {\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-md);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast);\n}\nnymbal-search-bar .nymbal-search-result-item:hover,\nnymbal-search-bar .nymbal-search-result-item[aria-selected=\"true\"] {\n background: var(--nymbal-color-secondary, #f4f4f5);\n}\nnymbal-search-bar .nymbal-search-result-name {\n font-weight: var(--nymbal-font-weight-medium);\n}\nnymbal-search-bar .nymbal-search-result-price {\n font-size: var(--nymbal-font-size-sm);\n color: var(--nymbal-color-text-muted);\n}\nnymbal-search-bar .nymbal-search-loading {\n padding: var(--nymbal-spacing-md);\n text-align: center;\n color: var(--nymbal-color-text-muted);\n font-size: var(--nymbal-font-size-sm);\n}\n`\n\nexport class NymbalSearchBar extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['placeholder', 'debounce-ms']\n }\n\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n private selectedIndex = -1\n\n protected setup(): void {\n injectStyles('nymbal-search-bar', STYLES)\n this.subscribeToStore(this.client.search)\n }\n\n protected render(): void {\n const state = this.client.search.getState()\n const placeholder = this.getAttribute('placeholder') ?? 'Search products...'\n\n let resultsHtml = ''\n if (state.query) {\n if (state.loading) {\n resultsHtml = `<div class=\"nymbal-search-results\" data-testid=\"search-results\"><div class=\"nymbal-search-loading\">Searching...</div></div>`\n } else if (state.results.length > 0) {\n resultsHtml = `<div class=\"nymbal-search-results\" data-testid=\"search-results\">\n ${state.results\n .map(\n (product, i) => `\n <div class=\"nymbal-search-result-item\" data-testid=\"search-result-${this.esc(product.slug)}\" data-slug=\"${this.esc(product.slug)}\" aria-selected=\"${i === this.selectedIndex}\">\n <div class=\"nymbal-search-result-name\">${this.esc(product.name)}</div>\n ${product.priceMinor !== null ? `<div class=\"nymbal-search-result-price\">${this.formatPrice(product.priceMinor, product.currency ?? 'USD')}</div>` : ''}\n </div>\n `,\n )\n .join('')}\n </div>`\n }\n }\n\n this.innerHTML = `\n <input data-testid=\"search-input\" type=\"search\" placeholder=\"${this.esc(placeholder)}\" value=\"${this.esc(state.query)}\" autocomplete=\"off\" role=\"combobox\" aria-expanded=\"${state.results.length > 0}\" aria-haspopup=\"listbox\">\n ${resultsHtml}\n `\n\n this.querySelectorAll<HTMLElement>('.nymbal-search-result-item').forEach((item, i) => {\n item.addEventListener('click', () => {\n const product = state.results[i]\n if (product) this.select(product)\n })\n })\n\n const input = this.querySelector('input')!\n input.addEventListener('input', () => {\n const query = input.value\n const debounceMs = Number(this.getAttribute('debounce-ms')) || 300\n if (this.debounceTimer !== null) clearTimeout(this.debounceTimer)\n this.debounceTimer = setTimeout(() => {\n this.selectedIndex = -1\n if (query.trim()) {\n this.client.search.search(query)\n } else {\n this.client.search.clearSearch()\n }\n }, debounceMs)\n })\n\n input.addEventListener('keydown', (e) => {\n const results = state.results\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n this.selectedIndex = Math.min(this.selectedIndex + 1, results.length - 1)\n this.render()\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n this.selectedIndex = Math.max(this.selectedIndex - 1, -1)\n this.render()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n const selected = this.selectedIndex >= 0 ? results[this.selectedIndex] : undefined\n if (selected) {\n this.select(selected)\n } else {\n const query = input.value.trim()\n if (query) {\n window.location.href = `/search?q=${encodeURIComponent(query)}`\n }\n }\n } else if (e.key === 'Escape') {\n this.client.search.clearSearch()\n this.selectedIndex = -1\n }\n })\n }\n\n private select(product: DenormalisedProduct): void {\n this.dispatchEvent(\n new CustomEvent('nymbal:search:selected', {\n bubbles: true,\n composed: true,\n detail: { product, query: product.name },\n }),\n )\n this.client.search.clearSearch()\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n }\n\n /** Escapes a value for both text and quoted-attribute contexts. */\n private esc(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n }\n\n private formatPrice(minor: number, currency: string): string {\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(minor / 100)\n }\n}\n","import type { Facet } from '@nymbalhq/types'\nimport { NymbalElement } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-product-filter {\n display: block;\n font-family: var(--nymbal-font-family);\n}\nnymbal-product-filter .nymbal-filter-group {\n margin-bottom: var(--nymbal-spacing-lg);\n}\nnymbal-product-filter .nymbal-filter-label {\n display: block;\n font-size: var(--nymbal-font-size-sm);\n font-weight: var(--nymbal-font-weight-bold);\n margin-bottom: var(--nymbal-spacing-sm);\n color: var(--nymbal-color-text);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\nnymbal-product-filter .nymbal-filter-option {\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm);\n padding: var(--nymbal-spacing-xs) 0;\n cursor: pointer;\n font-size: var(--nymbal-font-size-sm);\n color: var(--nymbal-color-text);\n}\nnymbal-product-filter .nymbal-filter-option input[type=\"checkbox\"] {\n accent-color: var(--nymbal-color-primary);\n}\nnymbal-product-filter .nymbal-filter-count {\n color: var(--nymbal-color-text-muted);\n font-size: var(--nymbal-font-size-xs);\n}\n`\n\n\n/**\n * The facet filter. By default it reads the `facets` attribute and filters the product list. With `source=\"search\"`\n * it renders the search's own facets and narrows the search to one category: choosing an option selects it, choosing\n * the selected option again clears it.\n */\nexport class NymbalProductFilter extends NymbalElement {\n static get observedAttributes(): string[] {\n return ['facets', 'source']\n }\n\n protected setup(): void {\n injectStyles('nymbal-product-filter', STYLES)\n // Both: a wrapper may set `source` after the element has connected.\n this.subscribeToStore(this.client.productList)\n this.subscribeToStore(this.client.search)\n }\n\n protected render(): void {\n if (this.isSearch()) {\n this.renderSearch()\n return\n }\n const state = this.client.productList.getState()\n const facets = this.getFacets()\n\n if (facets.length === 0) {\n this.innerHTML = ''\n return\n }\n\n this.innerHTML = facets.map((facet) => {\n const activeFilter = state.filters[facet.field]\n const activeValues = Array.isArray(activeFilter)\n ? activeFilter\n : (activeFilter ? [activeFilter] : [])\n\n return `\n <div class=\"nymbal-filter-group\" data-testid=\"filter-group-${facet.field}\">\n <span class=\"nymbal-filter-label\">${this.esc(facet.label)}</span>\n ${facet.values.map((v) => {\n const checked = activeValues.includes(v.value)\n return `\n <label class=\"nymbal-filter-option\" data-testid=\"filter-option-${v.value}\">\n <input type=\"checkbox\" data-filter-name=\"${facet.field}\" data-filter-value=\"${v.value}\" ${checked ? 'checked' : ''}>\n <span>${this.esc(v.label ?? v.value)}</span>\n ${v.count !== undefined ? `<span class=\"nymbal-filter-count\">(${v.count})</span>` : ''}\n </label>\n `\n }).join('')}\n </div>\n `\n }).join('')\n\n this.querySelectorAll('input[type=\"checkbox\"]').forEach((input) => {\n input.addEventListener('change', () => {\n const el = input as HTMLInputElement\n const name = el.dataset['filterName']!\n const value = el.dataset['filterValue']!\n if (el.checked) {\n const current = state.filters[name]\n const values = Array.isArray(current) ? [...current, value] : (current ? [current, value] : value)\n this.client.productList.applyFilter(name, values)\n } else {\n const current = state.filters[name]\n if (Array.isArray(current)) {\n const remaining = current.filter((v) => v !== value)\n if (remaining.length > 0) {\n this.client.productList.applyFilter(name, remaining)\n } else {\n this.client.productList.removeFilter(name)\n }\n } else {\n this.client.productList.removeFilter(name)\n }\n }\n this.dispatchEvent(\n new CustomEvent('nymbal:filter:changed', { bubbles: true, composed: true, detail: { name, value } }),\n )\n })\n })\n }\n\n private renderSearch(): void {\n const { facets, category } = this.client.search.getState()\n const shown = facets.filter((f) => f.field === 'category')\n this.innerHTML = shown\n .map(\n (facet) => `\n <div class=\"nymbal-filter-group\" data-testid=\"filter-group-${this.esc(facet.field)}\">\n <span class=\"nymbal-filter-label\">${this.esc(facet.label)}</span>\n ${facet.values\n .map(\n (v) => `\n <label class=\"nymbal-filter-option\" data-testid=\"filter-option-${this.esc(v.value)}\">\n <input type=\"checkbox\" data-filter-value=\"${this.esc(v.value)}\" ${v.value === category ? 'checked' : ''}>\n <span>${this.esc(v.label ?? v.value)}</span>\n ${v.count !== undefined ? `<span class=\"nymbal-filter-count\">(${v.count})</span>` : ''}\n </label>\n `,\n )\n .join('')}\n </div>\n `,\n )\n .join('')\n\n this.querySelectorAll<HTMLInputElement>('input[type=\"checkbox\"]').forEach((input) => {\n input.addEventListener('change', () => {\n const value = input.dataset['filterValue']!\n void this.client.search.setCategory(input.checked ? value : null)\n this.dispatchEvent(\n new CustomEvent('nymbal:filter:changed', {\n bubbles: true,\n composed: true,\n detail: { name: 'category', value: input.checked ? value : '' },\n }),\n )\n })\n })\n }\n\n private isSearch(): boolean {\n return this.getAttribute('source') === 'search'\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.requestRender()\n }\n\n private getFacets(): Facet[] {\n const attr = this.getAttribute('facets')\n if (!attr) return []\n try {\n return JSON.parse(attr) as Facet[]\n } catch (err) {\n console.error('[nymbal-product-filter] failed to parse facets attribute', err)\n return []\n }\n }\n\n private esc(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n}\n","import { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-toast {\n position: fixed;\n z-index: var(--nymbal-z-toast);\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-sm);\n pointer-events: none;\n font-family: var(--nymbal-font-family);\n}\nnymbal-toast[position=\"top-right\"],\nnymbal-toast:not([position]) {\n top: var(--nymbal-spacing-lg);\n right: var(--nymbal-spacing-lg);\n}\nnymbal-toast[position=\"top-left\"] {\n top: var(--nymbal-spacing-lg);\n left: var(--nymbal-spacing-lg);\n}\nnymbal-toast[position=\"bottom-right\"] {\n bottom: var(--nymbal-spacing-lg);\n right: var(--nymbal-spacing-lg);\n}\nnymbal-toast[position=\"bottom-left\"] {\n bottom: var(--nymbal-spacing-lg);\n left: var(--nymbal-spacing-lg);\n}\nnymbal-toast .nymbal-toast-item {\n pointer-events: auto;\n display: flex;\n align-items: center;\n gap: var(--nymbal-spacing-sm);\n padding: var(--nymbal-spacing-sm) var(--nymbal-spacing-md);\n border-radius: var(--nymbal-radius-md);\n box-shadow: var(--nymbal-shadow-lg);\n font-size: var(--nymbal-font-size-sm);\n background: var(--nymbal-color-surface);\n color: var(--nymbal-color-text);\n border: 1px solid var(--nymbal-color-border);\n animation: nymbal-toast-in var(--nymbal-transition-normal) forwards;\n max-width: 360px;\n}\nnymbal-toast .nymbal-toast-item[data-type=\"success\"] {\n border-left: 3px solid var(--nymbal-color-success);\n}\nnymbal-toast .nymbal-toast-item[data-type=\"error\"] {\n border-left: 3px solid var(--nymbal-color-error);\n}\nnymbal-toast .nymbal-toast-item[data-type=\"warning\"] {\n border-left: 3px solid var(--nymbal-color-warning);\n}\nnymbal-toast .nymbal-toast-item[data-type=\"info\"] {\n border-left: 3px solid var(--nymbal-color-accent);\n}\nnymbal-toast .nymbal-toast-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--nymbal-color-text-muted);\n font-size: var(--nymbal-font-size-base);\n padding: 0;\n margin-left: auto;\n}\n@keyframes nymbal-toast-in {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n`\n\nconst HTMLElementBase =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement)\n\nexport class NymbalToast extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['position', 'duration-ms']\n }\n\n connectedCallback(): void {\n injectStyles('nymbal-toast', STYLES)\n this.setAttribute('data-testid', 'toast')\n }\n\n show(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n const durationMs = Number(this.getAttribute('duration-ms')) || 4000\n\n const item = document.createElement('div')\n item.className = 'nymbal-toast-item'\n item.setAttribute('data-type', type)\n item.setAttribute('role', 'alert')\n\n const text = document.createElement('span')\n text.textContent = message\n\n const close = document.createElement('button')\n close.className = 'nymbal-toast-close'\n close.setAttribute('aria-label', 'Dismiss')\n close.textContent = '×'\n close.addEventListener('click', () => this.dismiss(item))\n\n item.appendChild(text)\n item.appendChild(close)\n this.appendChild(item)\n\n setTimeout(() => this.dismiss(item), durationMs)\n }\n\n private dismiss(item: HTMLElement): void {\n if (item.parentNode === this) {\n this.removeChild(item)\n }\n }\n\n static showGlobal(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n let toastEl = document.querySelector('nymbal-toast') as NymbalToast | null\n if (!toastEl) {\n toastEl = document.createElement('nymbal-toast') as NymbalToast\n document.body.appendChild(toastEl)\n }\n toastEl.show(message, type)\n }\n}\n","import {\n getPaymentDriver,\n type PaymentConfirmContext,\n type PaymentConfirmation,\n type PaymentDriver,\n} from '@nymbalhq/sdk'\nimport type { PaymentClient } from '@nymbalhq/types'\nimport { HTMLElementBase } from '../base-element.js'\nimport { injectStyles } from '../styles.js'\n\nconst STYLES = `\nnymbal-payment-element {\n display: block;\n font-family: var(--nymbal-font-family);\n}\nnymbal-payment-element .nymbal-payment-note {\n margin: 0;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n}\nnymbal-payment-element .nymbal-payment-card {\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.5rem);\n background: var(--nymbal-color-surface, #fff);\n min-height: 40px;\n}\nnymbal-payment-element .nymbal-payment-error {\n margin: 0;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-error, #dc2626);\n}\n`\n\n/** `pending` while a driver mounts; `error` when no payment can be taken, which a checkout must not submit past. */\nexport type PaymentElementStatus = 'pending' | 'ready' | 'error'\n\n/** No client half: nothing to collect, so nothing is confirmed here. */\nconst NO_PAYMENT_STEP: PaymentConfirmation = { ok: true, confirmed: false }\n\nfunction parseClient(value: string | null): PaymentClient | null | undefined {\n if (value === null || value === '' || value === 'null') return null\n try {\n const parsed = JSON.parse(value) as Partial<PaymentClient> | null\n if (parsed === null) return null\n if (typeof parsed.driver !== 'string' || typeof parsed.options !== 'object') return undefined\n return { driver: parsed.driver, options: { ...(parsed.options ?? {}) } }\n } catch {\n return undefined\n }\n}\n\n/**\n * <nymbal-payment-element client='<json>'>: renders the payments adapter's client half (GET /api/capabilities'\n * `payments.client`) through the driver the SDK registry holds under its `driver` name, and confirms a payment\n * through it. It emits `nymbal:payment:status` whenever `status` changes.\n */\nexport class NymbalPaymentElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['client']\n }\n\n private _status: PaymentElementStatus = 'pending'\n private _error = ''\n private _driver: PaymentDriver | null = null\n private _mounted: Promise<boolean> = Promise.resolve(true)\n private _rendered: string | null | undefined = undefined\n private _generation = 0\n\n get status(): PaymentElementStatus {\n return this._status\n }\n\n connectedCallback(): void {\n injectStyles('nymbal-payment-element', STYLES)\n this.render()\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.render()\n }\n\n /**\n * Completes a payment the API left `requires_action`, once the driver has mounted, handing the driver the\n * checkout's `context`; the driver's answer as is.\n */\n async confirm(clientSecret: string, context?: PaymentConfirmContext): Promise<PaymentConfirmation> {\n const driver = this._driver\n if (this._status === 'error') return { ok: false, error: this._error }\n if (driver === null) return NO_PAYMENT_STEP\n if (!(await this._mounted)) return { ok: false, error: this._error }\n return driver.confirm(clientSecret, context)\n }\n\n private render(): void {\n const attribute = this.getAttribute('client')\n // The same client half again (a reconnect, a framework re-setting the attribute) keeps what is mounted.\n if (attribute === this._rendered) return\n this._rendered = attribute\n const generation = ++this._generation\n this._driver = null\n this._mounted = Promise.resolve(true)\n this.replaceChildren()\n\n const client = parseClient(attribute)\n if (client === undefined)\n return this.fail('The store sent a payment client this page cannot read.')\n if (client === null) return this.setStatus('ready')\n const driver = getPaymentDriver(client.driver)\n if (driver === undefined) {\n return this.fail(`Payment method \"${client.driver}\" is not available on this storefront.`)\n }\n\n this._driver = driver\n this.setStatus('pending')\n this._mounted = Promise.resolve()\n .then(() => driver.mount(this, client.options))\n .then(\n () => {\n if (generation === this._generation) this.setStatus('ready')\n return true\n },\n (err: unknown) => {\n if (generation === this._generation) {\n this.fail(err instanceof Error ? err.message : 'The payment form could not be loaded.')\n }\n return false\n },\n )\n }\n\n private fail(message: string): void {\n this._error = message\n const p = document.createElement('p')\n p.className = 'nymbal-payment-error'\n p.setAttribute('data-testid', 'payment-element-error')\n p.setAttribute('role', 'alert')\n p.textContent = message\n this.replaceChildren(p)\n this.setStatus('error')\n }\n\n private setStatus(status: PaymentElementStatus): void {\n if (status === this._status) return\n this._status = status\n this.dispatchEvent(\n new CustomEvent('nymbal:payment:status', {\n bubbles: true,\n composed: true,\n detail: { status },\n }),\n )\n }\n}\n","import { DEFAULT_PRODUCT_SORT, PRODUCT_SORT_LABELS, type ProductSortKey } from '@nymbalhq/types'\nimport { HTMLElementBase } from '../base-element.js'\n\nfunction parseKeys(value: string | null): string[] {\n if (!value) return []\n try {\n const parsed: unknown = JSON.parse(value)\n return Array.isArray(parsed) ? parsed.filter((k): k is string => typeof k === 'string') : []\n } catch (err) {\n console.error('[nymbal-sort-dropdown] failed to parse sort-keys attribute', err)\n return []\n }\n}\n\n/**\n * <nymbal-sort-dropdown sort-keys='[…]' value default-sort>: one option per sort key the store reports, in its order,\n * labelled from PRODUCT_SORT_LABELS. A `value` the keys lack shows `default-sort`. Choosing an option emits\n * `nymbal:sort:changed` with `{ sort }`. A `<select>` already inside it (a server render's) is the one it fills.\n */\nexport class NymbalSortDropdown extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['sort-keys', 'value', 'default-sort']\n }\n\n private _select: HTMLSelectElement | null = null\n\n /** The key the dropdown shows. */\n get value(): string {\n return this._select?.value ?? ''\n }\n\n connectedCallback(): void {\n this.render()\n }\n\n attributeChangedCallback(): void {\n if (this.isConnected) this.render()\n }\n\n private selectElement(): HTMLSelectElement {\n if (this._select !== null && this.contains(this._select)) return this._select\n let select = this.querySelector('select')\n if (select === null) {\n select = document.createElement('select')\n select.setAttribute('aria-label', 'Sort products')\n this.appendChild(select)\n }\n if (!select.hasAttribute('data-testid')) select.setAttribute('data-testid', 'sort-dropdown')\n select.addEventListener('change', () => {\n this.dispatchEvent(\n new CustomEvent('nymbal:sort:changed', {\n bubbles: true,\n composed: true,\n detail: { sort: select.value },\n }),\n )\n })\n this._select = select\n return select\n }\n\n private render(): void {\n const keys = parseKeys(this.getAttribute('sort-keys'))\n const wanted = this.getAttribute('value') ?? ''\n const fallback = this.getAttribute('default-sort') ?? DEFAULT_PRODUCT_SORT\n const selected = keys.includes(wanted) ? wanted : keys.includes(fallback) ? fallback : keys[0]\n\n const select = this.selectElement()\n select.replaceChildren(\n ...keys.map((key) => {\n const option = document.createElement('option')\n option.value = key\n option.textContent = PRODUCT_SORT_LABELS[key as ProductSortKey] ?? key\n return option\n }),\n )\n if (selected !== undefined) select.value = selected\n }\n}\n","export { registerClient, getClient } from './registry.js'\nexport { NymbalElement } from './base-element.js'\nexport { injectStyles } from './styles.js'\n\nimport { NymbalAddToCart } from './components/add-to-cart.js'\nimport { NymbalCartDrawer } from './components/cart-drawer.js'\nimport { NymbalMiniCart } from './components/mini-cart.js'\nimport { NymbalVariantSelector } from './components/variant-selector.js'\nimport { NymbalQuantitySelector } from './components/quantity-selector.js'\nimport { NymbalSearchBar } from './components/search-bar.js'\nimport { NymbalProductFilter } from './components/product-filter.js'\nimport { NymbalToast } from './components/toast.js'\nimport { NymbalPaymentElement } from './components/payment-element.js'\nimport { NymbalSortDropdown } from './components/sort-dropdown.js'\nimport { getPaymentDriver, registerPaymentDriver, type PaymentDriver } from '@nymbalhq/sdk'\nimport { testModeDriver } from './payment-drivers/test-mode.js'\nimport { stripeDriver } from './payment-drivers/stripe.js'\n\nexport type { PaymentElementStatus } from './components/payment-element.js'\n\nexport {\n NymbalAddToCart,\n NymbalCartDrawer,\n NymbalMiniCart,\n NymbalVariantSelector,\n NymbalQuantitySelector,\n NymbalSearchBar,\n NymbalProductFilter,\n NymbalToast,\n NymbalPaymentElement,\n NymbalSortDropdown,\n}\n\nexport function registerComponents(): void {\n const components: Array<[string, CustomElementConstructor]> = [\n ['nymbal-add-to-cart', NymbalAddToCart],\n ['nymbal-cart-drawer', NymbalCartDrawer],\n ['nymbal-mini-cart', NymbalMiniCart],\n ['nymbal-variant-selector', NymbalVariantSelector],\n ['nymbal-quantity-selector', NymbalQuantitySelector],\n ['nymbal-search-bar', NymbalSearchBar],\n ['nymbal-product-filter', NymbalProductFilter],\n ['nymbal-toast', NymbalToast],\n ['nymbal-payment-element', NymbalPaymentElement],\n ['nymbal-sort-dropdown', NymbalSortDropdown],\n ]\n // A driver the project registered under a built-in's name wins, as a project-defined tag does.\n const drivers: Array<[string, PaymentDriver]> = [\n ['test-mode', testModeDriver],\n ['stripe', stripeDriver],\n ]\n\n for (const [name, driver] of drivers) {\n if (getPaymentDriver(name) === undefined) registerPaymentDriver(name, driver)\n }\n for (const [name, ctor] of components) {\n if (!customElements.get(name)) {\n customElements.define(name, ctor)\n }\n }\n}\n","import type { PaymentDriver } from '@nymbalhq/sdk'\n\n/**\n * For a store whose payments need no shopper input (the native stub): nothing to collect, and nothing confirmed here.\n * An order the stub holds as `requires_action` stays pending until its webhook pays it.\n */\nexport const testModeDriver: PaymentDriver = {\n mount(el) {\n el.innerHTML = '<p class=\"nymbal-payment-note\">Test mode — payment skipped</p>'\n },\n confirm() {\n return Promise.resolve({ ok: true, confirmed: false })\n },\n}\n","import type { PaymentConfirmContext, PaymentConfirmation, PaymentDriver } from '@nymbalhq/sdk'\n\n// Stripe.js from Stripe's own host, as Stripe requires, so no project installs a Stripe package to take a card.\nconst STRIPE_JS = 'https://js.stripe.com/v3/'\n\ninterface StripeCard {\n mount(target: HTMLElement): void\n}\n\ninterface StripeBillingDetails {\n email?: string\n name?: string\n address?: {\n line1: string\n line2?: string\n city: string\n state: string\n postal_code: string\n country: string\n }\n}\n\ninterface StripeInstance {\n elements(): { create(type: 'card', options?: Record<string, unknown>): StripeCard }\n confirmCardPayment(\n clientSecret: string,\n data: { payment_method: { card: StripeCard; billing_details?: StripeBillingDetails } },\n ): Promise<{ paymentIntent?: { status: string }; error?: { message?: string } }>\n}\n\n/** The checkout's shopper details as Stripe's `billing_details`, which its fraud checks read; none when empty. */\nfunction billingDetails(context: PaymentConfirmContext | undefined): StripeBillingDetails | undefined {\n const details: StripeBillingDetails = {}\n if (context?.email) details.email = context.email\n const billing = context?.billing\n if (billing) {\n const name = `${billing.firstName} ${billing.lastName}`.trim()\n if (name) details.name = name\n details.address = {\n line1: billing.addressLine1,\n ...(billing.addressLine2 ? { line2: billing.addressLine2 } : {}),\n city: billing.city,\n state: billing.region,\n postal_code: billing.postalCode,\n country: billing.country,\n }\n }\n return Object.keys(details).length > 0 ? details : undefined\n}\n\ntype StripeFactory = (publishableKey: string) => StripeInstance\n\nlet loading: Promise<StripeFactory> | null = null\n\n/** `window.Stripe`, injecting Stripe.js once per page when it is not there yet. */\nfunction loadStripeJs(): Promise<StripeFactory> {\n const win = window as unknown as { Stripe?: StripeFactory }\n if (win.Stripe) return Promise.resolve(win.Stripe)\n loading ??= new Promise<StripeFactory>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = STRIPE_JS\n script.async = true\n script.addEventListener('load', () => {\n if (win.Stripe) resolve(win.Stripe)\n else reject(new Error('Stripe.js loaded but window.Stripe is missing.'))\n })\n script.addEventListener('error', () => {\n loading = null\n script.remove()\n reject(new Error('Stripe.js could not be loaded.'))\n })\n document.head.appendChild(script)\n })\n return loading\n}\n\nconst instances = new Map<string, StripeInstance>()\nlet mounted: Array<{ host: HTMLElement; stripe: StripeInstance; card: StripeCard }> = []\n\n/** A card form from Stripe.js; confirm pays the intent with the card the shopper typed into. */\nexport const stripeDriver: PaymentDriver = {\n async mount(el, options) {\n const key = options['publishableKey']\n if (!key)\n throw new Error('Card payments are not configured: the store has no Stripe publishable key.')\n const factory = await loadStripeJs()\n let stripe = instances.get(key)\n if (stripe === undefined) {\n stripe = factory(key)\n instances.set(key, stripe)\n }\n const target = document.createElement('div')\n target.className = 'nymbal-payment-card'\n el.replaceChildren(target)\n const card = stripe.elements().create('card', {\n style: {\n base: { fontSize: '16px', color: '#1a1a1a', '::placeholder': { color: '#9ca3af' } },\n },\n })\n card.mount(target)\n mounted = [\n ...mounted.filter((m) => m.host.isConnected && m.host !== el),\n { host: el, stripe, card },\n ]\n },\n\n async confirm(clientSecret, context): Promise<PaymentConfirmation> {\n const current = mounted.findLast((m) => m.host.isConnected) ?? mounted.at(-1)\n if (current === undefined) return { ok: false, error: 'The card form is not ready.' }\n const billing_details = billingDetails(context)\n const result = await current.stripe.confirmCardPayment(clientSecret, {\n payment_method: { card: current.card, ...(billing_details ? { billing_details } : {}) },\n })\n if (result.error) return { ok: false, error: result.error.message ?? 'Payment failed.' }\n // `processing` is accepted but not paid yet: the webhook confirms it.\n return { ok: true, confirmed: result.paymentIntent?.status === 'succeeded' }\n },\n}\n"],"mappings":"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","names":["STYLES","STYLES","STYLES","STYLES","STYLES","STYLES","STYLES","HTMLElementBase","STYLES","getPaymentDriver","getPaymentDriver"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nymbalhq/web-components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Interactive commerce UI as native Web Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"src/tokens/tokens.css"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@nymbalhq/sdk": "
|
|
33
|
-
"@nymbalhq/types": "
|
|
32
|
+
"@nymbalhq/sdk": "1.1.0",
|
|
33
|
+
"@nymbalhq/types": "1.1.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"tsup": "^8.3.5",
|