@zappie/foundations 0.1.1 → 0.1.2

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 (72) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/account/index.js +49 -0
  3. package/dist/account/index.js.map +7 -0
  4. package/dist/cart/index.js +168 -0
  5. package/dist/cart/index.js.map +7 -0
  6. package/dist/fulfilment/address-picker.js +168 -0
  7. package/dist/fulfilment/address-picker.js.map +7 -0
  8. package/dist/fulfilment/branch-picker.js +54 -0
  9. package/dist/fulfilment/branch-picker.js.map +7 -0
  10. package/dist/fulfilment/schedule.js +146 -0
  11. package/dist/fulfilment/schedule.js.map +7 -0
  12. package/dist/fulfilment/switch.js +54 -0
  13. package/dist/fulfilment/switch.js.map +7 -0
  14. package/dist/index.js +48 -2114
  15. package/dist/index.js.map +7 -1
  16. package/dist/internal/combobox.js +131 -0
  17. package/dist/internal/combobox.js.map +7 -0
  18. package/dist/internal/data-attrs.js +8 -0
  19. package/dist/internal/data-attrs.js.map +7 -0
  20. package/dist/internal/ids.js +10 -0
  21. package/dist/internal/ids.js.map +7 -0
  22. package/dist/{chunk-D3J7S33L.js → internal/slot.js} +13 -6
  23. package/dist/internal/slot.js.map +7 -0
  24. package/dist/internal/use-controllable-state.js +29 -0
  25. package/dist/internal/use-controllable-state.js.map +7 -0
  26. package/dist/item-form/index.js +13 -0
  27. package/dist/item-form/index.js.map +7 -0
  28. package/dist/item-form/option-group.js +268 -0
  29. package/dist/item-form/option-group.js.map +7 -0
  30. package/dist/item-form/options.js +30 -0
  31. package/dist/item-form/options.js.map +7 -0
  32. package/dist/item-form/quantity.js +131 -0
  33. package/dist/item-form/quantity.js.map +7 -0
  34. package/dist/item-form/root.js +151 -0
  35. package/dist/item-form/root.js.map +7 -0
  36. package/dist/media/image.js +30 -0
  37. package/dist/media/image.js.map +7 -0
  38. package/dist/money.js +20 -0
  39. package/dist/money.js.map +7 -0
  40. package/dist/next.js +8 -6
  41. package/dist/next.js.map +7 -1
  42. package/dist/otp/input.js +110 -0
  43. package/dist/otp/input.js.map +7 -0
  44. package/dist/overlay/engine.js +16 -8
  45. package/dist/overlay/engine.js.map +7 -1
  46. package/dist/{chunk-BY2WQGLO.js → overlay/history.js} +9 -4
  47. package/dist/overlay/history.js.map +7 -0
  48. package/dist/overlay/react.js +261 -0
  49. package/dist/overlay/react.js.map +7 -0
  50. package/dist/payment/methods.js +103 -0
  51. package/dist/payment/methods.js.map +7 -0
  52. package/dist/rich-text/banners.js +9 -0
  53. package/dist/rich-text/banners.js.map +7 -0
  54. package/dist/rich-text/browser.js +3 -3
  55. package/dist/rich-text/browser.js.map +7 -1
  56. package/dist/rich-text/core.js +75 -0
  57. package/dist/rich-text/core.js.map +7 -0
  58. package/dist/rich-text/index.js +3 -79
  59. package/dist/rich-text/index.js.map +7 -1
  60. package/dist/search/engine.js +93 -0
  61. package/dist/search/engine.js.map +7 -0
  62. package/dist/search/index.js +175 -0
  63. package/dist/search/index.js.map +7 -0
  64. package/dist/status/hours.js +177 -0
  65. package/dist/status/hours.js.map +7 -0
  66. package/dist/status/open-status.js +45 -0
  67. package/dist/status/open-status.js.map +7 -0
  68. package/dist/version.js +6 -0
  69. package/dist/version.js.map +7 -0
  70. package/package.json +3 -3
  71. package/dist/chunk-BY2WQGLO.js.map +0 -1
  72. package/dist/chunk-D3J7S33L.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @zappie/foundations
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d3c5f98: Keep each runtime module separate so your app can split package code across routes. Check and verify accept compatible package updates without pinning you to one patch version.
8
+ - Updated dependencies [d3c5f98]
9
+ - @zappie/sdk@0.1.2
10
+
3
11
  ## 0.1.1
4
12
 
5
13
  ### Patch Changes
