@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.
- package/CHANGELOG.md +35 -0
- package/README.md +30 -253
- package/dist/account/index.js +49 -0
- package/dist/account/index.js.map +7 -0
- package/dist/cart/index.js +168 -0
- package/dist/cart/index.js.map +7 -0
- package/dist/fulfilment/address-picker.js +168 -0
- package/dist/fulfilment/address-picker.js.map +7 -0
- package/dist/fulfilment/branch-picker.js +54 -0
- package/dist/fulfilment/branch-picker.js.map +7 -0
- package/dist/fulfilment/schedule.js +146 -0
- package/dist/fulfilment/schedule.js.map +7 -0
- package/dist/fulfilment/switch.js +54 -0
- package/dist/fulfilment/switch.js.map +7 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +49 -2113
- package/dist/index.js.map +7 -1
- package/dist/internal/combobox.js +131 -0
- package/dist/internal/combobox.js.map +7 -0
- package/dist/internal/data-attrs.js +8 -0
- package/dist/internal/data-attrs.js.map +7 -0
- package/dist/internal/ids.js +10 -0
- package/dist/internal/ids.js.map +7 -0
- package/dist/{chunk-D3J7S33L.js → internal/slot.js} +13 -6
- package/dist/internal/slot.js.map +7 -0
- package/dist/internal/use-controllable-state.js +29 -0
- package/dist/internal/use-controllable-state.js.map +7 -0
- package/dist/item-form/index.js +13 -0
- package/dist/item-form/index.js.map +7 -0
- package/dist/item-form/option-group.js +268 -0
- package/dist/item-form/option-group.js.map +7 -0
- package/dist/item-form/options.js +30 -0
- package/dist/item-form/options.js.map +7 -0
- package/dist/item-form/quantity.js +131 -0
- package/dist/item-form/quantity.js.map +7 -0
- package/dist/item-form/root.js +151 -0
- package/dist/item-form/root.js.map +7 -0
- package/dist/media/image.js +30 -0
- package/dist/media/image.js.map +7 -0
- package/dist/money.js +20 -0
- package/dist/money.js.map +7 -0
- package/dist/next.js +8 -6
- package/dist/next.js.map +7 -1
- package/dist/otp/input.js +110 -0
- package/dist/otp/input.js.map +7 -0
- package/dist/overlay/engine.js +16 -8
- package/dist/overlay/engine.js.map +7 -1
- package/dist/{chunk-BY2WQGLO.js → overlay/history.js} +9 -4
- package/dist/overlay/history.js.map +7 -0
- package/dist/overlay/react.js +261 -0
- package/dist/overlay/react.js.map +7 -0
- package/dist/payment/methods.js +103 -0
- package/dist/payment/methods.js.map +7 -0
- package/dist/rich-text/banners.js +9 -0
- package/dist/rich-text/banners.js.map +7 -0
- package/dist/rich-text/browser.js +3 -3
- package/dist/rich-text/browser.js.map +7 -1
- package/dist/rich-text/core.js +75 -0
- package/dist/rich-text/core.js.map +7 -0
- package/dist/rich-text/index.js +3 -79
- package/dist/rich-text/index.js.map +7 -1
- package/dist/search/engine.js +93 -0
- package/dist/search/engine.js.map +7 -0
- package/dist/search/index.js +175 -0
- package/dist/search/index.js.map +7 -0
- package/dist/status/hours.js +177 -0
- package/dist/status/hours.js.map +7 -0
- package/dist/status/open-status.js +45 -0
- package/dist/status/open-status.js.map +7 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +7 -0
- package/package.json +5 -4
- package/dist/chunk-BY2WQGLO.js.map +0 -1
- 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
|
-
|
|
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 };
|