@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,138 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalFormElement,
4
+ renderField
5
+ } from "./chunk-PN6QKYE4.js";
6
+ import {
7
+ buttonStyles,
8
+ fieldStyles
9
+ } from "./chunk-GHXTJ2CF.js";
10
+ import {
11
+ baseStyles,
12
+ propertiesOf
13
+ } from "./chunk-EYDMJNNK.js";
14
+
15
+ // src/components/register-form.ts
16
+ import { css, html } from "lit";
17
+ var SCHEMA = {
18
+ tag: "nymbal-register-form",
19
+ reactName: "RegisterForm",
20
+ attributes: [],
21
+ events: [
22
+ {
23
+ name: "nymbal:auth:signed-in",
24
+ prop: "onSignedIn",
25
+ detail: "{ customer: unknown }"
26
+ }
27
+ ],
28
+ parts: ["form", "row", "field", "label", "input", "error", "submit"],
29
+ slots: ["submit-label"],
30
+ children: false
31
+ };
32
+ var NymbalRegisterForm = class extends NymbalFormElement {
33
+ static schema = SCHEMA;
34
+ static properties = propertiesOf(SCHEMA);
35
+ static styles = [
36
+ baseStyles,
37
+ buttonStyles,
38
+ fieldStyles,
39
+ css`
40
+ [part~='row'] {
41
+ display: grid;
42
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
43
+ gap: 0 var(--nymbal-spacing-md, 1rem);
44
+ }
45
+ `
46
+ ];
47
+ // The page leaves on success; a second submit while it does would try to register the email twice.
48
+ lockOnSuccess = true;
49
+ async send() {
50
+ if (this.value("password") !== this.value("confirmPassword")) {
51
+ throw new Error("Passwords do not match.");
52
+ }
53
+ await this.client.auth.register({
54
+ email: this.value("email"),
55
+ password: this.value("password"),
56
+ firstName: this.value("firstName"),
57
+ lastName: this.value("lastName"),
58
+ phone: this.value("phone")
59
+ });
60
+ this.emit("nymbal:auth:signed-in", { customer: this.client.auth.getState().customer });
61
+ }
62
+ failureText(err) {
63
+ return err instanceof Error && err.message !== "" ? err.message : "Registration failed. Please try again.";
64
+ }
65
+ render() {
66
+ return html`
67
+ ${this.renderError()}
68
+ <form part="form" data-testid="register-form" @submit=${this.onSubmit}>
69
+ <div part="row">
70
+ ${renderField({
71
+ id: "register-firstName",
72
+ name: "firstName",
73
+ label: "First Name",
74
+ autocomplete: "given-name",
75
+ placeholder: "First name"
76
+ })}
77
+ ${renderField({
78
+ id: "register-lastName",
79
+ name: "lastName",
80
+ label: "Last Name",
81
+ autocomplete: "family-name",
82
+ placeholder: "Last name"
83
+ })}
84
+ </div>
85
+ ${renderField({
86
+ id: "register-email",
87
+ name: "email",
88
+ label: "Email",
89
+ type: "email",
90
+ autocomplete: "email",
91
+ required: true,
92
+ placeholder: "you@example.com"
93
+ })}
94
+ ${renderField({
95
+ id: "register-password",
96
+ name: "password",
97
+ label: "Password",
98
+ type: "password",
99
+ autocomplete: "new-password",
100
+ required: true,
101
+ minlength: 8,
102
+ placeholder: "Choose a password"
103
+ })}
104
+ ${renderField({
105
+ id: "register-confirmPassword",
106
+ name: "confirmPassword",
107
+ label: "Confirm Password",
108
+ type: "password",
109
+ autocomplete: "new-password",
110
+ required: true,
111
+ placeholder: "Confirm your password"
112
+ })}
113
+ ${renderField({
114
+ id: "register-phone",
115
+ name: "phone",
116
+ label: "Phone (optional)",
117
+ type: "tel",
118
+ autocomplete: "tel",
119
+ placeholder: "+44 7700 900000"
120
+ })}
121
+ <button
122
+ type="submit"
123
+ part="submit"
124
+ class="btn btn-primary"
125
+ data-testid="register-submit"
126
+ ?disabled=${this.pending}
127
+ >
128
+ ${this.pending ? "Creating account..." : html`<slot name="submit-label">Create Account</slot>`}
129
+ </button>
130
+ </form>
131
+ `;
132
+ }
133
+ };
134
+
135
+ export {
136
+ NymbalRegisterForm
137
+ };
138
+ //# sourceMappingURL=chunk-NW47OJVN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/register-form.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalFormElement, renderField } from '../form-element.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-register-form',\n reactName: 'RegisterForm',\n attributes: [],\n events: [\n {\n name: 'nymbal:auth:signed-in',\n prop: 'onSignedIn',\n detail: '{ customer: unknown }',\n },\n ],\n parts: ['form', 'row', 'field', 'label', 'input', 'error', 'submit'],\n slots: ['submit-label'],\n children: false,\n}\n\n/** Creates the shopper's account and signs them in; the page decides where they go next. */\nexport class NymbalRegisterForm extends NymbalFormElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n [part~='row'] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0 var(--nymbal-spacing-md, 1rem);\n }\n `,\n ]\n\n // The page leaves on success; a second submit while it does would try to register the email twice.\n protected override readonly lockOnSuccess = true\n\n protected override async send(): Promise<void> {\n if (this.value('password') !== this.value('confirmPassword')) {\n throw new Error('Passwords do not match.')\n }\n await this.client.auth.register({\n email: this.value('email'),\n password: this.value('password'),\n firstName: this.value('firstName'),\n lastName: this.value('lastName'),\n phone: this.value('phone'),\n })\n this.emit('nymbal:auth:signed-in', { customer: this.client.auth.getState().customer })\n }\n\n protected override failureText(err: unknown): string {\n return err instanceof Error && err.message !== ''\n ? err.message\n : 'Registration failed. Please try again.'\n }\n\n protected override render() {\n return html`\n ${this.renderError()}\n <form part=\"form\" data-testid=\"register-form\" @submit=${this.onSubmit}>\n <div part=\"row\">\n ${renderField({\n id: 'register-firstName',\n name: 'firstName',\n label: 'First Name',\n autocomplete: 'given-name',\n placeholder: 'First name',\n })}\n ${renderField({\n id: 'register-lastName',\n name: 'lastName',\n label: 'Last Name',\n autocomplete: 'family-name',\n placeholder: 'Last name',\n })}\n </div>\n ${renderField({\n id: 'register-email',\n name: 'email',\n label: 'Email',\n type: 'email',\n autocomplete: 'email',\n required: true,\n placeholder: 'you@example.com',\n })}\n ${renderField({\n id: 'register-password',\n name: 'password',\n label: 'Password',\n type: 'password',\n autocomplete: 'new-password',\n required: true,\n minlength: 8,\n placeholder: 'Choose a password',\n })}\n ${renderField({\n id: 'register-confirmPassword',\n name: 'confirmPassword',\n label: 'Confirm Password',\n type: 'password',\n autocomplete: 'new-password',\n required: true,\n placeholder: 'Confirm your password',\n })}\n ${renderField({\n id: 'register-phone',\n name: 'phone',\n label: 'Phone (optional)',\n type: 'tel',\n autocomplete: 'tel',\n placeholder: '+44 7700 900000',\n })}\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"register-submit\"\n ?disabled=${this.pending}\n >\n ${this.pending ? 'Creating account...' : html`<slot name=\"submit-label\">Create Account</slot>`}\n </button>\n </form>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,KAAK,YAAY;AAK1B,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY,CAAC;AAAA,EACb,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,OAAO,CAAC,QAAQ,OAAO,SAAS,SAAS,SAAS,SAAS,QAAQ;AAAA,EACnE,OAAO,CAAC,cAAc;AAAA,EACtB,UAAU;AACZ;AAGO,IAAM,qBAAN,cAAiC,kBAAkB;AAAA,EACxD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOF;AAAA;AAAA,EAG4B,gBAAgB;AAAA,EAE5C,MAAyB,OAAsB;AAC7C,QAAI,KAAK,MAAM,UAAU,MAAM,KAAK,MAAM,iBAAiB,GAAG;AAC5D,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,KAAK,OAAO,KAAK,SAAS;AAAA,MAC9B,OAAO,KAAK,MAAM,OAAO;AAAA,MACzB,UAAU,KAAK,MAAM,UAAU;AAAA,MAC/B,WAAW,KAAK,MAAM,WAAW;AAAA,MACjC,UAAU,KAAK,MAAM,UAAU;AAAA,MAC/B,OAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,CAAC;AACD,SAAK,KAAK,yBAAyB,EAAE,UAAU,KAAK,OAAO,KAAK,SAAS,EAAE,SAAS,CAAC;AAAA,EACvF;AAAA,EAEmB,YAAY,KAAsB;AACnD,WAAO,eAAe,SAAS,IAAI,YAAY,KAC3C,IAAI,UACJ;AAAA,EACN;AAAA,EAEmB,SAAS;AAC1B,WAAO;AAAA,QACH,KAAK,YAAY,CAAC;AAAA,8DACoC,KAAK,QAAQ;AAAA;AAAA,YAE/D,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,YACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA;AAAA,UAEF,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAMY,KAAK,OAAO;AAAA;AAAA,YAEtB,KAAK,UAAU,wBAAwB,qDAAqD;AAAA;AAAA;AAAA;AAAA,EAItG;AACF;","names":[]}
@@ -0,0 +1,77 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalVariantElement
4
+ } from "./chunk-UYSZOEOA.js";
5
+ import {
6
+ baseStyles,
7
+ propertiesOf
8
+ } from "./chunk-EYDMJNNK.js";
9
+
10
+ // src/components/stock-badge.ts
11
+ import { css, html, nothing } from "lit";
12
+ import { STOCK_LABELS, stockLevel } from "@nymbalhq/types";
13
+ var SCHEMA = {
14
+ tag: "nymbal-stock-badge",
15
+ reactName: "StockBadge",
16
+ // The product's first variant's stock as a server read it, and the product whose store updates it may follow.
17
+ attributes: [
18
+ { name: "product-id", prop: "productId", type: "string" },
19
+ { name: "stock", prop: "stock", type: "number" }
20
+ ],
21
+ events: [],
22
+ parts: ["badge", "label"],
23
+ slots: [],
24
+ children: false
25
+ };
26
+ var NymbalStockBadge = class extends NymbalVariantElement {
27
+ static schema = SCHEMA;
28
+ static properties = propertiesOf(SCHEMA);
29
+ static styles = [
30
+ baseStyles,
31
+ css`
32
+ :host {
33
+ display: block;
34
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
35
+ }
36
+ [part~='badge'] {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ gap: var(--nymbal-spacing-xs, 0.25rem);
40
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
41
+ font-weight: var(--nymbal-font-weight-medium, 500);
42
+ }
43
+ [part~='badge'][data-level='in'] {
44
+ color: var(--nymbal-color-success, #16a34a);
45
+ }
46
+ [part~='badge'][data-level='low'] {
47
+ color: var(--nymbal-color-warning, #d97706);
48
+ }
49
+ [part~='badge'][data-level='out'] {
50
+ color: var(--nymbal-color-error, #dc2626);
51
+ }
52
+ .dot {
53
+ width: 8px;
54
+ height: 8px;
55
+ border-radius: var(--nymbal-radius-full, 9999px);
56
+ background-color: currentColor;
57
+ }
58
+ `
59
+ ];
60
+ render() {
61
+ const store = this.selected();
62
+ const stock = store === null ? this.stock : store.variant?.stock ?? 0;
63
+ if (stock === void 0 || Number.isNaN(stock)) return nothing;
64
+ const level = stockLevel(stock);
65
+ return html`
66
+ <span part="badge" data-testid="stock-badge" data-level=${level}>
67
+ <span class="dot" aria-hidden="true"></span>
68
+ <span part="label">${STOCK_LABELS[level]}</span>
69
+ </span>
70
+ `;
71
+ }
72
+ };
73
+
74
+ export {
75
+ NymbalStockBadge
76
+ };
77
+ //# sourceMappingURL=chunk-O2YKCDR2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/stock-badge.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { STOCK_LABELS, stockLevel } from '@nymbalhq/types'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalVariantElement } from '../selected-variant.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-stock-badge',\n reactName: 'StockBadge',\n // The product's first variant's stock as a server read it, and the product whose store updates it may follow.\n attributes: [\n { name: 'product-id', prop: 'productId', type: 'string' },\n { name: 'stock', prop: 'stock', type: 'number' },\n ],\n events: [],\n parts: ['badge', 'label'],\n slots: [],\n children: false,\n}\n\n/** A product page's stock level (`@nymbalhq/types`' rule): the selected variant's once the store holds this product. */\nexport class NymbalStockBadge extends NymbalVariantElement {\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~='badge'] {\n display: inline-flex;\n align-items: center;\n gap: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='badge'][data-level='in'] {\n color: var(--nymbal-color-success, #16a34a);\n }\n [part~='badge'][data-level='low'] {\n color: var(--nymbal-color-warning, #d97706);\n }\n [part~='badge'][data-level='out'] {\n color: var(--nymbal-color-error, #dc2626);\n }\n .dot {\n width: 8px;\n height: 8px;\n border-radius: var(--nymbal-radius-full, 9999px);\n background-color: currentColor;\n }\n `,\n ]\n\n declare stock: number | undefined\n\n protected override render() {\n const store = this.selected()\n // A product with no variants has nothing to sell, so it reads as out of stock.\n const stock = store === null ? this.stock : (store.variant?.stock ?? 0)\n if (stock === undefined || Number.isNaN(stock)) return nothing\n const level = stockLevel(stock)\n return html`\n <span part=\"badge\" data-testid=\"stock-badge\" data-level=${level}>\n <span class=\"dot\" aria-hidden=\"true\"></span>\n <span part=\"label\">${STOCK_LABELS[level]}</span>\n </span>\n `\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AACnC,SAAS,cAAc,kBAAkB;AAIzC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,IACV,EAAE,MAAM,cAAc,MAAM,aAAa,MAAM,SAAS;AAAA,IACxD,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,SAAS;AAAA,EACjD;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,SAAS,OAAO;AAAA,EACxB,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAGO,IAAM,mBAAN,cAA+B,qBAAqB;AAAA,EACzD,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,EA4BF;AAAA,EAImB,SAAS;AAC1B,UAAM,QAAQ,KAAK,SAAS;AAE5B,UAAM,QAAQ,UAAU,OAAO,KAAK,QAAS,MAAM,SAAS,SAAS;AACrE,QAAI,UAAU,UAAa,OAAO,MAAM,KAAK,EAAG,QAAO;AACvD,UAAM,QAAQ,WAAW,KAAK;AAC9B,WAAO;AAAA,gEACqD,KAAK;AAAA;AAAA,6BAExC,aAAa,KAAK,CAAC;AAAA;AAAA;AAAA,EAG9C;AACF;","names":[]}
@@ -0,0 +1,71 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalElement
4
+ } from "./chunk-EYDMJNNK.js";
5
+
6
+ // src/form-element.ts
7
+ import { html, nothing } from "lit";
8
+ function renderField(f) {
9
+ return html`
10
+ <div part="field">
11
+ <label part="label" for=${f.id}>${f.label}</label>
12
+ <input
13
+ part="input"
14
+ id=${f.id}
15
+ name=${f.name}
16
+ type=${f.type ?? "text"}
17
+ autocomplete=${f.autocomplete}
18
+ placeholder=${f.placeholder ?? nothing}
19
+ minlength=${f.minlength ?? nothing}
20
+ ?required=${f.required ?? false}
21
+ ?disabled=${f.disabled ?? false}
22
+ .value=${f.value ?? ""}
23
+ data-testid=${f.id}
24
+ />
25
+ </div>
26
+ `;
27
+ }
28
+ var NymbalFormElement = class extends NymbalElement {
29
+ pending = false;
30
+ error = null;
31
+ notice = null;
32
+ /** Keeps the submit disabled after a success, for a form whose page navigates away on it. */
33
+ lockOnSuccess = false;
34
+ /** The error text a refusal shows. */
35
+ failureText(err) {
36
+ return err instanceof Error && err.message !== "" ? err.message : "Something went wrong.";
37
+ }
38
+ onSubmit = (event) => {
39
+ event.preventDefault();
40
+ if (this.pending) return;
41
+ this.pending = true;
42
+ this.error = null;
43
+ this.notice = null;
44
+ const sent = this.send();
45
+ this.requestUpdate();
46
+ sent.then(
47
+ () => this.settle(null),
48
+ (err) => this.settle(this.failureText(err))
49
+ );
50
+ };
51
+ settle(error) {
52
+ this.pending = error === null && this.lockOnSuccess;
53
+ this.error = error;
54
+ this.requestUpdate();
55
+ }
56
+ value(name) {
57
+ return this.renderRoot.querySelector(`[name="${name}"]`)?.value ?? "";
58
+ }
59
+ emit(name, detail) {
60
+ this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }));
61
+ }
62
+ renderError() {
63
+ return this.error === null ? nothing : html`<div part="error" role="alert">${this.error}</div>`;
64
+ }
65
+ };
66
+
67
+ export {
68
+ renderField,
69
+ NymbalFormElement
70
+ };
71
+ //# sourceMappingURL=chunk-PN6QKYE4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/form-element.ts"],"sourcesContent":["import { html, nothing, type TemplateResult } from 'lit'\nimport { NymbalElement } from './base-element.js'\n\nexport interface FieldSpec {\n /** The input's id and test id, unique within the element's shadow root. */\n id: string\n name: string\n label: string\n type?: 'text' | 'email' | 'password' | 'tel'\n autocomplete: string\n required?: boolean\n disabled?: boolean\n minlength?: number\n placeholder?: string\n /** Set on the input when it changes, so typing is kept across renders. */\n value?: string\n}\n\n/** A labelled input; the label's `for` and the input share the element's shadow root. */\nexport function renderField(f: FieldSpec): TemplateResult {\n return html`\n <div part=\"field\">\n <label part=\"label\" for=${f.id}>${f.label}</label>\n <input\n part=\"input\"\n id=${f.id}\n name=${f.name}\n type=${f.type ?? 'text'}\n autocomplete=${f.autocomplete}\n placeholder=${f.placeholder ?? nothing}\n minlength=${f.minlength ?? nothing}\n ?required=${f.required ?? false}\n ?disabled=${f.disabled ?? false}\n .value=${f.value ?? ''}\n data-testid=${f.id}\n />\n </div>\n `\n}\n\n/**\n * A view whose form makes one SDK call per submission. `requestSubmit()` and Enter still fire `submit` while the\n * button is disabled, so the pending flag, not the button, is the guard.\n */\nexport abstract class NymbalFormElement extends NymbalElement {\n protected pending = false\n protected error: string | null = null\n protected notice: string | null = null\n /** Keeps the submit disabled after a success, for a form whose page navigates away on it. */\n protected readonly lockOnSuccess: boolean = false\n\n /** Reads the fields and makes the call; it must call the SDK before its first `await`. */\n protected abstract send(): Promise<void>\n\n /** The error text a refusal shows. */\n protected failureText(err: unknown): string {\n return err instanceof Error && err.message !== '' ? err.message : 'Something went wrong.'\n }\n\n protected readonly onSubmit = (event: Event): void => {\n event.preventDefault()\n if (this.pending) return\n this.pending = true\n this.error = null\n this.notice = null\n const sent = this.send()\n this.requestUpdate()\n sent.then(\n () => this.settle(null),\n (err: unknown) => this.settle(this.failureText(err)),\n )\n }\n\n private settle(error: string | null): void {\n this.pending = error === null && this.lockOnSuccess\n this.error = error\n this.requestUpdate()\n }\n\n protected value(name: string): string {\n return this.renderRoot.querySelector<HTMLInputElement>(`[name=\"${name}\"]`)?.value ?? ''\n }\n\n protected emit(name: string, detail: unknown): void {\n this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }))\n }\n\n protected renderError(): TemplateResult | typeof nothing {\n return this.error === null\n ? nothing\n : html`<div part=\"error\" role=\"alert\">${this.error}</div>`\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,MAAM,eAAoC;AAmB5C,SAAS,YAAY,GAA8B;AACxD,SAAO;AAAA;AAAA,gCAEuB,EAAE,EAAE,IAAI,EAAE,KAAK;AAAA;AAAA;AAAA,aAGlC,EAAE,EAAE;AAAA,eACF,EAAE,IAAI;AAAA,eACN,EAAE,QAAQ,MAAM;AAAA,uBACR,EAAE,YAAY;AAAA,sBACf,EAAE,eAAe,OAAO;AAAA,oBAC1B,EAAE,aAAa,OAAO;AAAA,oBACtB,EAAE,YAAY,KAAK;AAAA,oBACnB,EAAE,YAAY,KAAK;AAAA,iBACtB,EAAE,SAAS,EAAE;AAAA,sBACR,EAAE,EAAE;AAAA;AAAA;AAAA;AAI1B;AAMO,IAAe,oBAAf,cAAyC,cAAc;AAAA,EAClD,UAAU;AAAA,EACV,QAAuB;AAAA,EACvB,SAAwB;AAAA;AAAA,EAEf,gBAAyB;AAAA;AAAA,EAMlC,YAAY,KAAsB;AAC1C,WAAO,eAAe,SAAS,IAAI,YAAY,KAAK,IAAI,UAAU;AAAA,EACpE;AAAA,EAEmB,WAAW,CAAC,UAAuB;AACpD,UAAM,eAAe;AACrB,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,UAAM,OAAO,KAAK,KAAK;AACvB,SAAK,cAAc;AACnB,SAAK;AAAA,MACH,MAAM,KAAK,OAAO,IAAI;AAAA,MACtB,CAAC,QAAiB,KAAK,OAAO,KAAK,YAAY,GAAG,CAAC;AAAA,IACrD;AAAA,EACF;AAAA,EAEQ,OAAO,OAA4B;AACzC,SAAK,UAAU,UAAU,QAAQ,KAAK;AACtC,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA,EAEU,MAAM,MAAsB;AACpC,WAAO,KAAK,WAAW,cAAgC,UAAU,IAAI,IAAI,GAAG,SAAS;AAAA,EACvF;AAAA,EAEU,KAAK,MAAc,QAAuB;AAClD,SAAK,cAAc,IAAI,YAAY,MAAM,EAAE,SAAS,MAAM,UAAU,MAAM,OAAO,CAAC,CAAC;AAAA,EACrF;AAAA,EAEU,cAA+C;AACvD,WAAO,KAAK,UAAU,OAClB,UACA,sCAAsC,KAAK,KAAK;AAAA,EACtD;AACF;","names":[]}
@@ -0,0 +1,188 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+ import {
3
+ NymbalProductCard
4
+ } from "./chunk-RZVFA3N5.js";
5
+ import {
6
+ buttonStyles
7
+ } from "./chunk-GHXTJ2CF.js";
8
+ import {
9
+ NymbalElement,
10
+ baseStyles,
11
+ parsedJson,
12
+ propertiesOf
13
+ } from "./chunk-EYDMJNNK.js";
14
+
15
+ // src/components/search-results.ts
16
+ import { css, html, nothing } from "lit";
17
+ var SCHEMA = {
18
+ tag: "nymbal-search-results",
19
+ reactName: "SearchResults",
20
+ // The search a server ran: the store's `results` (the card products) and the term they answer, shown until the
21
+ // search store settles.
22
+ attributes: [
23
+ { name: "results", prop: "results", type: "json" },
24
+ { name: "query", prop: "query", type: "string" }
25
+ ],
26
+ events: [],
27
+ parts: [
28
+ "query",
29
+ "loading",
30
+ "empty",
31
+ "empty-icon",
32
+ "empty-title",
33
+ "empty-text",
34
+ "header",
35
+ "count",
36
+ "grid",
37
+ "pagination",
38
+ "load-more"
39
+ ],
40
+ slots: ["no-term", "no-results"],
41
+ children: false
42
+ };
43
+ var NymbalSearchResults = class extends NymbalElement {
44
+ static schema = SCHEMA;
45
+ static uses = [NymbalProductCard];
46
+ static properties = propertiesOf(SCHEMA);
47
+ static styles = [
48
+ baseStyles,
49
+ buttonStyles,
50
+ css`
51
+ :host {
52
+ display: block;
53
+ font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
54
+ color: var(--nymbal-color-text, #18181b);
55
+ }
56
+ [part~='query'] {
57
+ margin: 0 0 var(--nymbal-spacing-md, 1rem);
58
+ color: var(--nymbal-color-text-muted, #71717a);
59
+ }
60
+ [part~='loading'] {
61
+ padding: var(--nymbal-spacing-xl, 2rem) 0;
62
+ text-align: center;
63
+ color: var(--nymbal-color-text-muted, #71717a);
64
+ }
65
+ [part~='empty'] {
66
+ padding: 4rem var(--nymbal-spacing-md, 1rem);
67
+ text-align: center;
68
+ }
69
+ [part~='empty-icon'] {
70
+ font-size: 3rem;
71
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
72
+ opacity: 0.3;
73
+ }
74
+ [part~='empty-title'] {
75
+ margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);
76
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
77
+ font-weight: var(--nymbal-font-weight-medium, 500);
78
+ }
79
+ [part~='empty-text'] {
80
+ max-width: 28ch;
81
+ margin: 0 auto;
82
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
83
+ color: var(--nymbal-color-text-muted, #71717a);
84
+ }
85
+ [part~='header'] {
86
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
87
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
88
+ color: var(--nymbal-color-text-muted, #71717a);
89
+ }
90
+ [part~='grid'] {
91
+ display: grid;
92
+ grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
93
+ gap: var(--nymbal-spacing-lg, 1.5rem);
94
+ }
95
+ [part~='pagination'] {
96
+ display: flex;
97
+ justify-content: center;
98
+ margin-top: var(--nymbal-spacing-xl, 2rem);
99
+ }
100
+ `
101
+ ];
102
+ stores() {
103
+ return [this.client.search];
104
+ }
105
+ storeSettled() {
106
+ this.results = void 0;
107
+ }
108
+ /**
109
+ * The results a server gave while they stand (`given`), else the search store's; with neither, no search. Given
110
+ * results are shown whether or not the server named their term.
111
+ */
112
+ shown() {
113
+ const given = parsedJson(this, "results", this.results);
114
+ if (given === void 0 && this.readsStores) {
115
+ return { ...this.client.search.getState(), given: false };
116
+ }
117
+ const results = given ?? [];
118
+ const query = this.query ?? "";
119
+ return { query, results, total: results.length, nextCursor: null, loading: false, given: true };
120
+ }
121
+ empty(title, text, slot) {
122
+ return html`
123
+ <div part="empty">
124
+ <div part="empty-icon" aria-hidden="true">&#128269;</div>
125
+ <slot name=${slot}>
126
+ <h2 part="empty-title">${title}</h2>
127
+ <p part="empty-text">${text}</p>
128
+ </slot>
129
+ </div>
130
+ `;
131
+ }
132
+ body(state) {
133
+ const { results, query } = state;
134
+ if (state.loading && results.length === 0) {
135
+ return html`<div part="loading" role="status">Searching...</div>`;
136
+ }
137
+ if (query === "" && !(state.given && results.length > 0)) {
138
+ return this.empty("Search our store", "Type a keyword to find products.", "no-term");
139
+ }
140
+ if (results.length === 0) {
141
+ return this.empty(
142
+ "No results found",
143
+ "Try a different search term or browse our categories.",
144
+ "no-results"
145
+ );
146
+ }
147
+ const total = state.total;
148
+ return html`
149
+ <div part="header">
150
+ <span part="count" data-testid="search-total"
151
+ >${total} result${total === 1 ? "" : "s"}</span
152
+ >
153
+ </div>
154
+ <div part="grid">
155
+ ${results.map(
156
+ (p) => html`<nymbal-product-card product=${JSON.stringify(p)}></nymbal-product-card>`
157
+ )}
158
+ </div>
159
+ ${state.nextCursor === null ? nothing : html`
160
+ <div part="pagination">
161
+ <button
162
+ type="button"
163
+ part="load-more"
164
+ class="btn btn-secondary"
165
+ data-testid="search-load-more"
166
+ ?disabled=${state.loading}
167
+ @click=${() => void this.client.search.loadMore()}
168
+ >
169
+ ${state.loading ? "Loading..." : "Load more"}
170
+ </button>
171
+ </div>
172
+ `}
173
+ `;
174
+ }
175
+ render() {
176
+ const state = this.shown();
177
+ const { query } = state;
178
+ return html`
179
+ ${query === "" ? nothing : html`<p part="query">Results for <strong>"${query}"</strong></p>`}
180
+ ${this.body(state)}
181
+ `;
182
+ }
183
+ };
184
+
185
+ export {
186
+ NymbalSearchResults
187
+ };
188
+ //# sourceMappingURL=chunk-RAOGZH3Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/search-results.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport type { SearchState } from '@nymbalhq/sdk'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\nimport { buttonStyles } from '../view-styles.js'\nimport { NymbalProductCard } from './product-card.js'\n\ntype Shown = Pick<SearchState, 'query' | 'results' | 'total' | 'nextCursor' | 'loading'> & {\n given: boolean\n}\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-search-results',\n reactName: 'SearchResults',\n // The search a server ran: the store's `results` (the card products) and the term they answer, shown until the\n // search store settles.\n attributes: [\n { name: 'results', prop: 'results', type: 'json' },\n { name: 'query', prop: 'query', type: 'string' },\n ],\n events: [],\n parts: [\n 'query',\n 'loading',\n 'empty',\n 'empty-icon',\n 'empty-title',\n 'empty-text',\n 'header',\n 'count',\n 'grid',\n 'pagination',\n 'load-more',\n ],\n slots: ['no-term', 'no-results'],\n children: false,\n}\n\n/**\n * The search store's results: the term, a grid of `nymbal-product-card`s, the no-term and no-results states, and load\n * more. The page owns the term and the category (the URL and the search input) and searches; this renders the answer.\n */\nexport class NymbalSearchResults extends NymbalElement {\n static override schema = SCHEMA\n static uses = [NymbalProductCard]\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n color: var(--nymbal-color-text, #18181b);\n }\n [part~='query'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='loading'] {\n padding: var(--nymbal-spacing-xl, 2rem) 0;\n text-align: center;\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='empty'] {\n padding: 4rem var(--nymbal-spacing-md, 1rem);\n text-align: center;\n }\n [part~='empty-icon'] {\n font-size: 3rem;\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n opacity: 0.3;\n }\n [part~='empty-title'] {\n margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='empty-text'] {\n max-width: 28ch;\n margin: 0 auto;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='header'] {\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='grid'] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));\n gap: var(--nymbal-spacing-lg, 1.5rem);\n }\n [part~='pagination'] {\n display: flex;\n justify-content: center;\n margin-top: var(--nymbal-spacing-xl, 2rem);\n }\n `,\n ]\n\n declare results: unknown\n declare query: string | undefined\n\n protected override stores() {\n return [this.client.search]\n }\n\n protected override storeSettled(): void {\n this.results = undefined\n }\n\n /**\n * The results a server gave while they stand (`given`), else the search store's; with neither, no search. Given\n * results are shown whether or not the server named their term.\n */\n private shown(): Shown {\n const given = parsedJson<SearchState['results']>(this, 'results', this.results)\n if (given === undefined && this.readsStores) {\n return { ...this.client.search.getState(), given: false }\n }\n const results = given ?? []\n const query = this.query ?? ''\n return { query, results, total: results.length, nextCursor: null, loading: false, given: true }\n }\n\n private empty(title: string, text: string, slot: string) {\n return html`\n <div part=\"empty\">\n <div part=\"empty-icon\" aria-hidden=\"true\">&#128269;</div>\n <slot name=${slot}>\n <h2 part=\"empty-title\">${title}</h2>\n <p part=\"empty-text\">${text}</p>\n </slot>\n </div>\n `\n }\n\n private body(state: Shown) {\n const { results, query } = state\n // A load-more or a category change keeps the results shown until its answer replaces them.\n if (state.loading && results.length === 0) {\n return html`<div part=\"loading\" role=\"status\">Searching...</div>`\n }\n if (query === '' && !(state.given && results.length > 0)) {\n return this.empty('Search our store', 'Type a keyword to find products.', 'no-term')\n }\n if (results.length === 0) {\n return this.empty(\n 'No results found',\n 'Try a different search term or browse our categories.',\n 'no-results',\n )\n }\n const total = state.total\n return html`\n <div part=\"header\">\n <span part=\"count\" data-testid=\"search-total\"\n >${total} result${total === 1 ? '' : 's'}</span\n >\n </div>\n <div part=\"grid\">\n ${results.map(\n (p) => html`<nymbal-product-card product=${JSON.stringify(p)}></nymbal-product-card>`,\n )}\n </div>\n ${state.nextCursor === null\n ? nothing\n : html`\n <div part=\"pagination\">\n <button\n type=\"button\"\n part=\"load-more\"\n class=\"btn btn-secondary\"\n data-testid=\"search-load-more\"\n ?disabled=${state.loading}\n @click=${() => void this.client.search.loadMore()}\n >\n ${state.loading ? 'Loading...' : 'Load more'}\n </button>\n </div>\n `}\n `\n }\n\n protected override render() {\n const state = this.shown()\n const { query } = state\n return html`\n ${query === '' ? nothing : html`<p part=\"query\">Results for <strong>\"${query}\"</strong></p>`}\n ${this.body(state)}\n `\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
2
+
3
+ // src/format.ts
4
+ function formatPrice(minor, currency) {
5
+ return new Intl.NumberFormat("en-GB", { style: "currency", currency }).format(minor / 100);
6
+ }
7
+
8
+ export {
9
+ formatPrice
10
+ };
11
+ //# sourceMappingURL=chunk-RVSKX2RX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/format.ts"],"sourcesContent":["/** Minor units as the storefronts show money (`en-GB`, the templates' own formatting). */\nexport function formatPrice(minor: number, currency: string): string {\n return new Intl.NumberFormat('en-GB', { style: 'currency', currency }).format(minor / 100)\n}\n"],"mappings":";;;AACO,SAAS,YAAY,OAAe,UAA0B;AACnE,SAAO,IAAI,KAAK,aAAa,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,QAAQ,GAAG;AAC3F;","names":[]}