@@ -0,0 +1,49 @@
1
+ // src/account/index.tsx
2
+ import { forwardRef } from "react";
3
+ import { part } from "../internal/slot.js";
4
+ var AccountNotConfiguredError = class extends Error {
5
+ constructor() {
6
+ super("Account authentication requires SDK auth (Open Storefronts PRD 13)");
7
+ this.code = "not_configured";
8
+ this.name = "AccountNotConfiguredError";
9
+ }
10
+ };
11
+ async function signIn(_options) {
12
+ throw new AccountNotConfiguredError();
13
+ }
14
+ async function signOut(_options) {
15
+ throw new AccountNotConfiguredError();
16
+ }
17
+ var SignIn = forwardRef(function SignIn2(props, ref) {
18
+ return part(
19
+ "div",
20
+ props,
21
+ { signIn, status: "not_configured" },
22
+ { "data-state": "invalid" },
23
+ ref
24
+ );
25
+ });
26
+ var SignOut = forwardRef(function SignOut2(props, ref) {
27
+ return part(
28
+ "div",
29
+ props,
30
+ { signOut, status: "not_configured" },
31
+ { "data-state": "invalid" },
32
+ ref
33
+ );
34
+ });
35
+ var CustomerPart = forwardRef(function Customer(props, ref) {
36
+ return part(
37
+ "div",
38
+ props,
39
+ { customer: null, status: "not_configured" },
40
+ { "data-state": "invalid" },
41
+ ref
42
+ );
43
+ });
44
+ var Account = { SignIn, SignOut, Customer: CustomerPart };
45
+ export {
46
+ Account,
47
+ AccountNotConfiguredError
48
+ };
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/account/index.tsx"],
4
+ "sourcesContent": ["import { forwardRef } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\n/** Account stubs that do not read credentials or host authentication. */\nexport type Customer = { id: string; givenName?: string; phoneMasked: string };\n/**\n * Sign-in options reserved by the account contract.\n * The current Account sign-in stub rejects with AccountNotConfiguredError.\n */\nexport type SignInOptions = {\n returnTo?: string;\n prompt?: \"none\" | \"login\";\n display?: \"popup\" | \"redirect\" | \"auto\";\n scopes?: (\n | \"openid\"\n | \"phone\"\n | \"profile\"\n | \"addresses\"\n | \"orders:read\"\n | \"offline_access\"\n | \"cart\"\n )[];\n};\n/**\n * An error from Account sign-in and sign-out when authentication is not configured.\n */\nexport class AccountNotConfiguredError extends Error {\n readonly code = \"not_configured\";\n constructor() {\n super(\"Account authentication requires SDK auth (Open Storefronts PRD 13)\");\n this.name = \"AccountNotConfiguredError\";\n }\n}\nasync function signIn(_options?: SignInOptions): Promise<void> {\n throw new AccountNotConfiguredError();\n}\nasync function signOut(_options?: { everywhere?: boolean }): Promise<void> {\n throw new AccountNotConfiguredError();\n}\nconst SignIn = forwardRef<\n HTMLElement,\n PartProps<{ signIn: typeof signIn; status: \"not_configured\" }>\n>(function SignIn(props, ref) {\n return part(\n \"div\",\n props,\n { signIn, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\nconst SignOut = forwardRef<\n HTMLElement,\n PartProps<{ signOut: typeof signOut; status: \"not_configured\" }>\n>(function SignOut(props, ref) {\n return part(\n \"div\",\n props,\n { signOut, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\nconst CustomerPart = forwardRef<\n HTMLElement,\n PartProps<{ customer: Customer | null; status: \"not_configured\" }>\n>(function Customer(props, ref) {\n return part(\n \"div\",\n props,\n { customer: null, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\n/**\n * Account UI stubs that expose a not_configured status and a null customer.\n * SignIn and SignOut reject with AccountNotConfiguredError. They do not authenticate.\n */\nexport const Account = { SignIn, SignOut, Customer: CustomerPart };\n"],
5
+ "mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AAwB9B,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAEnD,cAAc;AACZ,UAAM,oEAAoE;AAF5E,SAAS,OAAO;AAGd,SAAK,OAAO;AAAA,EACd;AACF;AACA,eAAe,OAAO,UAAyC;AAC7D,QAAM,IAAI,0BAA0B;AACtC;AACA,eAAe,QAAQ,UAAoD;AACzE,QAAM,IAAI,0BAA0B;AACtC;AACA,IAAM,SAAS,WAGb,SAASA,QAAO,OAAO,KAAK;AAC5B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,QAAQ,iBAAiB;AAAA,IACnC,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AACD,IAAM,UAAU,WAGd,SAASC,SAAQ,OAAO,KAAK;AAC7B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,SAAS,QAAQ,iBAAiB;AAAA,IACpC,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AACD,IAAM,eAAe,WAGnB,SAAS,SAAS,OAAO,KAAK;AAC9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,UAAU,MAAM,QAAQ,iBAAiB;AAAA,IAC3C,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AAKM,IAAM,UAAU,EAAE,QAAQ,SAAS,UAAU,aAAa;",
6
+ "names": ["SignIn", "SignOut"]
7
+ }
@@ -0,0 +1,168 @@
1
+ "use client";
2
+
3
+ // src/cart/index.tsx
4
+ import { useCart, useCartCatalogue, useZappie } from "@zappie/sdk/react";
5
+ import {
6
+ createContext,
7
+ forwardRef,
8
+ useContext,
9
+ useEffect,
10
+ useState
11
+ } from "react";
12
+ import { part } from "../internal/slot.js";
13
+ import { Quantity } from "../item-form/quantity.js";
14
+ import { jsx } from "react/jsx-runtime";
15
+ var Context = createContext(null);
16
+ function useContextValue() {
17
+ const value = useContext(Context);
18
+ if (!value) {
19
+ throw new Error("Cart parts require Cart.Root");
20
+ }
21
+ return value;
22
+ }
23
+ function Root(props) {
24
+ const state = useCart();
25
+ return /* @__PURE__ */ jsx(Context.Provider, { value: state, children: part("div", props, state, {
26
+ "data-state": !state.isHydrated ? "loading" : state.issues.some((issue) => issue.severity === "blocking") ? "invalid" : "valid"
27
+ }) });
28
+ }
29
+ var Line = forwardRef(function Line2({ line, ...props }, ref) {
30
+ const cart = useContextValue();
31
+ const item = useCartCatalogue()?.items[line.itemId];
32
+ const current = cart.lines.find((candidate) => candidate.id === line.id);
33
+ if (!current) {
34
+ return null;
35
+ }
36
+ const context = {
37
+ line: current,
38
+ item,
39
+ edit: (patch) => cart.actions.update(line.id, patch),
40
+ remove: () => cart.actions.remove(line.id)
41
+ };
42
+ return part(
43
+ "div",
44
+ props,
45
+ context,
46
+ {
47
+ "data-zappie-cart-line": line.id,
48
+ "data-invalid": cart.issues.some(
49
+ (issue) => issue.lineId === line.id && issue.severity === "blocking"
50
+ ) ? "" : void 0
51
+ },
52
+ ref
53
+ );
54
+ });
55
+ function CartQuantity({
56
+ lineId,
57
+ ...props
58
+ }) {
59
+ const cart = useContextValue();
60
+ const line = cart.lines.find((candidate) => candidate.id === lineId);
61
+ const item = useCartCatalogue()?.items[line?.itemId ?? ""];
62
+ if (!line) {
63
+ return null;
64
+ }
65
+ const step = item?.quantity.step ?? 1;
66
+ const min = Math.ceil((item?.quantity.min ?? 1) / step) * step;
67
+ return /* @__PURE__ */ jsx(
68
+ Quantity.Root,
69
+ {
70
+ ...props,
71
+ value: line.quantity,
72
+ min,
73
+ max: Math.min(item?.quantity.max ?? 99, 99),
74
+ step,
75
+ onValueChange: (quantity) => cart.actions.update(lineId, { quantity })
76
+ }
77
+ );
78
+ }
79
+ var Remove = forwardRef(
80
+ function Remove2({ lineId, ...props }, ref) {
81
+ const cart = useContextValue();
82
+ return part(
83
+ "button",
84
+ props,
85
+ {},
86
+ { type: "button", onClick: () => cart.actions.remove(lineId) },
87
+ ref
88
+ );
89
+ }
90
+ );
91
+ var Empty = forwardRef(function Empty2(props, ref) {
92
+ const cart = useContextValue();
93
+ return cart.isHydrated && !cart.lines.length ? part("div", props, {}, {}, ref) : null;
94
+ });
95
+ var Issues = forwardRef(function Issues2(props, ref) {
96
+ const cart = useContextValue();
97
+ return part(
98
+ "div",
99
+ props,
100
+ { issues: cart.issues, applyFix: cart.actions.applyFix },
101
+ {
102
+ "aria-live": "polite",
103
+ "data-state": cart.issues.length ? "invalid" : "valid"
104
+ },
105
+ ref
106
+ );
107
+ });
108
+ var Cart = {
109
+ Root,
110
+ Line,
111
+ Quantity: CartQuantity,
112
+ Remove,
113
+ Empty,
114
+ Issues
115
+ };
116
+ var Totals = forwardRef(
117
+ function Totals2(props, ref) {
118
+ const cart = useCart();
119
+ const client = useZappie();
120
+ const [failure, setFailure] = useState(null);
121
+ const [expired, setExpired] = useState(false);
122
+ const quote = "quote" in cart.state ? cart.state.quote : void 0;
123
+ useEffect(
124
+ () => client.on(
125
+ "quote_failed",
126
+ ({ code }) => setFailure(
127
+ code === "network_error" || code === "timeout" ? "offline" : "stale"
128
+ )
129
+ ),
130
+ [client]
131
+ );
132
+ useEffect(() => {
133
+ if (cart.state.status === "quoting" || quote) {
134
+ setFailure(null);
135
+ }
136
+ setExpired(Boolean(quote && Date.parse(quote.expiresAt) <= Date.now()));
137
+ if (!quote) {
138
+ return;
139
+ }
140
+ const timeout = setTimeout(
141
+ () => setExpired(true),
142
+ Math.max(
143
+ 0,
144
+ Math.min(2147483647, Date.parse(quote.expiresAt) - Date.now())
145
+ )
146
+ );
147
+ return () => clearTimeout(timeout);
148
+ }, [quote, cart.state.status]);
149
+ const pending = cart.state.status === "ready" && !quote && cart.lines.length > 0;
150
+ const status = failure === "offline" ? "offline" : cart.state.status === "quoting" || !cart.isHydrated ? "loading" : failure === "stale" || expired ? "stale" : pending ? "loading" : !quote ? "stale" : "ready";
151
+ return part(
152
+ "div",
153
+ props,
154
+ { quote, status },
155
+ {
156
+ "aria-live": "polite",
157
+ "aria-busy": status === "loading",
158
+ "data-state": status === "ready" ? "valid" : status === "offline" ? "stale" : status
159
+ },
160
+ ref
161
+ );
162
+ }
163
+ );
164
+ export {
165
+ Cart,
166
+ Totals
167
+ };
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/cart/index.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport type {\n CartFix,\n CartIssue,\n CartLine,\n CartLineInput,\n Quote,\n} from \"@zappie/sdk\";\nimport { useCart, useCartCatalogue, useZappie } from \"@zappie/sdk/react\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { Quantity, type QuantityProps } from \"../item-form/quantity\";\ntype CartContext = ReturnType<typeof useCart>;\nconst Context = createContext<CartContext | null>(null);\nfunction useContextValue() {\n const value = useContext(Context);\n if (!value) {\n throw new Error(\"Cart parts require Cart.Root\");\n }\n return value;\n}\nfunction Root(props: PartProps<CartContext>) {\n const state = useCart();\n return (\n <Context.Provider value={state}>\n {part(\"div\", props, state, {\n \"data-state\": !state.isHydrated\n ? \"loading\"\n : state.issues.some((issue) => issue.severity === \"blocking\")\n ? \"invalid\"\n : \"valid\",\n })}\n </Context.Provider>\n );\n}\ntype LineContext = {\n line: CartLine;\n item: import(\"@zappie/sdk\").Item | undefined;\n edit: (patch: Partial<Omit<CartLineInput, \"itemId\">>) => CartLine;\n remove: () => void;\n};\nconst Line = forwardRef<\n HTMLElement,\n PartProps<LineContext> & { line: CartLine }\n>(function Line({ line, ...props }, ref) {\n const cart = useContextValue();\n const item = useCartCatalogue()?.items[line.itemId];\n const current = cart.lines.find((candidate) => candidate.id === line.id);\n if (!current) {\n return null;\n }\n const context = {\n line: current,\n item,\n edit: (patch: Partial<Omit<CartLineInput, \"itemId\">>) =>\n cart.actions.update(line.id, patch),\n remove: () => cart.actions.remove(line.id),\n };\n return part(\n \"div\",\n props,\n context,\n {\n \"data-zappie-cart-line\": line.id,\n \"data-invalid\": cart.issues.some(\n (issue) => issue.lineId === line.id && issue.severity === \"blocking\",\n )\n ? \"\"\n : undefined,\n },\n ref,\n );\n});\nfunction CartQuantity({\n lineId,\n ...props\n}: Omit<QuantityProps, \"value\" | \"defaultValue\" | \"onValueChange\"> & {\n lineId: string;\n}) {\n const cart = useContextValue();\n const line = cart.lines.find((candidate) => candidate.id === lineId);\n const item = useCartCatalogue()?.items[line?.itemId ?? \"\"];\n if (!line) {\n return null;\n }\n const step = item?.quantity.step ?? 1;\n const min = Math.ceil((item?.quantity.min ?? 1) / step) * step;\n return (\n <Quantity.Root\n {...props}\n value={line.quantity}\n min={min}\n max={Math.min(item?.quantity.max ?? 99, 99)}\n step={step}\n onValueChange={(quantity) => cart.actions.update(lineId, { quantity })}\n />\n );\n}\nconst Remove = forwardRef<HTMLElement, PartProps & { lineId: string }>(\n function Remove({ lineId, ...props }, ref) {\n const cart = useContextValue();\n return part(\n \"button\",\n props,\n {},\n { type: \"button\", onClick: () => cart.actions.remove(lineId) },\n ref,\n );\n },\n);\nconst Empty = forwardRef<HTMLElement, PartProps>(function Empty(props, ref) {\n const cart = useContextValue();\n return cart.isHydrated && !cart.lines.length\n ? part(\"div\", props, {}, {}, ref)\n : null;\n});\nconst Issues = forwardRef<\n HTMLElement,\n PartProps<{\n issues: readonly CartIssue[];\n applyFix: (issue: CartIssue, fix?: CartFix) => void;\n }>\n>(function Issues(props, ref) {\n const cart = useContextValue();\n return part(\n \"div\",\n props,\n { issues: cart.issues, applyFix: cart.actions.applyFix },\n {\n \"aria-live\": \"polite\",\n \"data-state\": cart.issues.length ? \"invalid\" : \"valid\",\n },\n ref,\n );\n});\n/**\n * Composable cart parts for lines, quantity changes, removal, empty state and validation issues.\n * Wrap the parts in Cart.Root inside your ZappieProvider.\n */\nexport const Cart = {\n Root,\n Line,\n Quantity: CartQuantity,\n Remove,\n Empty,\n Issues,\n};\n/**\n * The quote and its display status: loading, stale, ready or offline.\n */\nexport type TotalsState = {\n quote?: Quote | undefined;\n status: \"loading\" | \"stale\" | \"ready\" | \"offline\";\n};\n/**\n * Expose the cart quote and its freshness or network status to your render function.\n * Subscribes to the cart and quote failures through your ZappieProvider.\n */\nexport const Totals = forwardRef<HTMLElement, PartProps<TotalsState>>(\n function Totals(props, ref) {\n const cart = useCart();\n const client = useZappie();\n const [failure, setFailure] = useState<\"offline\" | \"stale\" | null>(null);\n const [expired, setExpired] = useState(false);\n const quote = \"quote\" in cart.state ? cart.state.quote : undefined;\n useEffect(\n () =>\n client.on(\"quote_failed\", ({ code }) =>\n setFailure(\n code === \"network_error\" || code === \"timeout\"\n ? \"offline\"\n : \"stale\",\n ),\n ),\n [client],\n );\n useEffect(() => {\n if (cart.state.status === \"quoting\" || quote) {\n setFailure(null);\n }\n setExpired(Boolean(quote && Date.parse(quote.expiresAt) <= Date.now()));\n if (!quote) {\n return;\n }\n const timeout = setTimeout(\n () => setExpired(true),\n Math.max(\n 0,\n Math.min(2147483647, Date.parse(quote.expiresAt) - Date.now()),\n ),\n );\n return () => clearTimeout(timeout);\n }, [quote, cart.state.status]);\n // The SDK re-quotes shortly after every change, so a cart with lines and\n // no quote yet is waiting for one rather than stale.\n const pending =\n cart.state.status === \"ready\" && !quote && cart.lines.length > 0;\n const status: TotalsState[\"status\"] =\n failure === \"offline\"\n ? \"offline\"\n : cart.state.status === \"quoting\" || !cart.isHydrated\n ? \"loading\"\n : failure === \"stale\" || expired\n ? \"stale\"\n : pending\n ? \"loading\"\n : !quote\n ? \"stale\"\n : \"ready\";\n return part(\n \"div\",\n props,\n { quote, status },\n {\n \"aria-live\": \"polite\",\n \"aria-busy\": status === \"loading\",\n \"data-state\":\n status === \"ready\"\n ? \"valid\"\n : status === \"offline\"\n ? \"stale\"\n : status,\n },\n ref,\n );\n },\n);\n"],
5
+ "mappings": "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",
6
+ "names": ["Line", "Remove", "Empty", "Issues", "Totals"]
7
+ }
@@ -0,0 +1,168 @@
1
+ "use client";
2
+
3
+ // src/fulfilment/address-picker.tsx
4
+ import {
5
+ createContext,
6
+ forwardRef,
7
+ useContext,
8
+ useEffect,
9
+ useState
10
+ } from "react";
11
+ import {
12
+ countAnnouncement,
13
+ useCombobox,
14
+ withLiveRegion
15
+ } from "../internal/combobox.js";
16
+ import { composeRefs, part } from "../internal/slot.js";
17
+ import { useControllableState } from "../internal/use-controllable-state.js";
18
+ import { jsx } from "react/jsx-runtime";
19
+ var Context = createContext(null);
20
+ function useAddress() {
21
+ const ctx = useContext(Context);
22
+ if (!ctx) {
23
+ throw new Error("Address parts require Address.Picker");
24
+ }
25
+ return ctx;
26
+ }
27
+ function Picker({
28
+ getSuggestions,
29
+ onSelect,
30
+ query,
31
+ defaultQuery = "",
32
+ onQueryChange,
33
+ debounceMs = 200,
34
+ announcement = countAnnouncement,
35
+ ...props
36
+ }) {
37
+ const [value, setValue] = useControllableState({
38
+ value: query,
39
+ defaultValue: defaultQuery,
40
+ onChange: onQueryChange
41
+ });
42
+ const [response, setResponse] = useState({ query: "", suggestions: [], status: "ready" });
43
+ const suggestions = response.query === value ? response.suggestions : [];
44
+ const combo = useCombobox(suggestions, (suggestion) => {
45
+ setValue(suggestion.label);
46
+ onSelect(suggestion);
47
+ });
48
+ useEffect(() => {
49
+ if (!value.trim()) {
50
+ setResponse({ query: value, suggestions: [], status: "ready" });
51
+ return;
52
+ }
53
+ let live = true;
54
+ const abort = new AbortController();
55
+ setResponse({ query: value, suggestions: [], status: "loading" });
56
+ const timer = setTimeout(() => {
57
+ void Promise.resolve().then(() => getSuggestions(value, abort.signal)).then(
58
+ (items) => {
59
+ if (live) {
60
+ setResponse({
61
+ query: value,
62
+ suggestions: items,
63
+ status: "ready"
64
+ });
65
+ }
66
+ },
67
+ (error) => {
68
+ if (live) {
69
+ setResponse({
70
+ query: value,
71
+ suggestions: [],
72
+ status: "error",
73
+ error
74
+ });
75
+ }
76
+ }
77
+ );
78
+ }, debounceMs);
79
+ return () => {
80
+ live = false;
81
+ abort.abort();
82
+ clearTimeout(timer);
83
+ };
84
+ }, [value, getSuggestions, debounceMs]);
85
+ const setQuery = (next) => {
86
+ setValue(next);
87
+ combo.setOpen(true);
88
+ combo.setActiveIndex(-1);
89
+ };
90
+ const state = {
91
+ ...combo,
92
+ query: value,
93
+ setQuery,
94
+ suggestions,
95
+ status: response.status,
96
+ error: response.error
97
+ };
98
+ return /* @__PURE__ */ jsx(Context.Provider, { value: state, children: part(
99
+ "div",
100
+ withLiveRegion(
101
+ props,
102
+ combo.isOpen && value.trim() && response.status === "ready" ? announcement(suggestions.length) : ""
103
+ ),
104
+ state,
105
+ {
106
+ "data-state": response.status === "error" ? "invalid" : response.status === "loading" ? "loading" : "valid",
107
+ onBlur: (event) => {
108
+ if (!event.currentTarget.contains(event.relatedTarget)) {
109
+ combo.setOpen(false);
110
+ }
111
+ }
112
+ }
113
+ ) });
114
+ }
115
+ var Input = forwardRef(
116
+ function Input2(props, ref) {
117
+ const state = useAddress();
118
+ return part(
119
+ "input",
120
+ props,
121
+ state,
122
+ {
123
+ ...state.inputProps,
124
+ type: "text",
125
+ autoComplete: "street-address",
126
+ "data-zappie-address-input": "",
127
+ value: state.query,
128
+ onChange: (event) => state.setQuery(event.target.value)
129
+ },
130
+ ref
131
+ );
132
+ }
133
+ );
134
+ var Suggestions = forwardRef(
135
+ function Suggestions2(props, ref) {
136
+ const state = useAddress();
137
+ return part(
138
+ "div",
139
+ props,
140
+ state,
141
+ { ...state.listProps, "aria-busy": state.status === "loading" },
142
+ composeRefs(ref, (node) => {
143
+ if (node?.id && node.id !== state.listProps.id) {
144
+ state.setListId(node.id);
145
+ }
146
+ })
147
+ );
148
+ }
149
+ );
150
+ var Suggestion = forwardRef(function Suggestion2({ index, ...props }, ref) {
151
+ const state = useAddress();
152
+ return part(
153
+ "div",
154
+ props,
155
+ state,
156
+ state.getOptionProps(index),
157
+ composeRefs(ref, (node) => {
158
+ if (node?.id && node.id !== state.getOptionProps(index).id) {
159
+ state.registerOption(index, node.id);
160
+ }
161
+ })
162
+ );
163
+ });
164
+ var Address = { Picker, Input, Suggestions, Suggestion };
165
+ export {
166
+ Address
167
+ };
168
+ //# sourceMappingURL=address-picker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fulfilment/address-picker.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport type { DeliveryAddress } from \"@zappie/sdk\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n type ChangeEvent,\n} from \"react\";\nimport {\n countAnnouncement,\n useCombobox,\n withLiveRegion,\n} from \"../internal/combobox\";\nimport { composeRefs, part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\n/**\n * A labelled address suggestion with an ID and the delivery address selected by the shopper.\n */\nexport type AddressSuggestion = {\n id: string;\n label: string;\n address: DeliveryAddress;\n};\n/**\n * Address picker state with query controls, combobox navigation, suggestions and request status.\n */\nexport type AddressState = ReturnType<typeof useCombobox<AddressSuggestion>> & {\n query: string;\n setQuery: (query: string) => void;\n suggestions: AddressSuggestion[];\n status: \"loading\" | \"ready\" | \"error\";\n error?: unknown;\n};\nconst Context = createContext<AddressState | null>(null);\nfunction useAddress() {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"Address parts require Address.Picker\");\n }\n return ctx;\n}\nfunction Picker({\n getSuggestions,\n onSelect,\n query,\n defaultQuery = \"\",\n onQueryChange,\n debounceMs = 200,\n announcement = countAnnouncement,\n ...props\n}: PartProps<AddressState> & {\n /** Screen-reader text for the suggestion count. Override it for your language. */\n announcement?: (count: number) => string;\n getSuggestions: (\n query: string,\n signal?: AbortSignal,\n ) => Promise<AddressSuggestion[]>;\n onSelect: (suggestion: AddressSuggestion) => void;\n query?: string;\n defaultQuery?: string;\n onQueryChange?: (query: string) => void;\n debounceMs?: number;\n}) {\n const [value, setValue] = useControllableState({\n value: query,\n defaultValue: defaultQuery,\n onChange: onQueryChange,\n });\n const [response, setResponse] = useState<{\n query: string;\n suggestions: AddressSuggestion[];\n status: AddressState[\"status\"];\n error?: unknown;\n }>({ query: \"\", suggestions: [], status: \"ready\" });\n const suggestions = response.query === value ? response.suggestions : [];\n const combo = useCombobox(suggestions, (suggestion) => {\n setValue(suggestion.label);\n onSelect(suggestion);\n });\n useEffect(() => {\n if (!value.trim()) {\n setResponse({ query: value, suggestions: [], status: \"ready\" });\n return;\n }\n let live = true;\n const abort = new AbortController();\n setResponse({ query: value, suggestions: [], status: \"loading\" });\n const timer = setTimeout(() => {\n void Promise.resolve()\n .then(() => getSuggestions(value, abort.signal))\n .then(\n (items) => {\n if (live) {\n setResponse({\n query: value,\n suggestions: items,\n status: \"ready\",\n });\n }\n },\n (error) => {\n if (live) {\n setResponse({\n query: value,\n suggestions: [],\n status: \"error\",\n error,\n });\n }\n },\n );\n }, debounceMs);\n return () => {\n live = false;\n abort.abort();\n clearTimeout(timer);\n };\n }, [value, getSuggestions, debounceMs]);\n const setQuery = (next: string) => {\n setValue(next);\n combo.setOpen(true);\n combo.setActiveIndex(-1);\n };\n const state = {\n ...combo,\n query: value,\n setQuery,\n suggestions,\n status: response.status,\n error: response.error,\n };\n return (\n <Context.Provider value={state}>\n {part(\n \"div\",\n withLiveRegion(\n props,\n combo.isOpen && value.trim() && response.status === \"ready\"\n ? announcement(suggestions.length)\n : \"\",\n ) as typeof props,\n state,\n {\n \"data-state\":\n response.status === \"error\"\n ? \"invalid\"\n : response.status === \"loading\"\n ? \"loading\"\n : \"valid\",\n onBlur: (event: {\n currentTarget: HTMLElement;\n relatedTarget: Node | null;\n }) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n combo.setOpen(false);\n }\n },\n },\n )}\n </Context.Provider>\n );\n}\nconst Input = forwardRef<HTMLElement, PartProps<AddressState>>(\n function Input(props, ref) {\n const state = useAddress();\n return part(\n \"input\",\n props,\n state,\n {\n ...state.inputProps,\n type: \"text\",\n autoComplete: \"street-address\",\n \"data-zappie-address-input\": \"\",\n value: state.query,\n onChange: (event: ChangeEvent<HTMLInputElement>) =>\n state.setQuery(event.target.value),\n },\n ref,\n );\n },\n);\nconst Suggestions = forwardRef<HTMLElement, PartProps<AddressState>>(\n function Suggestions(props, ref) {\n const state = useAddress();\n return part(\n \"div\",\n props,\n state,\n { ...state.listProps, \"aria-busy\": state.status === \"loading\" },\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.listProps.id) {\n state.setListId(node.id);\n }\n }),\n );\n },\n);\nconst Suggestion = forwardRef<\n HTMLElement,\n PartProps<AddressState> & { index: number }\n>(function Suggestion({ index, ...props }, ref) {\n const state = useAddress();\n return part(\n \"div\",\n props,\n state,\n state.getOptionProps(index),\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.getOptionProps(index).id) {\n state.registerOption(index, node.id);\n }\n }),\n );\n});\n/**\n * Composable address picker parts with debounced suggestions and keyboard navigation.\n * Supply getSuggestions and onSelect to Address.Picker. It cancels outdated requests.\n */\nexport const Address = { Picker, Input, Suggestions, Suggestion };\n"],
5
+ "mappings": "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",
6
+ "names": ["Input", "Suggestions", "Suggestion"]
7
+ }
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ // src/fulfilment/branch-picker.tsx
4
+ import { useCart, useStore } from "@zappie/sdk/react";
5
+ import { forwardRef, useState } from "react";
6
+ import { part } from "../internal/slot.js";
7
+ import { getOpenStatus } from "../status/hours.js";
8
+ var Picker = forwardRef(function Picker2({ branches, value, onSelect, ...props }, ref) {
9
+ const cart = useCart();
10
+ const store = useStore();
11
+ const [isLoading, setLoading] = useState(false);
12
+ const [error, setError] = useState();
13
+ const selected = branches.find(
14
+ (branch) => branch.id === (value ?? cart.branchId)
15
+ );
16
+ const select = async (branch) => {
17
+ if (isLoading || branch.id === cart.branchId || !branches.some((candidate) => candidate.id === branch.id)) {
18
+ return;
19
+ }
20
+ setLoading(true);
21
+ setError(void 0);
22
+ try {
23
+ await cart.actions.moveTo(branch.id);
24
+ onSelect?.(branch);
25
+ } catch (failure) {
26
+ setError(failure);
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ };
31
+ return part(
32
+ "div",
33
+ props,
34
+ {
35
+ branches,
36
+ selected,
37
+ select,
38
+ isLoading,
39
+ error,
40
+ isOpenNow: (branch) => getOpenStatus(branch, branch.timezone ?? store.data?.timezone ?? "UTC").isOpen
41
+ },
42
+ {
43
+ role: "group",
44
+ "aria-busy": isLoading,
45
+ "data-state": isLoading ? "loading" : error ? "invalid" : "valid"
46
+ },
47
+ ref
48
+ );
49
+ });
50
+ var Branch = { Picker };
51
+ export {
52
+ Branch
53
+ };
54
+ //# sourceMappingURL=branch-picker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fulfilment/branch-picker.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport type { Branch as BranchModel } from \"@zappie/sdk\";\nimport { useCart, useStore } from \"@zappie/sdk/react\";\nimport { forwardRef, useState } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { getOpenStatus } from \"../status/hours\";\nexport type BranchPickerState = {\n branches: BranchModel[];\n selected?: BranchModel | undefined;\n isOpenNow: (branch: BranchModel) => boolean;\n select: (branch: BranchModel) => Promise<void>;\n isLoading: boolean;\n error?: unknown;\n};\nconst Picker = forwardRef<\n HTMLElement,\n PartProps<BranchPickerState> & {\n branches: BranchModel[];\n value?: string;\n onSelect?: (branch: BranchModel) => void;\n }\n>(function Picker({ branches, value, onSelect, ...props }, ref) {\n const cart = useCart();\n const store = useStore();\n const [isLoading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>();\n const selected = branches.find(\n (branch) => branch.id === (value ?? cart.branchId),\n );\n const select = async (branch: BranchModel) => {\n if (\n isLoading ||\n branch.id === cart.branchId ||\n !branches.some((candidate) => candidate.id === branch.id)\n ) {\n return;\n }\n setLoading(true);\n setError(undefined);\n try {\n await cart.actions.moveTo(branch.id);\n onSelect?.(branch);\n } catch (failure) {\n setError(failure);\n } finally {\n setLoading(false);\n }\n };\n return part(\n \"div\",\n props,\n {\n branches,\n selected,\n select,\n isLoading,\n error,\n isOpenNow: (branch) =>\n getOpenStatus(branch, branch.timezone ?? store.data?.timezone ?? \"UTC\")\n .isOpen,\n },\n {\n role: \"group\",\n \"aria-busy\": isLoading,\n \"data-state\": isLoading ? \"loading\" : error ? \"invalid\" : \"valid\",\n },\n ref,\n );\n});\n/**\n * A branch picker that moves the cart to a selected branch and exposes loading and error state.\n * Use Branch.Picker within your ZappieProvider.\n */\nexport const Branch = { Picker };\n"],
5
+ "mappings": ";;;AAEA,SAAS,SAAS,gBAAgB;AAClC,SAAS,YAAY,gBAAgB;AACrC,SAAS,YAA4B;AACrC,SAAS,qBAAqB;AAS9B,IAAM,SAAS,WAOb,SAASA,QAAO,EAAE,UAAU,OAAO,UAAU,GAAG,MAAM,GAAG,KAAK;AAC9D,QAAM,OAAO,QAAQ;AACrB,QAAM,QAAQ,SAAS;AACvB,QAAM,CAAC,WAAW,UAAU,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB;AAC5C,QAAM,WAAW,SAAS;AAAA,IACxB,CAAC,WAAW,OAAO,QAAQ,SAAS,KAAK;AAAA,EAC3C;AACA,QAAM,SAAS,OAAO,WAAwB;AAC5C,QACE,aACA,OAAO,OAAO,KAAK,YACnB,CAAC,SAAS,KAAK,CAAC,cAAc,UAAU,OAAO,OAAO,EAAE,GACxD;AACA;AAAA,IACF;AACA,eAAW,IAAI;AACf,aAAS,MAAS;AAClB,QAAI;AACF,YAAM,KAAK,QAAQ,OAAO,OAAO,EAAE;AACnC,iBAAW,MAAM;AAAA,IACnB,SAAS,SAAS;AAChB,eAAS,OAAO;AAAA,IAClB,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,CAAC,WACV,cAAc,QAAQ,OAAO,YAAY,MAAM,MAAM,YAAY,KAAK,EACnE;AAAA,IACP;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,cAAc,YAAY,YAAY,QAAQ,YAAY;AAAA,IAC5D;AAAA,IACA;AAAA,EACF;AACF,CAAC;AAKM,IAAM,SAAS,EAAE,OAAO;",
6
+ "names": ["Picker"]
7
+ }