@zappie/foundations 0.1.0 → 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 (74) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +30 -253
  3. package/dist/account/index.js +49 -0
  4. package/dist/account/index.js.map +7 -0
  5. package/dist/cart/index.js +168 -0
  6. package/dist/cart/index.js.map +7 -0
  7. package/dist/fulfilment/address-picker.js +168 -0
  8. package/dist/fulfilment/address-picker.js.map +7 -0
  9. package/dist/fulfilment/branch-picker.js +54 -0
  10. package/dist/fulfilment/branch-picker.js.map +7 -0
  11. package/dist/fulfilment/schedule.js +146 -0
  12. package/dist/fulfilment/schedule.js.map +7 -0
  13. package/dist/fulfilment/switch.js +54 -0
  14. package/dist/fulfilment/switch.js.map +7 -0
  15. package/dist/index.d.ts +2 -4
  16. package/dist/index.js +49 -2113
  17. package/dist/index.js.map +7 -1
  18. package/dist/internal/combobox.js +131 -0
  19. package/dist/internal/combobox.js.map +7 -0
  20. package/dist/internal/data-attrs.js +8 -0
  21. package/dist/internal/data-attrs.js.map +7 -0
  22. package/dist/internal/ids.js +10 -0
  23. package/dist/internal/ids.js.map +7 -0
  24. package/dist/{chunk-D3J7S33L.js → internal/slot.js} +13 -6
  25. package/dist/internal/slot.js.map +7 -0
  26. package/dist/internal/use-controllable-state.js +29 -0
  27. package/dist/internal/use-controllable-state.js.map +7 -0
  28. package/dist/item-form/index.js +13 -0
  29. package/dist/item-form/index.js.map +7 -0
  30. package/dist/item-form/option-group.js +268 -0
  31. package/dist/item-form/option-group.js.map +7 -0
  32. package/dist/item-form/options.js +30 -0
  33. package/dist/item-form/options.js.map +7 -0
  34. package/dist/item-form/quantity.js +131 -0
  35. package/dist/item-form/quantity.js.map +7 -0
  36. package/dist/item-form/root.js +151 -0
  37. package/dist/item-form/root.js.map +7 -0
  38. package/dist/media/image.js +30 -0
  39. package/dist/media/image.js.map +7 -0
  40. package/dist/money.js +20 -0
  41. package/dist/money.js.map +7 -0
  42. package/dist/next.js +8 -6
  43. package/dist/next.js.map +7 -1
  44. package/dist/otp/input.js +110 -0
  45. package/dist/otp/input.js.map +7 -0
  46. package/dist/overlay/engine.js +16 -8
  47. package/dist/overlay/engine.js.map +7 -1
  48. package/dist/{chunk-BY2WQGLO.js → overlay/history.js} +9 -4
  49. package/dist/overlay/history.js.map +7 -0
  50. package/dist/overlay/react.js +261 -0
  51. package/dist/overlay/react.js.map +7 -0
  52. package/dist/payment/methods.js +103 -0
  53. package/dist/payment/methods.js.map +7 -0
  54. package/dist/rich-text/banners.js +9 -0
  55. package/dist/rich-text/banners.js.map +7 -0
  56. package/dist/rich-text/browser.js +3 -3
  57. package/dist/rich-text/browser.js.map +7 -1
  58. package/dist/rich-text/core.js +75 -0
  59. package/dist/rich-text/core.js.map +7 -0
  60. package/dist/rich-text/index.js +3 -79
  61. package/dist/rich-text/index.js.map +7 -1
  62. package/dist/search/engine.js +93 -0
  63. package/dist/search/engine.js.map +7 -0
  64. package/dist/search/index.js +175 -0
  65. package/dist/search/index.js.map +7 -0
  66. package/dist/status/hours.js +177 -0
  67. package/dist/status/hours.js.map +7 -0
  68. package/dist/status/open-status.js +45 -0
  69. package/dist/status/open-status.js.map +7 -0
  70. package/dist/version.js +6 -0
  71. package/dist/version.js.map +7 -0
  72. package/package.json +5 -4
  73. package/dist/chunk-BY2WQGLO.js.map +0 -1
  74. package/dist/chunk-D3J7S33L.js.map +0 -1
