@nymbalhq/web-components 1.0.0 → 1.2.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.
Files changed (187) hide show
  1. package/astro/NymbalElement.astro +19 -0
  2. package/dist/base-element-CPMgAdyQ.d.ts +91 -0
  3. package/dist/chunk-2TPQNQYO.js +155 -0
  4. package/dist/chunk-2TPQNQYO.js.map +1 -0
  5. package/dist/chunk-2XXBQPDB.js +113 -0
  6. package/dist/chunk-2XXBQPDB.js.map +1 -0
  7. package/dist/chunk-42SSME5H.js +84 -0
  8. package/dist/chunk-42SSME5H.js.map +1 -0
  9. package/dist/chunk-4COVK727.js +204 -0
  10. package/dist/chunk-4COVK727.js.map +1 -0
  11. package/dist/chunk-5VWZ2U5K.js +149 -0
  12. package/dist/chunk-5VWZ2U5K.js.map +1 -0
  13. package/dist/chunk-6XCCAAMF.js +656 -0
  14. package/dist/chunk-6XCCAAMF.js.map +1 -0
  15. package/dist/chunk-7JAAOVCE.js +114 -0
  16. package/dist/chunk-7JAAOVCE.js.map +1 -0
  17. package/dist/chunk-B73QVSA5.js +206 -0
  18. package/dist/chunk-B73QVSA5.js.map +1 -0
  19. package/dist/chunk-DR5QB5VN.js +17 -0
  20. package/dist/chunk-DR5QB5VN.js.map +1 -0
  21. package/dist/chunk-DXWMNB5F.js +70 -0
  22. package/dist/chunk-DXWMNB5F.js.map +1 -0
  23. package/dist/chunk-EBMA7JGV.js +130 -0
  24. package/dist/chunk-EBMA7JGV.js.map +1 -0
  25. package/dist/chunk-EYDMJNNK.js +176 -0
  26. package/dist/chunk-EYDMJNNK.js.map +1 -0
  27. package/dist/chunk-FIF6GQ6U.js +141 -0
  28. package/dist/chunk-FIF6GQ6U.js.map +1 -0
  29. package/dist/chunk-GHXTJ2CF.js +109 -0
  30. package/dist/chunk-GHXTJ2CF.js.map +1 -0
  31. package/dist/chunk-H3ZBYNHV.js +153 -0
  32. package/dist/chunk-H3ZBYNHV.js.map +1 -0
  33. package/dist/chunk-HPBC3OG7.js +174 -0
  34. package/dist/chunk-HPBC3OG7.js.map +1 -0
  35. package/dist/chunk-IYT7VXFS.js +105 -0
  36. package/dist/chunk-IYT7VXFS.js.map +1 -0
  37. package/dist/chunk-LHYXNQWY.js +105 -0
  38. package/dist/chunk-LHYXNQWY.js.map +1 -0
  39. package/dist/chunk-LO2EW5YP.js +25 -0
  40. package/dist/chunk-LO2EW5YP.js.map +1 -0
  41. package/dist/chunk-LSQPUZK6.js +291 -0
  42. package/dist/chunk-LSQPUZK6.js.map +1 -0
  43. package/dist/chunk-N4Y5DI4X.js +82 -0
  44. package/dist/chunk-N4Y5DI4X.js.map +1 -0
  45. package/dist/chunk-NE6UHTOT.js +148 -0
  46. package/dist/chunk-NE6UHTOT.js.map +1 -0
  47. package/dist/chunk-NW47OJVN.js +138 -0
  48. package/dist/chunk-NW47OJVN.js.map +1 -0
  49. package/dist/chunk-O2YKCDR2.js +77 -0
  50. package/dist/chunk-O2YKCDR2.js.map +1 -0
  51. package/dist/chunk-PN6QKYE4.js +71 -0
  52. package/dist/chunk-PN6QKYE4.js.map +1 -0
  53. package/dist/chunk-RAOGZH3Z.js +188 -0
  54. package/dist/chunk-RAOGZH3Z.js.map +1 -0
  55. package/dist/chunk-RVSKX2RX.js +11 -0
  56. package/dist/chunk-RVSKX2RX.js.map +1 -0
  57. package/dist/chunk-RZVFA3N5.js +154 -0
  58. package/dist/chunk-RZVFA3N5.js.map +1 -0
  59. package/dist/chunk-SM7TASEW.js +135 -0
  60. package/dist/chunk-SM7TASEW.js.map +1 -0
  61. package/dist/chunk-U2PU4CWB.js +124 -0
  62. package/dist/chunk-U2PU4CWB.js.map +1 -0
  63. package/dist/chunk-UCEVCLQ7.js +5 -0
  64. package/dist/chunk-UCEVCLQ7.js.map +1 -0
  65. package/dist/chunk-UYSZOEOA.js +32 -0
  66. package/dist/chunk-UYSZOEOA.js.map +1 -0
  67. package/dist/chunk-V55K2CS5.js +126 -0
  68. package/dist/chunk-V55K2CS5.js.map +1 -0
  69. package/dist/chunk-VTQVGNVC.js +346 -0
  70. package/dist/chunk-VTQVGNVC.js.map +1 -0
  71. package/dist/chunk-W23P7SI4.js +239 -0
  72. package/dist/chunk-W23P7SI4.js.map +1 -0
  73. package/dist/chunk-W7D3STJH.js +279 -0
  74. package/dist/chunk-W7D3STJH.js.map +1 -0
  75. package/dist/chunk-WEEHXWM3.js +172 -0
  76. package/dist/chunk-WEEHXWM3.js.map +1 -0
  77. package/dist/chunk-WTEVTCIL.js +128 -0
  78. package/dist/chunk-WTEVTCIL.js.map +1 -0
  79. package/dist/chunk-XE6IWISA.js +137 -0
  80. package/dist/chunk-XE6IWISA.js.map +1 -0
  81. package/dist/elements/nymbal-add-to-cart.d.ts +14 -0
  82. package/dist/elements/nymbal-add-to-cart.js +10 -0
  83. package/dist/elements/nymbal-add-to-cart.js.map +1 -0
  84. package/dist/elements/nymbal-address-book.d.ts +30 -0
  85. package/dist/elements/nymbal-address-book.js +13 -0
  86. package/dist/elements/nymbal-address-book.js.map +1 -0
  87. package/dist/elements/nymbal-address-form.d.ts +29 -0
  88. package/dist/elements/nymbal-address-form.js +12 -0
  89. package/dist/elements/nymbal-address-form.js.map +1 -0
  90. package/dist/elements/nymbal-breadcrumb.d.ts +15 -0
  91. package/dist/elements/nymbal-breadcrumb.js +11 -0
  92. package/dist/elements/nymbal-breadcrumb.js.map +1 -0
  93. package/dist/elements/nymbal-cart-drawer.d.ts +24 -0
  94. package/dist/elements/nymbal-cart-drawer.js +11 -0
  95. package/dist/elements/nymbal-cart-drawer.js.map +1 -0
  96. package/dist/elements/nymbal-cart-page.d.ts +20 -0
  97. package/dist/elements/nymbal-cart-page.js +14 -0
  98. package/dist/elements/nymbal-cart-page.js.map +1 -0
  99. package/dist/elements/nymbal-category-nav.d.ts +15 -0
  100. package/dist/elements/nymbal-category-nav.js +11 -0
  101. package/dist/elements/nymbal-category-nav.js.map +1 -0
  102. package/dist/elements/nymbal-checkout.d.ts +77 -0
  103. package/dist/elements/nymbal-checkout.js +15 -0
  104. package/dist/elements/nymbal-checkout.js.map +1 -0
  105. package/dist/elements/nymbal-image-gallery.d.ts +19 -0
  106. package/dist/elements/nymbal-image-gallery.js +10 -0
  107. package/dist/elements/nymbal-image-gallery.js.map +1 -0
  108. package/dist/elements/nymbal-login-form.d.ts +17 -0
  109. package/dist/elements/nymbal-login-form.js +12 -0
  110. package/dist/elements/nymbal-login-form.js.map +1 -0
  111. package/dist/elements/nymbal-mini-cart.d.ts +17 -0
  112. package/dist/elements/nymbal-mini-cart.js +10 -0
  113. package/dist/elements/nymbal-mini-cart.js.map +1 -0
  114. package/dist/elements/nymbal-order-summary.d.ts +20 -0
  115. package/dist/elements/nymbal-order-summary.js +11 -0
  116. package/dist/elements/nymbal-order-summary.js.map +1 -0
  117. package/dist/elements/nymbal-payment-element.d.ts +4 -0
  118. package/dist/elements/nymbal-payment-element.js +10 -0
  119. package/dist/elements/nymbal-payment-element.js.map +1 -0
  120. package/dist/elements/nymbal-product-card.d.ts +4 -0
  121. package/dist/elements/nymbal-product-card.js +12 -0
  122. package/dist/elements/nymbal-product-card.js.map +1 -0
  123. package/dist/elements/nymbal-product-filter.d.ts +32 -0
  124. package/dist/elements/nymbal-product-filter.js +10 -0
  125. package/dist/elements/nymbal-product-filter.js.map +1 -0
  126. package/dist/elements/nymbal-product-grid.d.ts +24 -0
  127. package/dist/elements/nymbal-product-grid.js +14 -0
  128. package/dist/elements/nymbal-product-grid.js.map +1 -0
  129. package/dist/elements/nymbal-product-price.d.ts +18 -0
  130. package/dist/elements/nymbal-product-price.js +12 -0
  131. package/dist/elements/nymbal-product-price.js.map +1 -0
  132. package/dist/elements/nymbal-profile-form.d.ts +19 -0
  133. package/dist/elements/nymbal-profile-form.js +12 -0
  134. package/dist/elements/nymbal-profile-form.js.map +1 -0
  135. package/dist/elements/nymbal-quantity-selector.d.ts +20 -0
  136. package/dist/elements/nymbal-quantity-selector.js +10 -0
  137. package/dist/elements/nymbal-quantity-selector.js.map +1 -0
  138. package/dist/elements/nymbal-register-form.d.ts +17 -0
  139. package/dist/elements/nymbal-register-form.js +12 -0
  140. package/dist/elements/nymbal-register-form.js.map +1 -0
  141. package/dist/elements/nymbal-review-form.d.ts +17 -0
  142. package/dist/elements/nymbal-review-form.js +12 -0
  143. package/dist/elements/nymbal-review-form.js.map +1 -0
  144. package/dist/elements/nymbal-review-list.d.ts +14 -0
  145. package/dist/elements/nymbal-review-list.js +10 -0
  146. package/dist/elements/nymbal-review-list.js.map +1 -0
  147. package/dist/elements/nymbal-search-bar.d.ts +19 -0
  148. package/dist/elements/nymbal-search-bar.js +10 -0
  149. package/dist/elements/nymbal-search-bar.js.map +1 -0
  150. package/dist/elements/nymbal-search-results.d.ts +29 -0
  151. package/dist/elements/nymbal-search-results.js +14 -0
  152. package/dist/elements/nymbal-search-results.js.map +1 -0
  153. package/dist/elements/nymbal-site-footer.d.ts +15 -0
  154. package/dist/elements/nymbal-site-footer.js +11 -0
  155. package/dist/elements/nymbal-site-footer.js.map +1 -0
  156. package/dist/elements/nymbal-site-header.d.ts +19 -0
  157. package/dist/elements/nymbal-site-header.js +11 -0
  158. package/dist/elements/nymbal-site-header.js.map +1 -0
  159. package/dist/elements/nymbal-sort-dropdown.d.ts +28 -0
  160. package/dist/elements/nymbal-sort-dropdown.js +10 -0
  161. package/dist/elements/nymbal-sort-dropdown.js.map +1 -0
  162. package/dist/elements/nymbal-stock-badge.d.ts +16 -0
  163. package/dist/elements/nymbal-stock-badge.js +11 -0
  164. package/dist/elements/nymbal-stock-badge.js.map +1 -0
  165. package/dist/elements/nymbal-toast.d.ts +18 -0
  166. package/dist/elements/nymbal-toast.js +10 -0
  167. package/dist/elements/nymbal-toast.js.map +1 -0
  168. package/dist/elements/nymbal-variant-selector.d.ts +16 -0
  169. package/dist/elements/nymbal-variant-selector.js +10 -0
  170. package/dist/elements/nymbal-variant-selector.js.map +1 -0
  171. package/dist/form-element-C-qo3gpD.d.ts +25 -0
  172. package/dist/hydrate-support.d.ts +2 -0
  173. package/dist/hydrate-support.js +3 -0
  174. package/dist/hydrate-support.js.map +1 -0
  175. package/dist/index.d.ts +37 -105
  176. package/dist/index.js +130 -1098
  177. package/dist/index.js.map +1 -1
  178. package/dist/nymbal-payment-element-Bt8EF42T.d.ts +51 -0
  179. package/dist/nymbal-product-card-C8HkE_rG.d.ts +47 -0
  180. package/dist/register-components.d.ts +46 -0
  181. package/dist/register-components.js +48 -0
  182. package/dist/register-components.js.map +1 -0
  183. package/dist/selected-variant-Dym0shlJ.d.ts +23 -0
  184. package/dist/ssr/index.d.ts +30 -0
  185. package/dist/ssr/index.js +143 -0
  186. package/dist/ssr/index.js.map +1 -0
  187. package/package.json +33 -6