@@ -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
+ }
@@ -0,0 +1,146 @@
1
+ "use client";
2
+
3
+ // src/fulfilment/schedule.tsx
4
+ import { useMenu, useStore } from "@zappie/sdk/react";
5
+ import { forwardRef, useEffect, useMemo, useState } from "react";
6
+ import { part } from "../internal/slot.js";
7
+ import { useControllableState } from "../internal/use-controllable-state.js";
8
+ import { getOpenStatus, localParts, openingIntervals } from "../status/hours.js";
9
+ function scheduleSlots(branch, timezone, now, {
10
+ intervalMinutes = 30,
11
+ maxDays = 14,
12
+ noticeMinutes = 0,
13
+ menuSchedule
14
+ } = {}) {
15
+ if (!Number.isInteger(intervalMinutes) || intervalMinutes < 1 || !Number.isInteger(maxDays) || maxDays < 1 || maxDays > 90 || !Number.isFinite(noticeMinutes) || noticeMinutes < 0) {
16
+ throw new RangeError(
17
+ "Schedule requires a positive interval, 1\u201390 days and a non-negative notice"
18
+ );
19
+ }
20
+ if (!branch.fulfilment.scheduling) {
21
+ return [];
22
+ }
23
+ const preorder = menuSchedule?.preorder ? menuSchedule : void 0;
24
+ const days = preorder?.preorderMaxDays && preorder.preorderMaxDays > 0 ? Math.min(maxDays, preorder.preorderMaxDays) : maxDays;
25
+ const earliest = now.getTime() + Math.max(noticeMinutes, (preorder?.preorderNoticeHours ?? 0) * 60) * 6e4;
26
+ const latest = now.getTime() + days * 864e5;
27
+ const menuIntervals = menuSchedule?.openingTimes.length ? openingIntervals(
28
+ {
29
+ ...branch,
30
+ openingTimes: menuSchedule.openingTimes,
31
+ specialHours: []
32
+ },
33
+ timezone,
34
+ now,
35
+ days + 1
36
+ ) : void 0;
37
+ const slots = [];
38
+ for (const { start, end } of openingIntervals(
39
+ branch,
40
+ timezone,
41
+ now,
42
+ days + 1
43
+ )) {
44
+ const first = start + Math.max(0, Math.ceil((earliest - start) / (intervalMinutes * 6e4))) * intervalMinutes * 6e4;
45
+ for (let instant = first; instant < end && instant <= latest; instant += intervalMinutes * 6e4) {
46
+ if (menuIntervals && !menuIntervals.some(
47
+ (interval) => interval.start <= instant && interval.end > instant
48
+ )) {
49
+ continue;
50
+ }
51
+ const date = new Date(instant);
52
+ slots.push({
53
+ value: date.toISOString(),
54
+ date: localParts(date, timezone).date,
55
+ label: new Intl.DateTimeFormat("en", {
56
+ timeZone: timezone,
57
+ hour: "numeric",
58
+ minute: "2-digit",
59
+ timeZoneName: "shortOffset"
60
+ }).format(date)
61
+ });
62
+ }
63
+ }
64
+ return slots;
65
+ }
66
+ var Schedule = forwardRef(function Schedule2({
67
+ branch,
68
+ timezone: suppliedTimezone,
69
+ value,
70
+ defaultValue = null,
71
+ onValueChange,
72
+ intervalMinutes,
73
+ maxDays,
74
+ noticeMinutes,
75
+ ...props
76
+ }, ref) {
77
+ const store = useStore();
78
+ const menu = useMenu(branch.id, { metadataOnly: true });
79
+ const timezone = suppliedTimezone ?? branch.timezone ?? store.data?.timezone ?? "UTC";
80
+ const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
81
+ useEffect(() => {
82
+ const timer = setTimeout(
83
+ () => setNow(/* @__PURE__ */ new Date()),
84
+ 6e4 - Date.now() % 6e4
85
+ );
86
+ return () => clearTimeout(timer);
87
+ }, [now]);
88
+ const [selection, setSelection] = useControllableState({
89
+ value,
90
+ defaultValue,
91
+ onChange: onValueChange
92
+ });
93
+ const slots = useMemo(
94
+ () => menu.isLoading || menu.error ? [] : scheduleSlots(branch, timezone, now, {
95
+ intervalMinutes,
96
+ maxDays,
97
+ noticeMinutes,
98
+ menuSchedule: menu.data?.schedule
99
+ }),
100
+ [
101
+ branch,
102
+ timezone,
103
+ now,
104
+ intervalMinutes,
105
+ maxDays,
106
+ noticeMinutes,
107
+ menu.data?.schedule,
108
+ menu.isLoading,
109
+ menu.error
110
+ ]
111
+ );
112
+ const days = Array.from(new Set(slots.map((slot) => slot.date))).map(
113
+ (date) => ({ date, slots: slots.filter((slot) => slot.date === date) })
114
+ );
115
+ const canAsap = !menu.isLoading && !menu.error && !menu.data?.schedule?.preorder && getOpenStatus(branch, timezone, now).isOpen;
116
+ const isValid = selection === null ? canAsap : slots.some((slot) => slot.value === selection);
117
+ const setValue = (next) => {
118
+ if (next === null ? canAsap : slots.some((slot) => slot.value === next)) {
119
+ setSelection(next);
120
+ }
121
+ };
122
+ return part(
123
+ "div",
124
+ props,
125
+ {
126
+ days,
127
+ slots,
128
+ value: selection,
129
+ setValue,
130
+ isAsap: selection === null,
131
+ canAsap,
132
+ isValid
133
+ },
134
+ {
135
+ role: "group",
136
+ "data-state": isValid ? "valid" : "invalid",
137
+ "data-invalid": isValid ? void 0 : ""
138
+ },
139
+ ref
140
+ );
141
+ });
142
+ export {
143
+ Schedule,
144
+ scheduleSlots
145
+ };
146
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fulfilment/schedule.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport type { Branch, MenuSchedule } from \"@zappie/sdk\";\nimport { useMenu, useStore } from \"@zappie/sdk/react\";\nimport { forwardRef, useEffect, useMemo, useState } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\nimport { getOpenStatus, localParts, openingIntervals } from \"../status/hours\";\n/**\n * A selectable time with an ISO instant, local date and display label including the UTC offset.\n */\nexport type ScheduleSlot = { value: string; label: string; date: string };\n/**\n * Available days and slots, the selected time and controls for choosing ASAP or a scheduled time.\n */\nexport type ScheduleState = {\n days: { date: string; slots: ScheduleSlot[] }[];\n slots: ScheduleSlot[];\n value: string | null;\n setValue: (value: string | null) => void;\n isAsap: boolean;\n canAsap: boolean;\n isValid: boolean;\n};\n/**\n * Generate scheduling slots within branch hours and any supplied menu hours and preorder limits.\n * Returns no slots when the branch does not support scheduling.\n *\n * @param branch - The branch whose hours and scheduling capability apply.\n * @param timezone - The time zone used to interpret opening hours and label slots.\n * @param now - The instant from which notice and scheduling limits are measured.\n * @returns Slots with ISO values, local dates and display labels.\n */\nexport function scheduleSlots(\n branch: Branch,\n timezone: string,\n now: Date,\n {\n intervalMinutes = 30,\n maxDays = 14,\n noticeMinutes = 0,\n menuSchedule,\n }: {\n intervalMinutes?: number | undefined;\n maxDays?: number | undefined;\n noticeMinutes?: number | undefined;\n menuSchedule?: MenuSchedule | undefined;\n } = {},\n) {\n if (\n !Number.isInteger(intervalMinutes) ||\n intervalMinutes < 1 ||\n !Number.isInteger(maxDays) ||\n maxDays < 1 ||\n maxDays > 90 ||\n !Number.isFinite(noticeMinutes) ||\n noticeMinutes < 0\n ) {\n throw new RangeError(\n \"Schedule requires a positive interval, 1\u201390 days and a non-negative notice\",\n );\n }\n if (!branch.fulfilment.scheduling) {\n return [];\n }\n // Pre-order limits apply to pre-order menus only. Other menus carry zeros\n // (the API's default), and a zero horizon would leave nothing to schedule.\n const preorder = menuSchedule?.preorder ? menuSchedule : undefined;\n const days =\n preorder?.preorderMaxDays && preorder.preorderMaxDays > 0\n ? Math.min(maxDays, preorder.preorderMaxDays)\n : maxDays;\n const earliest =\n now.getTime() +\n Math.max(noticeMinutes, (preorder?.preorderNoticeHours ?? 0) * 60) * 60000;\n const latest = now.getTime() + days * 86400000;\n const menuIntervals = menuSchedule?.openingTimes.length\n ? openingIntervals(\n {\n ...branch,\n openingTimes: menuSchedule.openingTimes,\n specialHours: [],\n },\n timezone,\n now,\n days + 1,\n )\n : undefined;\n const slots: ScheduleSlot[] = [];\n for (const { start, end } of openingIntervals(\n branch,\n timezone,\n now,\n days + 1,\n )) {\n const first =\n start +\n Math.max(0, Math.ceil((earliest - start) / (intervalMinutes * 60000))) *\n intervalMinutes *\n 60000;\n for (\n let instant = first;\n instant < end && instant <= latest;\n instant += intervalMinutes * 60000\n ) {\n if (\n menuIntervals &&\n !menuIntervals.some(\n (interval) => interval.start <= instant && interval.end > instant,\n )\n ) {\n continue;\n }\n const date = new Date(instant);\n slots.push({\n value: date.toISOString(),\n date: localParts(date, timezone).date,\n label: new Intl.DateTimeFormat(\"en\", {\n timeZone: timezone,\n hour: \"numeric\",\n minute: \"2-digit\",\n timeZoneName: \"shortOffset\",\n }).format(date),\n });\n }\n }\n return slots;\n}\n/**\n * Expose valid fulfilment slots and ASAP eligibility using branch hours and loaded menu metadata.\n * Use null for ASAP. Requires a ZappieProvider and refreshes the available times each minute.\n */\nexport const Schedule = forwardRef<\n HTMLElement,\n PartProps<ScheduleState> & {\n branch: Branch;\n timezone?: string;\n value?: string | null;\n defaultValue?: string | null;\n onValueChange?: (value: string | null) => void;\n intervalMinutes?: number | undefined;\n maxDays?: number | undefined;\n noticeMinutes?: number;\n }\n>(function Schedule(\n {\n branch,\n timezone: suppliedTimezone,\n value,\n defaultValue = null,\n onValueChange,\n intervalMinutes,\n maxDays,\n noticeMinutes,\n ...props\n },\n ref,\n) {\n const store = useStore();\n const menu = useMenu(branch.id, { metadataOnly: true });\n const timezone =\n suppliedTimezone ?? branch.timezone ?? store.data?.timezone ?? \"UTC\";\n const [now, setNow] = useState(() => new Date());\n useEffect(() => {\n const timer = setTimeout(\n () => setNow(new Date()),\n 60000 - (Date.now() % 60000),\n );\n return () => clearTimeout(timer);\n }, [now]);\n const [selection, setSelection] = useControllableState({\n value,\n defaultValue,\n onChange: onValueChange,\n });\n const slots = useMemo(\n () =>\n menu.isLoading || menu.error\n ? []\n : scheduleSlots(branch, timezone, now, {\n intervalMinutes,\n maxDays,\n noticeMinutes,\n menuSchedule: menu.data?.schedule,\n }),\n [\n branch,\n timezone,\n now,\n intervalMinutes,\n maxDays,\n noticeMinutes,\n menu.data?.schedule,\n menu.isLoading,\n menu.error,\n ],\n );\n const days = Array.from(new Set(slots.map((slot) => slot.date))).map(\n (date) => ({ date, slots: slots.filter((slot) => slot.date === date) }),\n );\n const canAsap =\n !menu.isLoading &&\n !menu.error &&\n !menu.data?.schedule?.preorder &&\n getOpenStatus(branch, timezone, now).isOpen;\n const isValid =\n selection === null\n ? canAsap\n : slots.some((slot) => slot.value === selection);\n const setValue = (next: string | null) => {\n if (next === null ? canAsap : slots.some((slot) => slot.value === next)) {\n setSelection(next);\n }\n };\n return part(\n \"div\",\n props,\n {\n days,\n slots,\n value: selection,\n setValue,\n isAsap: selection === null,\n canAsap,\n isValid,\n },\n {\n role: \"group\",\n \"data-state\": isValid ? \"valid\" : \"invalid\",\n \"data-invalid\": isValid ? undefined : \"\",\n },\n ref,\n );\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["Schedule"]
7
+ }
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ // src/fulfilment/switch.tsx
4
+ import { ZappieError } from "@zappie/sdk";
5
+ import { useCart, useStore } from "@zappie/sdk/react";
6
+ import { forwardRef } from "react";
7
+ import { part } from "../internal/slot.js";
8
+ import { useControllableState } from "../internal/use-controllable-state.js";
9
+ var Switch = forwardRef(function Switch2({ value, defaultValue, onValueChange, ...props }, ref) {
10
+ const cart = useCart();
11
+ const store = useStore();
12
+ const [selection, setSelection] = useControllableState({
13
+ value,
14
+ defaultValue: defaultValue ?? cart.fulfilment,
15
+ onChange: onValueChange
16
+ });
17
+ const current = value === void 0 && defaultValue === void 0 ? cart.fulfilment : selection;
18
+ const branch = store.data?.branches.find(
19
+ (branch2) => branch2.id === (cart.branchId ?? current?.branchId)
20
+ ) ?? store.data?.branches.find((branch2) => branch2.isDefault) ?? store.data?.branches[0];
21
+ const available = branch?.fulfilment;
22
+ const options = [];
23
+ if (available?.pickup) {
24
+ options.push("pickup");
25
+ }
26
+ if (available?.delivery) {
27
+ options.push("delivery");
28
+ }
29
+ if (available?.dineIn) {
30
+ options.push("dine_in");
31
+ }
32
+ const setValue = (next) => {
33
+ if (!options.includes(next.type) || next.branchId !== branch?.id) {
34
+ throw new ZappieError(
35
+ "invalid_request",
36
+ "Select an offered fulfilment mode for this branch"
37
+ );
38
+ }
39
+ cart.actions.setFulfilment(next);
40
+ setSelection(next);
41
+ };
42
+ return part(
43
+ "div",
44
+ props,
45
+ { options, value: current, setValue },
46
+ { role: "group" },
47
+ ref
48
+ );
49
+ });
50
+ var Fulfilment = { Switch };
51
+ export {
52
+ Fulfilment
53
+ };
54
+ //# sourceMappingURL=switch.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/fulfilment/switch.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport { ZappieError, type CartFulfilment } from \"@zappie/sdk\";\nimport { useCart, useStore } from \"@zappie/sdk/react\";\nimport { forwardRef } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\nexport type FulfilmentState = {\n options: CartFulfilment[\"type\"][];\n value: CartFulfilment | null;\n setValue: (value: CartFulfilment) => void;\n};\nconst Switch = forwardRef<\n HTMLElement,\n PartProps<FulfilmentState> & {\n value?: CartFulfilment | null;\n defaultValue?: CartFulfilment | null;\n onValueChange?: (value: CartFulfilment | null) => void;\n }\n>(function Switch({ value, defaultValue, onValueChange, ...props }, ref) {\n const cart = useCart();\n const store = useStore();\n const [selection, setSelection] = useControllableState({\n value,\n defaultValue: defaultValue ?? cart.fulfilment,\n onChange: onValueChange,\n });\n const current =\n value === undefined && defaultValue === undefined\n ? cart.fulfilment\n : selection;\n const branch =\n store.data?.branches.find(\n (branch) => branch.id === (cart.branchId ?? current?.branchId),\n ) ??\n store.data?.branches.find((branch) => branch.isDefault) ??\n store.data?.branches[0];\n const available = branch?.fulfilment;\n const options: CartFulfilment[\"type\"][] = [];\n if (available?.pickup) {\n options.push(\"pickup\");\n }\n if (available?.delivery) {\n options.push(\"delivery\");\n }\n if (available?.dineIn) {\n options.push(\"dine_in\");\n }\n const setValue = (next: CartFulfilment) => {\n if (!options.includes(next.type) || next.branchId !== branch?.id) {\n throw new ZappieError(\n \"invalid_request\",\n \"Select an offered fulfilment mode for this branch\",\n );\n }\n cart.actions.setFulfilment(next);\n setSelection(next);\n };\n return part(\n \"div\",\n props,\n { options, value: current, setValue },\n { role: \"group\" },\n ref,\n );\n});\n/**\n * A fulfilment switch that exposes the active branch modes and updates the SDK cart.\n * Selections must use an offered mode and the active branch ID.\n */\nexport const Fulfilment = { Switch };\n"],
5
+ "mappings": ";;;AACA,SAAS,mBAAwC;AACjD,SAAS,SAAS,gBAAgB;AAClC,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AACrC,SAAS,4BAA4B;AAMrC,IAAM,SAAS,WAOb,SAASA,QAAO,EAAE,OAAO,cAAc,eAAe,GAAG,MAAM,GAAG,KAAK;AACvE,QAAM,OAAO,QAAQ;AACrB,QAAM,QAAQ,SAAS;AACvB,QAAM,CAAC,WAAW,YAAY,IAAI,qBAAqB;AAAA,IACrD;AAAA,IACA,cAAc,gBAAgB,KAAK;AAAA,IACnC,UAAU;AAAA,EACZ,CAAC;AACD,QAAM,UACJ,UAAU,UAAa,iBAAiB,SACpC,KAAK,aACL;AACN,QAAM,SACJ,MAAM,MAAM,SAAS;AAAA,IACnB,CAACC,YAAWA,QAAO,QAAQ,KAAK,YAAY,SAAS;AAAA,EACvD,KACA,MAAM,MAAM,SAAS,KAAK,CAACA,YAAWA,QAAO,SAAS,KACtD,MAAM,MAAM,SAAS,CAAC;AACxB,QAAM,YAAY,QAAQ;AAC1B,QAAM,UAAoC,CAAC;AAC3C,MAAI,WAAW,QAAQ;AACrB,YAAQ,KAAK,QAAQ;AAAA,EACvB;AACA,MAAI,WAAW,UAAU;AACvB,YAAQ,KAAK,UAAU;AAAA,EACzB;AACA,MAAI,WAAW,QAAQ;AACrB,YAAQ,KAAK,SAAS;AAAA,EACxB;AACA,QAAM,WAAW,CAAC,SAAyB;AACzC,QAAI,CAAC,QAAQ,SAAS,KAAK,IAAI,KAAK,KAAK,aAAa,QAAQ,IAAI;AAChE,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,SAAK,QAAQ,cAAc,IAAI;AAC/B,iBAAa,IAAI;AAAA,EACnB;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,SAAS,OAAO,SAAS,SAAS;AAAA,IACpC,EAAE,MAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AACF,CAAC;AAKM,IAAM,aAAa,EAAE,OAAO;",
6
+ "names": ["Switch", "branch"]
7
+ }
package/dist/index.d.ts CHANGED
@@ -802,9 +802,7 @@ declare const OpenStatus: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttr
802
802
  timezone?: string;
803
803
  } & react.RefAttributes<HTMLElement>>;
804
804
 
805
- /**
806
- * The foundations package version.
807
- */
808
- declare const FOUNDATIONS_VERSION = "0.0.0";
805
+ /** The installed @zappie/foundations version. */
806
+ declare const FOUNDATIONS_VERSION: string;
809
807
 
810
808
  export { Account, AccountNotConfiguredError, type ActiveGroup, Address, type AddressState, type AddressSuggestion, Branch, Cart, Choice, type Customer, FOUNDATIONS_VERSION, Fulfilment, Image, type ImageProps, ItemForm, type ItemFormContext, type ItemFormProps, Money, OpenStatus, OptionGroup, Otp, Overlay, PartProps, type PaymentMethod, PaymentMethods, Quantity, type QuantityProps, type QuantityState, Schedule, type ScheduleSlot, type ScheduleState, Search, type SearchResult, type SearchSource, type SearchState, type SignInOptions, Totals, type TotalsState, createSearchIndex, formatOpeningHours, formatWallClock, getOpenStatus, parseWallClock, scheduleSlots, useControllableState, useOverlay };