@@ -0,0 +1,153 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement,
4
+ baseStyles,
5
+ parsedJson,
6
+ propertiesOf
7
+ } from "./chunk-EYDMJNNK.js";
8
+
9
+ // src/components/variant-selector.ts
10
+ import { css, html } from "lit";
11
+ function optionsOf(variant) {
12
+ return Array.isArray(variant.options) ? variant.options : [];
13
+ }
14
+ function optionGroupsOf(product) {
15
+ const groups = /* @__PURE__ */ new Map();
16
+ for (const variant of product.variants) {
17
+ for (const opt of optionsOf(variant)) {
18
+ let values = groups.get(opt.name);
19
+ if (!values) {
20
+ values = /* @__PURE__ */ new Set();
21
+ groups.set(opt.name, values);
22
+ }
23
+ values.add(opt.value);
24
+ }
25
+ }
26
+ return Array.from(groups.entries()).map(([name, values]) => ({
27
+ name,
28
+ values: Array.from(values)
29
+ }));
30
+ }
31
+ var SCHEMA = {
32
+ tag: "nymbal-variant-selector",
33
+ reactName: "VariantSelector",
34
+ // The product a server read, whose options show until the product store settles.
35
+ attributes: [{ name: "product", prop: "product", type: "json" }],
36
+ events: [
37
+ { name: "nymbal:variant:selected", prop: "onVariantSelected", detail: "{ variant: unknown }" }
38
+ ],
39
+ parts: ["group", "label", "options", "option"],
40
+ slots: [],
41
+ children: false
42
+ };
43
+ var NymbalVariantSelector = class extends NymbalElement {
44
+ static schema = SCHEMA;
45
+ static properties = propertiesOf(SCHEMA);
46
+ static styles = [
47
+ baseStyles,
48
+ css`
49
+ :host {
50
+ display: block;
51
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
52
+ }
53
+ [part~='group'] {
54
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
55
+ }
56
+ [part~='label'] {
57
+ display: block;
58
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
59
+ font-weight: var(--nymbal-font-weight-medium, 500);
60
+ margin-bottom: var(--nymbal-spacing-sm, 0.5rem);
61
+ color: var(--nymbal-color-text, #18181b);
62
+ }
63
+ [part~='options'] {
64
+ display: flex;
65
+ flex-wrap: wrap;
66
+ gap: var(--nymbal-spacing-sm, 0.5rem);
67
+ }
68
+ [part~='option'] {
69
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
70
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
71
+ background: var(--nymbal-color-surface, #ffffff);
72
+ color: var(--nymbal-color-text, #18181b);
73
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
74
+ border-radius: var(--nymbal-radius-md, 0.375rem);
75
+ padding: var(--nymbal-spacing-xs, 0.25rem) var(--nymbal-spacing-md, 1rem);
76
+ cursor: pointer;
77
+ transition:
78
+ border-color var(--nymbal-transition-fast, 150ms ease),
79
+ background var(--nymbal-transition-fast, 150ms ease);
80
+ }
81
+ [part~='option']:hover {
82
+ border-color: var(--nymbal-color-primary, #18181b);
83
+ }
84
+ [part~='option'][aria-pressed='true'] {
85
+ background: var(--nymbal-color-primary, #18181b);
86
+ color: var(--nymbal-color-text-inverse, #ffffff);
87
+ border-color: var(--nymbal-color-primary, #18181b);
88
+ }
89
+ [part~='option']:disabled {
90
+ opacity: 0.4;
91
+ cursor: not-allowed;
92
+ }
93
+ `
94
+ ];
95
+ stores() {
96
+ return [this.client.product];
97
+ }
98
+ storeSettled() {
99
+ this.product = void 0;
100
+ }
101
+ render() {
102
+ const state = this.readsStores ? this.client.product.getState() : null;
103
+ const product = parsedJson(this, "product", this.product) ?? state?.product ?? null;
104
+ const selected = state?.selectedVariant ?? null;
105
+ if (!product) return html``;
106
+ return optionGroupsOf(product).map(
107
+ (group) => html`
108
+ <div part="group">
109
+ <span part="label">${group.name}</span>
110
+ <div part="options">
111
+ ${group.values.map((value) => {
112
+ const isSelected = selected !== null && optionsOf(selected).some((o) => o.name === group.name && o.value === value);
113
+ return html`<button
114
+ type="button"
115
+ part="option"
116
+ data-testid="variant-option-${group.name}-${value}"
117
+ aria-pressed=${String(isSelected)}
118
+ @click=${() => this.choose(product, group.name, value)}
119
+ >
120
+ ${value}
121
+ </button>`;
122
+ })}
123
+ </div>
124
+ </div>
125
+ `
126
+ );
127
+ }
128
+ choose(product, optionName, optionValue) {
129
+ const selected = this.client.product.getState().selectedVariant;
130
+ const currentSelections = /* @__PURE__ */ new Map();
131
+ if (selected) {
132
+ for (const opt of optionsOf(selected)) currentSelections.set(opt.name, opt.value);
133
+ }
134
+ currentSelections.set(optionName, optionValue);
135
+ const match = product.variants.find(
136
+ (v) => optionsOf(v).every((o) => currentSelections.get(o.name) === o.value)
137
+ );
138
+ if (!match) return;
139
+ this.client.product.selectVariant(match);
140
+ this.dispatchEvent(
141
+ new CustomEvent("nymbal:variant:selected", {
142
+ bubbles: true,
143
+ composed: true,
144
+ detail: { variant: match }
145
+ })
146
+ );
147
+ }
148
+ };
149
+
150
+ export {
151
+ NymbalVariantSelector
152
+ };
153
+ //# sourceMappingURL=chunk-H3ZBYNHV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/variant-selector.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport type { DenormalisedVariant, DenormalisedProduct } from '@nymbalhq/sdk'\nimport type { VariantOption } from '@nymbalhq/types'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\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\nfunction optionGroupsOf(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\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-variant-selector',\n reactName: 'VariantSelector',\n // The product a server read, whose options show until the product store settles.\n attributes: [{ name: 'product', prop: 'product', type: 'json' }],\n events: [\n { name: 'nymbal:variant:selected', prop: 'onVariantSelected', detail: '{ variant: unknown }' },\n ],\n parts: ['group', 'label', 'options', 'option'],\n slots: [],\n children: false,\n}\n\nexport class NymbalVariantSelector extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n\n static override styles = [\n baseStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n }\n [part~='group'] {\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n }\n [part~='label'] {\n display: block;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n margin-bottom: var(--nymbal-spacing-sm, 0.5rem);\n color: var(--nymbal-color-text, #18181b);\n }\n [part~='options'] {\n display: flex;\n flex-wrap: wrap;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n }\n [part~='option'] {\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n background: var(--nymbal-color-surface, #ffffff);\n color: var(--nymbal-color-text, #18181b);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.375rem);\n padding: var(--nymbal-spacing-xs, 0.25rem) var(--nymbal-spacing-md, 1rem);\n cursor: pointer;\n transition:\n border-color var(--nymbal-transition-fast, 150ms ease),\n background var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='option']:hover {\n border-color: var(--nymbal-color-primary, #18181b);\n }\n [part~='option'][aria-pressed='true'] {\n background: var(--nymbal-color-primary, #18181b);\n color: var(--nymbal-color-text-inverse, #ffffff);\n border-color: var(--nymbal-color-primary, #18181b);\n }\n [part~='option']:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n `,\n ]\n\n declare product: unknown\n\n protected override stores() {\n return [this.client.product]\n }\n\n protected override storeSettled(): void {\n this.product = undefined\n }\n\n protected override render() {\n const state = this.readsStores ? this.client.product.getState() : null\n const product =\n parsedJson<DenormalisedProduct>(this, 'product', this.product) ?? state?.product ?? null\n const selected = state?.selectedVariant ?? null\n if (!product) return html``\n\n return optionGroupsOf(product).map(\n (group) => html`\n <div part=\"group\">\n <span part=\"label\">${group.name}</span>\n <div part=\"options\">\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 html`<button\n type=\"button\"\n part=\"option\"\n data-testid=\"variant-option-${group.name}-${value}\"\n aria-pressed=${String(isSelected)}\n @click=${() => this.choose(product, group.name, value)}\n >\n ${value}\n </button>`\n })}\n </div>\n </div>\n `,\n )\n }\n\n private choose(product: DenormalisedProduct, optionName: string, optionValue: string): void {\n const selected = this.client.product.getState().selectedVariant\n const currentSelections = new Map<string, string>()\n if (selected) {\n for (const opt of optionsOf(selected)) currentSelections.set(opt.name, opt.value)\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 if (!match) return\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"],"mappings":"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","names":[]}
@@ -0,0 +1,174 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement,
4
+ baseStyles,
5
+ propertiesOf
6
+ } from "./chunk-EYDMJNNK.js";
7
+
8
+ // src/components/quantity-selector.ts
9
+ import { css, html } from "lit";
10
+ import { live } from "lit/directives/live.js";
11
+ function wholeQuantity(raw, min, max) {
12
+ const n = raw === null || raw.trim() === "" ? Number.NaN : Number(raw);
13
+ if (Number.isNaN(n)) return min;
14
+ return Math.min(max, Math.max(min, Math.trunc(n)));
15
+ }
16
+ var SCHEMA = {
17
+ tag: "nymbal-quantity-selector",
18
+ reactName: "QuantitySelector",
19
+ attributes: [
20
+ { name: "value", prop: "value", type: "number" },
21
+ { name: "min", prop: "min", type: "number" },
22
+ { name: "max", prop: "max", type: "number" }
23
+ ],
24
+ events: [
25
+ { name: "nymbal:quantity:changed", prop: "onQuantityChange", detail: "{ value: number }" }
26
+ ],
27
+ parts: ["decrement", "input", "increment"],
28
+ slots: [],
29
+ children: false
30
+ };
31
+ var NymbalQuantitySelector = class extends NymbalElement {
32
+ static schema = SCHEMA;
33
+ static properties = propertiesOf(SCHEMA);
34
+ static styles = [
35
+ baseStyles,
36
+ css`
37
+ :host {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
41
+ }
42
+ [part~='decrement'],
43
+ [part~='increment'] {
44
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
45
+ font-size: var(--nymbal-font-size-base, 1rem);
46
+ background: var(--nymbal-color-surface, #ffffff);
47
+ color: var(--nymbal-color-text, #18181b);
48
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
49
+ width: 36px;
50
+ height: 36px;
51
+ padding: 0;
52
+ cursor: pointer;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ transition: background var(--nymbal-transition-fast, 150ms ease);
57
+ }
58
+ [part~='decrement']:hover:not(:disabled),
59
+ [part~='increment']:hover:not(:disabled) {
60
+ background: var(--nymbal-color-secondary, #f4f4f5);
61
+ }
62
+ [part~='decrement']:disabled,
63
+ [part~='increment']:disabled {
64
+ opacity: 0.4;
65
+ cursor: not-allowed;
66
+ }
67
+ [part~='decrement'] {
68
+ border-radius: var(--nymbal-radius-md, 0.375rem) 0 0 var(--nymbal-radius-md, 0.375rem);
69
+ }
70
+ [part~='increment'] {
71
+ border-radius: 0 var(--nymbal-radius-md, 0.375rem) var(--nymbal-radius-md, 0.375rem) 0;
72
+ }
73
+ [part~='input'] {
74
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
75
+ font-size: var(--nymbal-font-size-base, 1rem);
76
+ width: 48px;
77
+ height: 36px;
78
+ padding: 0;
79
+ margin: 0;
80
+ text-align: center;
81
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
82
+ border-left: none;
83
+ border-right: none;
84
+ -moz-appearance: textfield;
85
+ }
86
+ [part~='input']::-webkit-outer-spin-button,
87
+ [part~='input']::-webkit-inner-spin-button {
88
+ -webkit-appearance: none;
89
+ margin: 0;
90
+ }
91
+ `
92
+ ];
93
+ _value = 1;
94
+ bounds() {
95
+ return [Number(this.getAttribute("min")) || 1, Number(this.getAttribute("max")) || 99];
96
+ }
97
+ willUpdate(changed) {
98
+ if (!this.hasUpdated || changed.has("value")) {
99
+ this._value = wholeQuantity(this.getAttribute("value"), ...this.bounds());
100
+ }
101
+ }
102
+ // Setting `value` resets the quantity even to the value the attribute already holds (a page resetting to 1 after the
103
+ // shopper stepped up), which Lit's own change check would skip.
104
+ attributeChangedCallback(name, old, value) {
105
+ super.attributeChangedCallback(name, old, value);
106
+ if (name === "value" && old === value && this.hasUpdated) {
107
+ this._value = wholeQuantity(value, ...this.bounds());
108
+ this.requestUpdate();
109
+ }
110
+ }
111
+ render() {
112
+ const [min, max] = this.bounds();
113
+ return html`
114
+ <button
115
+ type="button"
116
+ part="decrement"
117
+ data-testid="qty-decrement"
118
+ aria-label="Decrease quantity"
119
+ ?disabled=${this._value <= min}
120
+ @click=${() => this.step(-1)}
121
+ >
122
+ &#8722;
123
+ </button>
124
+ <input
125
+ part="input"
126
+ data-testid="qty-input"
127
+ type="number"
128
+ .value=${live(String(this._value))}
129
+ min=${min}
130
+ max=${max}
131
+ aria-label="Quantity"
132
+ @change=${this.handleChange}
133
+ />
134
+ <button
135
+ type="button"
136
+ part="increment"
137
+ data-testid="qty-increment"
138
+ aria-label="Increase quantity"
139
+ ?disabled=${this._value >= max}
140
+ @click=${() => this.step(1)}
141
+ >
142
+ &#43;
143
+ </button>
144
+ `;
145
+ }
146
+ step(by) {
147
+ const [min, max] = this.bounds();
148
+ const next = this._value + by;
149
+ if (next < min || next > max) return;
150
+ this._value = next;
151
+ this.emitChange();
152
+ this.requestUpdate();
153
+ }
154
+ handleChange = (e) => {
155
+ const [min, max] = this.bounds();
156
+ this._value = wholeQuantity(e.target.value, min, max);
157
+ this.emitChange();
158
+ this.requestUpdate();
159
+ };
160
+ emitChange() {
161
+ this.dispatchEvent(
162
+ new CustomEvent("nymbal:quantity:changed", {
163
+ bubbles: true,
164
+ composed: true,
165
+ detail: { value: this._value }
166
+ })
167
+ );
168
+ }
169
+ };
170
+
171
+ export {
172
+ NymbalQuantitySelector
173
+ };
174
+ //# sourceMappingURL=chunk-HPBC3OG7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/quantity-selector.ts"],"sourcesContent":["import { css, html, type PropertyValues } from 'lit'\nimport { live } from 'lit/directives/live.js'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\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\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-quantity-selector',\n reactName: 'QuantitySelector',\n attributes: [\n { name: 'value', prop: 'value', type: 'number' },\n { name: 'min', prop: 'min', type: 'number' },\n { name: 'max', prop: 'max', type: 'number' },\n ],\n events: [\n { name: 'nymbal:quantity:changed', prop: 'onQuantityChange', detail: '{ value: number }' },\n ],\n parts: ['decrement', 'input', 'increment'],\n slots: [],\n children: false,\n}\n\nexport class NymbalQuantitySelector extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n\n static override styles = [\n baseStyles,\n css`\n :host {\n display: inline-flex;\n align-items: center;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n }\n [part~='decrement'],\n [part~='increment'] {\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n font-size: var(--nymbal-font-size-base, 1rem);\n background: var(--nymbal-color-surface, #ffffff);\n color: var(--nymbal-color-text, #18181b);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n width: 36px;\n height: 36px;\n padding: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='decrement']:hover:not(:disabled),\n [part~='increment']:hover:not(:disabled) {\n background: var(--nymbal-color-secondary, #f4f4f5);\n }\n [part~='decrement']:disabled,\n [part~='increment']:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n }\n [part~='decrement'] {\n border-radius: var(--nymbal-radius-md, 0.375rem) 0 0 var(--nymbal-radius-md, 0.375rem);\n }\n [part~='increment'] {\n border-radius: 0 var(--nymbal-radius-md, 0.375rem) var(--nymbal-radius-md, 0.375rem) 0;\n }\n [part~='input'] {\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n font-size: var(--nymbal-font-size-base, 1rem);\n width: 48px;\n height: 36px;\n padding: 0;\n margin: 0;\n text-align: center;\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-left: none;\n border-right: none;\n -moz-appearance: textfield;\n }\n [part~='input']::-webkit-outer-spin-button,\n [part~='input']::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n }\n `,\n ]\n\n private _value = 1\n\n private bounds(): [number, number] {\n return [Number(this.getAttribute('min')) || 1, Number(this.getAttribute('max')) || 99]\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n if (!this.hasUpdated || changed.has('value')) {\n this._value = wholeQuantity(this.getAttribute('value'), ...this.bounds())\n }\n }\n\n // Setting `value` resets the quantity even to the value the attribute already holds (a page resetting to 1 after the\n // shopper stepped up), which Lit's own change check would skip.\n override attributeChangedCallback(name: string, old: string | null, value: string | null): void {\n super.attributeChangedCallback(name, old, value)\n if (name === 'value' && old === value && this.hasUpdated) {\n this._value = wholeQuantity(value, ...this.bounds())\n this.requestUpdate()\n }\n }\n\n protected override render() {\n const [min, max] = this.bounds()\n return html`\n <button\n type=\"button\"\n part=\"decrement\"\n data-testid=\"qty-decrement\"\n aria-label=\"Decrease quantity\"\n ?disabled=${this._value <= min}\n @click=${() => this.step(-1)}\n >\n &#8722;\n </button>\n <input\n part=\"input\"\n data-testid=\"qty-input\"\n type=\"number\"\n .value=${live(String(this._value))}\n min=${min}\n max=${max}\n aria-label=\"Quantity\"\n @change=${this.handleChange}\n />\n <button\n type=\"button\"\n part=\"increment\"\n data-testid=\"qty-increment\"\n aria-label=\"Increase quantity\"\n ?disabled=${this._value >= max}\n @click=${() => this.step(1)}\n >\n &#43;\n </button>\n `\n }\n\n private step(by: number): void {\n const [min, max] = this.bounds()\n const next = this._value + by\n if (next < min || next > max) return\n this._value = next\n this.emitChange()\n this.requestUpdate()\n }\n\n private handleChange = (e: Event): void => {\n const [min, max] = this.bounds()\n this._value = wholeQuantity((e.target as HTMLInputElement).value, min, max)\n this.emitChange()\n this.requestUpdate()\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"],"mappings":"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","names":[]}
@@ -0,0 +1,105 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ navigateOnClick
4
+ } from "./chunk-LO2EW5YP.js";
5
+ import {
6
+ NymbalElement,
7
+ baseStyles,
8
+ parsedJson,
9
+ propertiesOf
10
+ } from "./chunk-EYDMJNNK.js";
11
+
12
+ // src/components/category-nav.ts
13
+ import { css, html, nothing } from "lit";
14
+ var SCHEMA = {
15
+ tag: "nymbal-category-nav",
16
+ reactName: "CategoryNav",
17
+ // The categories as `GET /api/categories` serves them.
18
+ attributes: [{ name: "categories", prop: "categories", type: "json" }],
19
+ events: [],
20
+ parts: ["grid", "card"],
21
+ slots: [],
22
+ children: false
23
+ };
24
+ var NymbalCategoryNav = class extends NymbalElement {
25
+ static schema = SCHEMA;
26
+ static properties = propertiesOf(SCHEMA);
27
+ static styles = [
28
+ baseStyles,
29
+ css`
30
+ :host {
31
+ display: block;
32
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
33
+ }
34
+ [part~='grid'] {
35
+ display: grid;
36
+ grid-template-columns: repeat(2, 1fr);
37
+ gap: var(--nymbal-spacing-md, 1rem);
38
+ }
39
+ @media (min-width: 641px) {
40
+ [part~='grid'] {
41
+ grid-template-columns: repeat(4, 1fr);
42
+ gap: var(--nymbal-spacing-lg, 1.5rem);
43
+ }
44
+ }
45
+ [part~='card'] {
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: center;
49
+ justify-content: center;
50
+ min-height: 140px;
51
+ padding: var(--nymbal-spacing-lg, 1.5rem) var(--nymbal-spacing-md, 1rem);
52
+ text-align: center;
53
+ text-decoration: none;
54
+ background-color: var(--nymbal-color-surface, #ffffff);
55
+ border: 1px solid var(--nymbal-color-border, #e4e4e7);
56
+ border-radius: var(--nymbal-radius-lg, 0.75rem);
57
+ transition:
58
+ border-color var(--nymbal-transition-fast, 150ms ease),
59
+ box-shadow var(--nymbal-transition-fast, 150ms ease),
60
+ transform var(--nymbal-transition-fast, 150ms ease);
61
+ }
62
+ [part~='card']:hover {
63
+ border-color: var(--nymbal-color-accent, #2563eb);
64
+ box-shadow: var(--nymbal-shadow-md, 0 4px 6px rgba(0, 0, 0, 0.1));
65
+ transform: translateY(-2px);
66
+ }
67
+ .name {
68
+ font-size: var(--nymbal-font-size-base, 1rem);
69
+ font-weight: var(--nymbal-font-weight-medium, 500);
70
+ color: var(--nymbal-color-text, #18181b);
71
+ }
72
+ .description {
73
+ margin-top: var(--nymbal-spacing-xs, 0.25rem);
74
+ font-size: var(--nymbal-font-size-xs, 0.75rem);
75
+ color: var(--nymbal-color-text-muted, #71717a);
76
+ }
77
+ `
78
+ ];
79
+ render() {
80
+ const categories = parsedJson(this, "categories", this.categories);
81
+ if (!Array.isArray(categories) || categories.length === 0) return nothing;
82
+ return html`
83
+ <div part="grid">
84
+ ${categories.map(
85
+ (c) => html`
86
+ <a
87
+ part="card"
88
+ href="/category/${encodeURIComponent(c.slug)}"
89
+ data-testid="category-${c.slug}"
90
+ @click=${navigateOnClick}
91
+ >
92
+ <span class="name">${c.name}</span>
93
+ ${c.description ? html`<span class="description">${c.description}</span>` : nothing}
94
+ </a>
95
+ `
96
+ )}
97
+ </div>
98
+ `;
99
+ }
100
+ };
101
+
102
+ export {
103
+ NymbalCategoryNav
104
+ };
105
+ //# sourceMappingURL=chunk-IYT7VXFS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/category-nav.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\nimport { navigateOnClick } from '../navigate.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-category-nav',\n reactName: 'CategoryNav',\n // The categories as `GET /api/categories` serves them.\n attributes: [{ name: 'categories', prop: 'categories', type: 'json' }],\n events: [],\n parts: ['grid', 'card'],\n slots: [],\n children: false,\n}\n\ninterface Category {\n slug: string\n name: string\n description?: string | null\n}\n\n/** A card per category, each a link to its listing. */\nexport class NymbalCategoryNav extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n }\n [part~='grid'] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: var(--nymbal-spacing-md, 1rem);\n }\n @media (min-width: 641px) {\n [part~='grid'] {\n grid-template-columns: repeat(4, 1fr);\n gap: var(--nymbal-spacing-lg, 1.5rem);\n }\n }\n [part~='card'] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 140px;\n padding: var(--nymbal-spacing-lg, 1.5rem) var(--nymbal-spacing-md, 1rem);\n text-align: center;\n text-decoration: none;\n background-color: var(--nymbal-color-surface, #ffffff);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-lg, 0.75rem);\n transition:\n border-color var(--nymbal-transition-fast, 150ms ease),\n box-shadow var(--nymbal-transition-fast, 150ms ease),\n transform var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='card']:hover {\n border-color: var(--nymbal-color-accent, #2563eb);\n box-shadow: var(--nymbal-shadow-md, 0 4px 6px rgba(0, 0, 0, 0.1));\n transform: translateY(-2px);\n }\n .name {\n font-size: var(--nymbal-font-size-base, 1rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n color: var(--nymbal-color-text, #18181b);\n }\n .description {\n margin-top: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n `,\n ]\n\n declare categories: unknown\n\n protected override render() {\n const categories = parsedJson<Category[]>(this, 'categories', this.categories)\n if (!Array.isArray(categories) || categories.length === 0) return nothing\n return html`\n <div part=\"grid\">\n ${categories.map(\n (c) => html`\n <a\n part=\"card\"\n href=\"/category/${encodeURIComponent(c.slug)}\"\n data-testid=\"category-${c.slug}\"\n @click=${navigateOnClick}\n >\n <span class=\"name\">${c.name}</span>\n ${c.description ? html`<span class=\"description\">${c.description}</span>` : nothing}\n </a>\n `,\n )}\n </div>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AAUnC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY,CAAC,EAAE,MAAM,cAAc,MAAM,cAAc,MAAM,OAAO,CAAC;AAAA,EACrE,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,QAAQ,MAAM;AAAA,EACtB,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AASO,IAAM,oBAAN,cAAgC,cAAc;AAAA,EACnD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiDF;AAAA,EAImB,SAAS;AAC1B,UAAM,aAAa,WAAuB,MAAM,cAAc,KAAK,UAAU;AAC7E,QAAI,CAAC,MAAM,QAAQ,UAAU,KAAK,WAAW,WAAW,EAAG,QAAO;AAClE,WAAO;AAAA;AAAA,UAED,WAAW;AAAA,MACX,CAAC,MAAM;AAAA;AAAA;AAAA,gCAGe,mBAAmB,EAAE,IAAI,CAAC;AAAA,sCACpB,EAAE,IAAI;AAAA,uBACrB,eAAe;AAAA;AAAA,mCAEH,EAAE,IAAI;AAAA,gBACzB,EAAE,cAAc,iCAAiC,EAAE,WAAW,YAAY,OAAO;AAAA;AAAA;AAAA,IAGzF,CAAC;AAAA;AAAA;AAAA,EAGP;AACF;","names":[]}
@@ -0,0 +1,105 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement,
4
+ baseStyles,
5
+ propertiesOf
6
+ } from "./chunk-EYDMJNNK.js";
7
+
8
+ // src/components/add-to-cart.ts
9
+ import { css, html } from "lit";
10
+ var MIN_QTY = 1;
11
+ var MAX_QTY = 99;
12
+ function quantityOf(attr) {
13
+ const n = attr === null || attr.trim() === "" ? Number.NaN : Number(attr);
14
+ const whole = Number.isInteger(n) ? n : MIN_QTY;
15
+ return Math.min(MAX_QTY, Math.max(MIN_QTY, whole));
16
+ }
17
+ var SCHEMA = {
18
+ tag: "nymbal-add-to-cart",
19
+ reactName: "AddToCart",
20
+ attributes: [
21
+ { name: "variant-id", prop: "variantId", type: "string" },
22
+ { name: "qty", prop: "qty", type: "number" },
23
+ { name: "disabled", prop: "disabled", type: "boolean" }
24
+ ],
25
+ events: [
26
+ {
27
+ name: "nymbal:cart:item-added",
28
+ prop: "onItemAdded",
29
+ detail: "{ variantId: string; qty: number }"
30
+ }
31
+ ],
32
+ parts: ["button"],
33
+ slots: ["label", "loading-label"],
34
+ children: true
35
+ };
36
+ var NymbalAddToCart = class extends NymbalElement {
37
+ static schema = SCHEMA;
38
+ static properties = propertiesOf(SCHEMA);
39
+ static styles = [
40
+ baseStyles,
41
+ css`
42
+ :host {
43
+ display: inline-block;
44
+ }
45
+ [part~='button'] {
46
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
47
+ font-size: var(--nymbal-font-size-base, 1rem);
48
+ font-weight: var(--nymbal-font-weight-medium, 500);
49
+ background: var(--nymbal-color-primary, #18181b);
50
+ color: var(--nymbal-color-text-inverse, #ffffff);
51
+ border: none;
52
+ border-radius: var(--nymbal-radius-md, 0.375rem);
53
+ padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-lg, 1.5rem);
54
+ cursor: pointer;
55
+ transition: background var(--nymbal-transition-fast, 150ms ease);
56
+ min-width: 140px;
57
+ }
58
+ [part~='button']:hover:not(:disabled) {
59
+ background: var(--nymbal-color-primary-hover, #27272a);
60
+ }
61
+ [part~='button']:disabled {
62
+ opacity: 0.5;
63
+ cursor: not-allowed;
64
+ }
65
+ `
66
+ ];
67
+ stores() {
68
+ return [this.client.cart];
69
+ }
70
+ render() {
71
+ const variantId = this.getAttribute("variant-id");
72
+ const loading = this.readsStores && this.client.cart.getState().loading;
73
+ return html`<button
74
+ type="button"
75
+ part="button"
76
+ data-testid="add-to-cart"
77
+ ?disabled=${this.hasAttribute("disabled") || loading || !variantId}
78
+ aria-busy=${String(loading)}
79
+ @click=${this.handleClick}
80
+ >
81
+ ${loading ? html`<slot name="loading-label">Adding...</slot>` : html`<slot name="label">Add to Cart</slot>`}
82
+ </button>`;
83
+ }
84
+ handleClick = async () => {
85
+ const variantId = this.getAttribute("variant-id");
86
+ if (!variantId) return;
87
+ const qty = quantityOf(this.getAttribute("qty"));
88
+ try {
89
+ await this.client.cart.addItem(variantId, qty);
90
+ this.dispatchEvent(
91
+ new CustomEvent("nymbal:cart:item-added", {
92
+ bubbles: true,
93
+ composed: true,
94
+ detail: { variantId, qty }
95
+ })
96
+ );
97
+ } catch {
98
+ }
99
+ };
100
+ };
101
+
102
+ export {
103
+ NymbalAddToCart
104
+ };
105
+ //# sourceMappingURL=chunk-LHYXNQWY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/add-to-cart.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\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\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-add-to-cart',\n reactName: 'AddToCart',\n attributes: [\n { name: 'variant-id', prop: 'variantId', type: 'string' },\n { name: 'qty', prop: 'qty', type: 'number' },\n { name: 'disabled', prop: 'disabled', type: 'boolean' },\n ],\n events: [\n {\n name: 'nymbal:cart:item-added',\n prop: 'onItemAdded',\n detail: '{ variantId: string; qty: number }',\n },\n ],\n parts: ['button'],\n slots: ['label', 'loading-label'],\n children: true,\n}\n\nexport class NymbalAddToCart extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n\n static override styles = [\n baseStyles,\n css`\n :host {\n display: inline-block;\n }\n [part~='button'] {\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n font-size: var(--nymbal-font-size-base, 1rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n background: var(--nymbal-color-primary, #18181b);\n color: var(--nymbal-color-text-inverse, #ffffff);\n border: none;\n border-radius: var(--nymbal-radius-md, 0.375rem);\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-lg, 1.5rem);\n cursor: pointer;\n transition: background var(--nymbal-transition-fast, 150ms ease);\n min-width: 140px;\n }\n [part~='button']:hover:not(:disabled) {\n background: var(--nymbal-color-primary-hover, #27272a);\n }\n [part~='button']:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n `,\n ]\n\n protected override stores() {\n return [this.client.cart]\n }\n\n protected override render() {\n const variantId = this.getAttribute('variant-id')\n // Until it reads the cart store (a server render, and the first render taking that over) the cart is not adding.\n const loading = this.readsStores && this.client.cart.getState().loading\n return html`<button\n type=\"button\"\n part=\"button\"\n data-testid=\"add-to-cart\"\n ?disabled=${this.hasAttribute('disabled') || loading || !variantId}\n aria-busy=${String(loading)}\n @click=${this.handleClick}\n >\n ${loading\n ? html`<slot name=\"loading-label\">Adding...</slot>`\n : html`<slot name=\"label\">Add to Cart</slot>`}\n </button>`\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"],"mappings":";;;;;;;;AAAA,SAAS,KAAK,YAAY;AAG1B,IAAM,UAAU;AAChB,IAAM,UAAU;AAGhB,SAAS,WAAW,MAA6B;AAC/C,QAAM,IAAI,SAAS,QAAQ,KAAK,KAAK,MAAM,KAAK,OAAO,MAAM,OAAO,IAAI;AACxE,QAAM,QAAQ,OAAO,UAAU,CAAC,IAAI,IAAI;AACxC,SAAO,KAAK,IAAI,SAAS,KAAK,IAAI,SAAS,KAAK,CAAC;AACnD;AAEA,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,MAAM,cAAc,MAAM,aAAa,MAAM,SAAS;AAAA,IACxD,EAAE,MAAM,OAAO,MAAM,OAAO,MAAM,SAAS;AAAA,IAC3C,EAAE,MAAM,YAAY,MAAM,YAAY,MAAM,UAAU;AAAA,EACxD;AAAA,EACA,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,OAAO,CAAC,QAAQ;AAAA,EAChB,OAAO,CAAC,SAAS,eAAe;AAAA,EAChC,UAAU;AACZ;AAEO,IAAM,kBAAN,cAA8B,cAAc;AAAA,EACjD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAEhD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBF;AAAA,EAEmB,SAAS;AAC1B,WAAO,CAAC,KAAK,OAAO,IAAI;AAAA,EAC1B;AAAA,EAEmB,SAAS;AAC1B,UAAM,YAAY,KAAK,aAAa,YAAY;AAEhD,UAAM,UAAU,KAAK,eAAe,KAAK,OAAO,KAAK,SAAS,EAAE;AAChE,WAAO;AAAA;AAAA;AAAA;AAAA,kBAIO,KAAK,aAAa,UAAU,KAAK,WAAW,CAAC,SAAS;AAAA,kBACtD,OAAO,OAAO,CAAC;AAAA,eAClB,KAAK,WAAW;AAAA;AAAA,QAEvB,UACE,oDACA,2CAA2C;AAAA;AAAA,EAEnD;AAAA,EAEQ,cAAc,YAA2B;AAC/C,UAAM,YAAY,KAAK,aAAa,YAAY;AAChD,QAAI,CAAC,UAAW;AAChB,UAAM,MAAM,WAAW,KAAK,aAAa,KAAK,CAAC;AAC/C,QAAI;AACF,YAAM,KAAK,OAAO,KAAK,QAAQ,WAAW,GAAG;AAC7C,WAAK;AAAA,QACH,IAAI,YAAY,0BAA0B;AAAA,UACxC,SAAS;AAAA,UACT,UAAU;AAAA,UACV,QAAQ,EAAE,WAAW,IAAI;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,25 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+
3
+ // src/navigate.ts
4
+ function navigateOnClick(event) {
5
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
6
+ return;
7
+ }
8
+ const anchor = event.currentTarget;
9
+ const href = anchor?.getAttribute("href");
10
+ if (anchor === null || href === null || href === void 0) return;
11
+ const routed = !anchor.dispatchEvent(
12
+ new CustomEvent("nymbal:navigate", {
13
+ detail: { href },
14
+ bubbles: true,
15
+ composed: true,
16
+ cancelable: true
17
+ })
18
+ );
19
+ if (routed) event.preventDefault();
20
+ }
21
+
22
+ export {
23
+ navigateOnClick
24
+ };
25
+ //# sourceMappingURL=chunk-LO2EW5YP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/navigate.ts"],"sourcesContent":["/**\n * The click handler of an element's anchor part. An anchor in a shadow root is a plain `<a>`, which a client-side\n * router does not see, so a plain left click asks the page first: a cancelable, composed `nymbal:navigate` with the\n * href. A page that routes it (Next's `router.push`) cancels the event, and the click's own navigation is prevented; a\n * page that does not leaves the anchor to load the page. A click with a modifier key or another button is the\n * browser's (a new tab or window), untouched.\n */\nexport function navigateOnClick(event: MouseEvent): void {\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return\n }\n const anchor = event.currentTarget as HTMLAnchorElement | null\n const href = anchor?.getAttribute('href')\n if (anchor === null || href === null || href === undefined) return\n const routed = !anchor.dispatchEvent(\n new CustomEvent('nymbal:navigate', {\n detail: { href },\n bubbles: true,\n composed: true,\n cancelable: true,\n }),\n )\n if (routed) event.preventDefault()\n}\n"],"mappings":";;;AAOO,SAAS,gBAAgB,OAAyB;AACvD,MACE,MAAM,oBACN,MAAM,WAAW,KACjB,MAAM,WACN,MAAM,WACN,MAAM,YACN,MAAM,QACN;AACA;AAAA,EACF;AACA,QAAM,SAAS,MAAM;AACrB,QAAM,OAAO,QAAQ,aAAa,MAAM;AACxC,MAAI,WAAW,QAAQ,SAAS,QAAQ,SAAS,OAAW;AAC5D,QAAM,SAAS,CAAC,OAAO;AAAA,IACrB,IAAI,YAAY,mBAAmB;AAAA,MACjC,QAAQ,EAAE,KAAK;AAAA,MACf,SAAS;AAAA,MACT,UAAU;AAAA,MACV,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,MAAI,OAAQ,OAAM,eAAe;AACnC;","names":[]}