@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.
- package/CHANGELOG.md +8 -0
- 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.js +48 -2114
- 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 +3 -3
- package/dist/chunk-BY2WQGLO.js.map +0 -1
- package/dist/chunk-D3J7S33L.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2119 +1,53 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { subscribeLocation, locationSnapshot, openOverlay, closeOverlay } from './chunk-BY2WQGLO.js';
|
|
3
|
-
import { part, composeRefs, partId } from './chunk-D3J7S33L.js';
|
|
4
|
-
import { forwardRef, createContext, useState, useEffect, useMemo, useContext, useRef, memo, useCallback, useId, useSyncExternalStore } from 'react';
|
|
5
|
-
import { useCartCatalogue, useCart, useZappie, useStore, useMenu } from '@zappie/sdk/react';
|
|
6
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
|
-
import { format, ZappieError, previewItem, ZappieCartError } from '@zappie/sdk';
|
|
8
|
-
import { srcSet, resized } from '@zappie/sdk/template';
|
|
9
|
-
|
|
10
|
-
// src/version.ts
|
|
11
|
-
var PACKAGE_VERSION = "0.1.1" ;
|
|
12
|
-
var AccountNotConfiguredError = class extends Error {
|
|
13
|
-
constructor() {
|
|
14
|
-
super("Account authentication requires SDK auth (Open Storefronts PRD 13)");
|
|
15
|
-
this.code = "not_configured";
|
|
16
|
-
this.name = "AccountNotConfiguredError";
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
async function signIn(_options) {
|
|
20
|
-
throw new AccountNotConfiguredError();
|
|
21
|
-
}
|
|
22
|
-
async function signOut(_options) {
|
|
23
|
-
throw new AccountNotConfiguredError();
|
|
24
|
-
}
|
|
25
|
-
var SignIn = forwardRef(function SignIn2(props, ref) {
|
|
26
|
-
return part(
|
|
27
|
-
"div",
|
|
28
|
-
props,
|
|
29
|
-
{ signIn, status: "not_configured" },
|
|
30
|
-
{ "data-state": "invalid" },
|
|
31
|
-
ref
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
var SignOut = forwardRef(function SignOut2(props, ref) {
|
|
35
|
-
return part(
|
|
36
|
-
"div",
|
|
37
|
-
props,
|
|
38
|
-
{ signOut, status: "not_configured" },
|
|
39
|
-
{ "data-state": "invalid" },
|
|
40
|
-
ref
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
var CustomerPart = forwardRef(function Customer(props, ref) {
|
|
44
|
-
return part(
|
|
45
|
-
"div",
|
|
46
|
-
props,
|
|
47
|
-
{ customer: null, status: "not_configured" },
|
|
48
|
-
{ "data-state": "invalid" },
|
|
49
|
-
ref
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
var Account = { SignIn, SignOut, Customer: CustomerPart };
|
|
53
|
-
|
|
54
|
-
// src/internal/data-attrs.ts
|
|
55
|
-
function flag(value) {
|
|
56
|
-
return value ? "" : void 0;
|
|
57
|
-
}
|
|
58
|
-
function useControllableState({
|
|
59
|
-
value,
|
|
60
|
-
defaultValue,
|
|
61
|
-
onChange
|
|
62
|
-
}) {
|
|
63
|
-
const [internal, setInternal] = useState(defaultValue);
|
|
64
|
-
const current = value === void 0 ? internal : value;
|
|
65
|
-
const latest = useRef({ current, onChange, controlled: value !== void 0 });
|
|
66
|
-
latest.current = { current, onChange, controlled: value !== void 0 };
|
|
67
|
-
const set = useCallback((next) => {
|
|
68
|
-
const snapshot = latest.current;
|
|
69
|
-
const resolved = typeof next === "function" ? next(snapshot.current) : next;
|
|
70
|
-
if (Object.is(resolved, snapshot.current)) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (!snapshot.controlled) {
|
|
74
|
-
latest.current.current = resolved;
|
|
75
|
-
setInternal(resolved);
|
|
76
|
-
}
|
|
77
|
-
snapshot.onChange?.(resolved);
|
|
78
|
-
}, []);
|
|
79
|
-
return [current, set];
|
|
80
|
-
}
|
|
81
|
-
var Context = createContext(null);
|
|
82
|
-
function useQuantity() {
|
|
83
|
-
const context = useContext(Context);
|
|
84
|
-
if (!context) {
|
|
85
|
-
throw new Error("Quantity parts require Quantity.Root");
|
|
86
|
-
}
|
|
87
|
-
return context;
|
|
88
|
-
}
|
|
89
|
-
function Root({
|
|
90
|
-
value,
|
|
91
|
-
defaultValue,
|
|
92
|
-
onValueChange,
|
|
93
|
-
min = 1,
|
|
94
|
-
max = 99,
|
|
95
|
-
step = 1,
|
|
96
|
-
disabled = false,
|
|
97
|
-
...props
|
|
98
|
-
}) {
|
|
99
|
-
if (!Number.isFinite(min) || !Number.isFinite(max) || !Number.isFinite(step) || step <= 0 || min > max) {
|
|
100
|
-
throw new RangeError(
|
|
101
|
-
"Quantity requires finite ordered bounds and a positive step"
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
const [current, set] = useControllableState({
|
|
105
|
-
value,
|
|
106
|
-
defaultValue: defaultValue ?? min,
|
|
107
|
-
onChange: onValueChange
|
|
108
|
-
});
|
|
109
|
-
const setValue = useCallback(
|
|
110
|
-
(next) => {
|
|
111
|
-
if (!disabled && Number.isFinite(next)) {
|
|
112
|
-
const last = min + Math.floor((max - min) / step) * step;
|
|
113
|
-
set(
|
|
114
|
-
Math.max(
|
|
115
|
-
min,
|
|
116
|
-
Math.min(last, min + Math.round((next - min) / step) * step)
|
|
117
|
-
)
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
[min, max, step, disabled, set]
|
|
122
|
-
);
|
|
123
|
-
const state = useMemo(
|
|
124
|
-
() => ({ value: current, setValue, min, max, step, disabled }),
|
|
125
|
-
[current, setValue, min, max, step, disabled]
|
|
126
|
-
);
|
|
127
|
-
return /* @__PURE__ */ jsx(Context.Provider, { value: state, children: part("div", props, state, { "data-disabled": flag(disabled) }) });
|
|
128
|
-
}
|
|
129
|
-
function button(direction) {
|
|
130
|
-
return forwardRef(
|
|
131
|
-
function QuantityButton(props, ref) {
|
|
132
|
-
const ctx = useQuantity();
|
|
133
|
-
const disabled = ctx.disabled || (direction < 0 ? ctx.value <= ctx.min : ctx.value + ctx.step > ctx.max);
|
|
134
|
-
return part(
|
|
135
|
-
"button",
|
|
136
|
-
props,
|
|
137
|
-
ctx,
|
|
138
|
-
{
|
|
139
|
-
type: "button",
|
|
140
|
-
disabled,
|
|
141
|
-
"aria-disabled": disabled,
|
|
142
|
-
"data-disabled": flag(disabled),
|
|
143
|
-
onClick: () => ctx.setValue(ctx.value + direction * ctx.step)
|
|
144
|
-
},
|
|
145
|
-
ref
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
var Value = forwardRef(
|
|
151
|
-
function Value2(props, ref) {
|
|
152
|
-
const ctx = useQuantity();
|
|
153
|
-
return part(
|
|
154
|
-
"input",
|
|
155
|
-
props,
|
|
156
|
-
ctx,
|
|
157
|
-
{
|
|
158
|
-
type: "text",
|
|
159
|
-
inputMode: "numeric",
|
|
160
|
-
role: "spinbutton",
|
|
161
|
-
value: String(ctx.value),
|
|
162
|
-
disabled: ctx.disabled,
|
|
163
|
-
"aria-valuenow": ctx.value,
|
|
164
|
-
"aria-valuemin": ctx.min,
|
|
165
|
-
"aria-valuemax": ctx.max,
|
|
166
|
-
onChange: (event) => {
|
|
167
|
-
if (event.target.value.trim()) {
|
|
168
|
-
ctx.setValue(Number(event.target.value));
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
onKeyDown: (event) => {
|
|
172
|
-
const next = {
|
|
173
|
-
ArrowUp: ctx.value + ctx.step,
|
|
174
|
-
ArrowDown: ctx.value - ctx.step,
|
|
175
|
-
Home: ctx.min,
|
|
176
|
-
End: ctx.max
|
|
177
|
-
}[event.key];
|
|
178
|
-
if (next !== void 0) {
|
|
179
|
-
event.preventDefault();
|
|
180
|
-
ctx.setValue(next);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
ref
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
);
|
|
188
|
-
var Quantity = {
|
|
189
|
-
Root,
|
|
190
|
-
Value,
|
|
191
|
-
Increment: button(1),
|
|
192
|
-
Decrement: button(-1)
|
|
193
|
-
};
|
|
194
|
-
var Context2 = createContext(null);
|
|
195
|
-
function useContextValue() {
|
|
196
|
-
const value = useContext(Context2);
|
|
197
|
-
if (!value) {
|
|
198
|
-
throw new Error("Cart parts require Cart.Root");
|
|
199
|
-
}
|
|
200
|
-
return value;
|
|
201
|
-
}
|
|
202
|
-
function Root2(props) {
|
|
203
|
-
const state = useCart();
|
|
204
|
-
return /* @__PURE__ */ jsx(Context2.Provider, { value: state, children: part("div", props, state, {
|
|
205
|
-
"data-state": !state.isHydrated ? "loading" : state.issues.some((issue) => issue.severity === "blocking") ? "invalid" : "valid"
|
|
206
|
-
}) });
|
|
207
|
-
}
|
|
208
|
-
var Line = forwardRef(function Line2({ line, ...props }, ref) {
|
|
209
|
-
const cart = useContextValue();
|
|
210
|
-
const item = useCartCatalogue()?.items[line.itemId];
|
|
211
|
-
const current = cart.lines.find((candidate) => candidate.id === line.id);
|
|
212
|
-
if (!current) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
const context = {
|
|
216
|
-
line: current,
|
|
217
|
-
item,
|
|
218
|
-
edit: (patch) => cart.actions.update(line.id, patch),
|
|
219
|
-
remove: () => cart.actions.remove(line.id)
|
|
220
|
-
};
|
|
221
|
-
return part(
|
|
222
|
-
"div",
|
|
223
|
-
props,
|
|
224
|
-
context,
|
|
225
|
-
{
|
|
226
|
-
"data-zappie-cart-line": line.id,
|
|
227
|
-
"data-invalid": cart.issues.some(
|
|
228
|
-
(issue) => issue.lineId === line.id && issue.severity === "blocking"
|
|
229
|
-
) ? "" : void 0
|
|
230
|
-
},
|
|
231
|
-
ref
|
|
232
|
-
);
|
|
233
|
-
});
|
|
234
|
-
function CartQuantity({
|
|
235
|
-
lineId,
|
|
236
|
-
...props
|
|
237
|
-
}) {
|
|
238
|
-
const cart = useContextValue();
|
|
239
|
-
const line = cart.lines.find((candidate) => candidate.id === lineId);
|
|
240
|
-
const item = useCartCatalogue()?.items[line?.itemId ?? ""];
|
|
241
|
-
if (!line) {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
const step = item?.quantity.step ?? 1;
|
|
245
|
-
const min = Math.ceil((item?.quantity.min ?? 1) / step) * step;
|
|
246
|
-
return /* @__PURE__ */ jsx(
|
|
247
|
-
Quantity.Root,
|
|
248
|
-
{
|
|
249
|
-
...props,
|
|
250
|
-
value: line.quantity,
|
|
251
|
-
min,
|
|
252
|
-
max: Math.min(item?.quantity.max ?? 99, 99),
|
|
253
|
-
step,
|
|
254
|
-
onValueChange: (quantity) => cart.actions.update(lineId, { quantity })
|
|
255
|
-
}
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
var Remove = forwardRef(
|
|
259
|
-
function Remove2({ lineId, ...props }, ref) {
|
|
260
|
-
const cart = useContextValue();
|
|
261
|
-
return part(
|
|
262
|
-
"button",
|
|
263
|
-
props,
|
|
264
|
-
{},
|
|
265
|
-
{ type: "button", onClick: () => cart.actions.remove(lineId) },
|
|
266
|
-
ref
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
);
|
|
270
|
-
var Empty = forwardRef(function Empty2(props, ref) {
|
|
271
|
-
const cart = useContextValue();
|
|
272
|
-
return cart.isHydrated && !cart.lines.length ? part("div", props, {}, {}, ref) : null;
|
|
273
|
-
});
|
|
274
|
-
var Issues = forwardRef(function Issues2(props, ref) {
|
|
275
|
-
const cart = useContextValue();
|
|
276
|
-
return part(
|
|
277
|
-
"div",
|
|
278
|
-
props,
|
|
279
|
-
{ issues: cart.issues, applyFix: cart.actions.applyFix },
|
|
280
|
-
{
|
|
281
|
-
"aria-live": "polite",
|
|
282
|
-
"data-state": cart.issues.length ? "invalid" : "valid"
|
|
283
|
-
},
|
|
284
|
-
ref
|
|
285
|
-
);
|
|
286
|
-
});
|
|
287
|
-
var Cart = {
|
|
288
|
-
Root: Root2,
|
|
289
|
-
Line,
|
|
290
|
-
Quantity: CartQuantity,
|
|
291
|
-
Remove,
|
|
292
|
-
Empty,
|
|
293
|
-
Issues
|
|
294
|
-
};
|
|
295
|
-
var Totals = forwardRef(
|
|
296
|
-
function Totals2(props, ref) {
|
|
297
|
-
const cart = useCart();
|
|
298
|
-
const client = useZappie();
|
|
299
|
-
const [failure, setFailure] = useState(null);
|
|
300
|
-
const [expired, setExpired] = useState(false);
|
|
301
|
-
const quote = "quote" in cart.state ? cart.state.quote : void 0;
|
|
302
|
-
useEffect(
|
|
303
|
-
() => client.on(
|
|
304
|
-
"quote_failed",
|
|
305
|
-
({ code }) => setFailure(
|
|
306
|
-
code === "network_error" || code === "timeout" ? "offline" : "stale"
|
|
307
|
-
)
|
|
308
|
-
),
|
|
309
|
-
[client]
|
|
310
|
-
);
|
|
311
|
-
useEffect(() => {
|
|
312
|
-
if (cart.state.status === "quoting" || quote) {
|
|
313
|
-
setFailure(null);
|
|
314
|
-
}
|
|
315
|
-
setExpired(Boolean(quote && Date.parse(quote.expiresAt) <= Date.now()));
|
|
316
|
-
if (!quote) {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const timeout = setTimeout(
|
|
320
|
-
() => setExpired(true),
|
|
321
|
-
Math.max(
|
|
322
|
-
0,
|
|
323
|
-
Math.min(2147483647, Date.parse(quote.expiresAt) - Date.now())
|
|
324
|
-
)
|
|
325
|
-
);
|
|
326
|
-
return () => clearTimeout(timeout);
|
|
327
|
-
}, [quote, cart.state.status]);
|
|
328
|
-
const pending = cart.state.status === "ready" && !quote && cart.lines.length > 0;
|
|
329
|
-
const status = failure === "offline" ? "offline" : cart.state.status === "quoting" || !cart.isHydrated ? "loading" : failure === "stale" || expired ? "stale" : pending ? "loading" : !quote ? "stale" : "ready";
|
|
330
|
-
return part(
|
|
331
|
-
"div",
|
|
332
|
-
props,
|
|
333
|
-
{ quote, status },
|
|
334
|
-
{
|
|
335
|
-
"aria-live": "polite",
|
|
336
|
-
"aria-busy": status === "loading",
|
|
337
|
-
"data-state": status === "ready" ? "valid" : status === "offline" ? "stale" : status
|
|
338
|
-
},
|
|
339
|
-
ref
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
);
|
|
343
|
-
function usePartId(provided) {
|
|
344
|
-
const id = useId();
|
|
345
|
-
return provided ?? `zappie-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
346
|
-
}
|
|
347
|
-
function useCombobox(results, onSelect) {
|
|
348
|
-
const id = usePartId();
|
|
349
|
-
const [activeIndex, setActiveIndex] = useState(-1);
|
|
350
|
-
const [isOpen, setOpen] = useState(false);
|
|
351
|
-
const [listId, setListId] = useState(`${id}-list`);
|
|
352
|
-
const [optionIds, setOptionIds] = useState({});
|
|
353
|
-
useEffect(() => setActiveIndex(-1), [results.length]);
|
|
354
|
-
const registerOption = (index, id2) => setOptionIds(
|
|
355
|
-
(previous) => previous[index] === id2 ? previous : { ...previous, [index]: id2 }
|
|
356
|
-
);
|
|
357
|
-
const active = activeIndex < results.length ? activeIndex : -1;
|
|
358
|
-
const optionId = (index) => optionIds[index] ?? `${id}-option-${index}`;
|
|
359
|
-
const activeId = isOpen && active >= 0 ? optionId(active) : void 0;
|
|
360
|
-
useEffect(() => {
|
|
361
|
-
if (activeId) {
|
|
362
|
-
document.getElementById(activeId)?.scrollIntoView?.({ block: "nearest" });
|
|
363
|
-
}
|
|
364
|
-
}, [activeId]);
|
|
365
|
-
const select = (index) => {
|
|
366
|
-
const item = results[index];
|
|
367
|
-
if (item !== void 0) {
|
|
368
|
-
onSelect(item);
|
|
369
|
-
setOpen(false);
|
|
370
|
-
setActiveIndex(-1);
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
return {
|
|
374
|
-
setListId,
|
|
375
|
-
registerOption,
|
|
376
|
-
activeIndex: active,
|
|
377
|
-
setActiveIndex,
|
|
378
|
-
isOpen,
|
|
379
|
-
setOpen,
|
|
380
|
-
select,
|
|
381
|
-
inputProps: {
|
|
382
|
-
role: "combobox",
|
|
383
|
-
"aria-autocomplete": "list",
|
|
384
|
-
"aria-expanded": isOpen,
|
|
385
|
-
"aria-controls": listId,
|
|
386
|
-
"aria-activedescendant": activeId,
|
|
387
|
-
onKeyDown: (event) => {
|
|
388
|
-
if (event.nativeEvent.isComposing || event.keyCode === 229) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (event.key === "Escape") {
|
|
392
|
-
event.preventDefault();
|
|
393
|
-
setOpen(false);
|
|
394
|
-
setActiveIndex(-1);
|
|
395
|
-
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
396
|
-
event.preventDefault();
|
|
397
|
-
setOpen(true);
|
|
398
|
-
setActiveIndex(
|
|
399
|
-
results.length ? active < 0 ? event.key === "ArrowDown" ? 0 : results.length - 1 : (active + (event.key === "ArrowDown" ? 1 : -1) + results.length) % results.length : -1
|
|
400
|
-
);
|
|
401
|
-
} else if (event.key === "Enter" && isOpen && active >= 0) {
|
|
402
|
-
event.preventDefault();
|
|
403
|
-
select(active);
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
onFocus: () => setOpen(true)
|
|
407
|
-
},
|
|
408
|
-
listProps: { id: listId, role: "listbox", hidden: !isOpen },
|
|
409
|
-
getOptionProps: (index) => ({
|
|
410
|
-
id: optionId(index),
|
|
411
|
-
role: "option",
|
|
412
|
-
"aria-selected": active === index,
|
|
413
|
-
"data-selected": active === index ? "" : void 0,
|
|
414
|
-
onMouseDown: (event) => event.preventDefault(),
|
|
415
|
-
// Inside a drawer or sheet, the overlay engine starts a swipe on any
|
|
416
|
-
// press that isn't a button, link or field, then captures the pointer,
|
|
417
|
-
// so the option's click never fires on touch. Options aren't swipe handles.
|
|
418
|
-
onPointerDown: (event) => event.stopPropagation(),
|
|
419
|
-
onTouchStart: (event) => event.stopPropagation(),
|
|
420
|
-
onClick: () => select(index)
|
|
421
|
-
})
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
var visuallyHidden = {
|
|
425
|
-
position: "absolute",
|
|
426
|
-
width: 1,
|
|
427
|
-
height: 1,
|
|
428
|
-
margin: -1,
|
|
429
|
-
padding: 0,
|
|
430
|
-
border: 0,
|
|
431
|
-
overflow: "hidden",
|
|
432
|
-
clip: "rect(0 0 0 0)",
|
|
433
|
-
whiteSpace: "nowrap"
|
|
434
|
-
};
|
|
435
|
-
function countAnnouncement(count) {
|
|
436
|
-
return count === 0 ? "No results" : `${count} result${count === 1 ? "" : "s"} available`;
|
|
437
|
-
}
|
|
438
|
-
function LiveRegion({ message }) {
|
|
439
|
-
return /* @__PURE__ */ jsx(
|
|
440
|
-
"span",
|
|
441
|
-
{
|
|
442
|
-
role: "status",
|
|
443
|
-
"aria-live": "polite",
|
|
444
|
-
"aria-atomic": "true",
|
|
445
|
-
"data-zappie-live": "",
|
|
446
|
-
style: visuallyHidden,
|
|
447
|
-
children: message
|
|
448
|
-
}
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
function withLiveRegion(props, message) {
|
|
452
|
-
if (props.asChild) {
|
|
453
|
-
return props;
|
|
454
|
-
}
|
|
455
|
-
const children = props.children;
|
|
456
|
-
return {
|
|
457
|
-
...props,
|
|
458
|
-
children: (state) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
459
|
-
typeof children === "function" ? children(state) : children,
|
|
460
|
-
/* @__PURE__ */ jsx(LiveRegion, { message })
|
|
461
|
-
] })
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
var Context3 = createContext(null);
|
|
465
|
-
function useAddress() {
|
|
466
|
-
const ctx = useContext(Context3);
|
|
467
|
-
if (!ctx) {
|
|
468
|
-
throw new Error("Address parts require Address.Picker");
|
|
469
|
-
}
|
|
470
|
-
return ctx;
|
|
471
|
-
}
|
|
472
|
-
function Picker({
|
|
473
|
-
getSuggestions,
|
|
474
|
-
onSelect,
|
|
475
|
-
query,
|
|
476
|
-
defaultQuery = "",
|
|
477
|
-
onQueryChange,
|
|
478
|
-
debounceMs = 200,
|
|
479
|
-
announcement = countAnnouncement,
|
|
480
|
-
...props
|
|
481
|
-
}) {
|
|
482
|
-
const [value, setValue] = useControllableState({
|
|
483
|
-
value: query,
|
|
484
|
-
defaultValue: defaultQuery,
|
|
485
|
-
onChange: onQueryChange
|
|
486
|
-
});
|
|
487
|
-
const [response, setResponse] = useState({ query: "", suggestions: [], status: "ready" });
|
|
488
|
-
const suggestions = response.query === value ? response.suggestions : [];
|
|
489
|
-
const combo = useCombobox(suggestions, (suggestion) => {
|
|
490
|
-
setValue(suggestion.label);
|
|
491
|
-
onSelect(suggestion);
|
|
492
|
-
});
|
|
493
|
-
useEffect(() => {
|
|
494
|
-
if (!value.trim()) {
|
|
495
|
-
setResponse({ query: value, suggestions: [], status: "ready" });
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
let live = true;
|
|
499
|
-
const abort = new AbortController();
|
|
500
|
-
setResponse({ query: value, suggestions: [], status: "loading" });
|
|
501
|
-
const timer = setTimeout(() => {
|
|
502
|
-
void Promise.resolve().then(() => getSuggestions(value, abort.signal)).then(
|
|
503
|
-
(items) => {
|
|
504
|
-
if (live) {
|
|
505
|
-
setResponse({
|
|
506
|
-
query: value,
|
|
507
|
-
suggestions: items,
|
|
508
|
-
status: "ready"
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
},
|
|
512
|
-
(error) => {
|
|
513
|
-
if (live) {
|
|
514
|
-
setResponse({
|
|
515
|
-
query: value,
|
|
516
|
-
suggestions: [],
|
|
517
|
-
status: "error",
|
|
518
|
-
error
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
);
|
|
523
|
-
}, debounceMs);
|
|
524
|
-
return () => {
|
|
525
|
-
live = false;
|
|
526
|
-
abort.abort();
|
|
527
|
-
clearTimeout(timer);
|
|
528
|
-
};
|
|
529
|
-
}, [value, getSuggestions, debounceMs]);
|
|
530
|
-
const setQuery = (next) => {
|
|
531
|
-
setValue(next);
|
|
532
|
-
combo.setOpen(true);
|
|
533
|
-
combo.setActiveIndex(-1);
|
|
534
|
-
};
|
|
535
|
-
const state = {
|
|
536
|
-
...combo,
|
|
537
|
-
query: value,
|
|
538
|
-
setQuery,
|
|
539
|
-
suggestions,
|
|
540
|
-
status: response.status,
|
|
541
|
-
error: response.error
|
|
542
|
-
};
|
|
543
|
-
return /* @__PURE__ */ jsx(Context3.Provider, { value: state, children: part(
|
|
544
|
-
"div",
|
|
545
|
-
withLiveRegion(
|
|
546
|
-
props,
|
|
547
|
-
combo.isOpen && value.trim() && response.status === "ready" ? announcement(suggestions.length) : ""
|
|
548
|
-
),
|
|
549
|
-
state,
|
|
550
|
-
{
|
|
551
|
-
"data-state": response.status === "error" ? "invalid" : response.status === "loading" ? "loading" : "valid",
|
|
552
|
-
onBlur: (event) => {
|
|
553
|
-
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
554
|
-
combo.setOpen(false);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
) });
|
|
559
|
-
}
|
|
560
|
-
var Input = forwardRef(
|
|
561
|
-
function Input2(props, ref) {
|
|
562
|
-
const state = useAddress();
|
|
563
|
-
return part(
|
|
564
|
-
"input",
|
|
565
|
-
props,
|
|
566
|
-
state,
|
|
567
|
-
{
|
|
568
|
-
...state.inputProps,
|
|
569
|
-
type: "text",
|
|
570
|
-
autoComplete: "street-address",
|
|
571
|
-
"data-zappie-address-input": "",
|
|
572
|
-
value: state.query,
|
|
573
|
-
onChange: (event) => state.setQuery(event.target.value)
|
|
574
|
-
},
|
|
575
|
-
ref
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
);
|
|
579
|
-
var Suggestions = forwardRef(
|
|
580
|
-
function Suggestions2(props, ref) {
|
|
581
|
-
const state = useAddress();
|
|
582
|
-
return part(
|
|
583
|
-
"div",
|
|
584
|
-
props,
|
|
585
|
-
state,
|
|
586
|
-
{ ...state.listProps, "aria-busy": state.status === "loading" },
|
|
587
|
-
composeRefs(ref, (node) => {
|
|
588
|
-
if (node?.id && node.id !== state.listProps.id) {
|
|
589
|
-
state.setListId(node.id);
|
|
590
|
-
}
|
|
591
|
-
})
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
);
|
|
595
|
-
var Suggestion = forwardRef(function Suggestion2({ index, ...props }, ref) {
|
|
596
|
-
const state = useAddress();
|
|
597
|
-
return part(
|
|
598
|
-
"div",
|
|
599
|
-
props,
|
|
600
|
-
state,
|
|
601
|
-
state.getOptionProps(index),
|
|
602
|
-
composeRefs(ref, (node) => {
|
|
603
|
-
if (node?.id && node.id !== state.getOptionProps(index).id) {
|
|
604
|
-
state.registerOption(index, node.id);
|
|
605
|
-
}
|
|
606
|
-
})
|
|
607
|
-
);
|
|
608
|
-
});
|
|
609
|
-
var Address = { Picker, Input, Suggestions, Suggestion };
|
|
610
|
-
|
|
611
|
-
// src/status/hours.ts
|
|
612
|
-
var DAY = 864e5;
|
|
613
|
-
var formatters = /* @__PURE__ */ new Map();
|
|
614
|
-
function formatter(timezone) {
|
|
615
|
-
let value = formatters.get(timezone);
|
|
616
|
-
if (!value) {
|
|
617
|
-
value = new Intl.DateTimeFormat("en-CA", {
|
|
618
|
-
timeZone: timezone,
|
|
619
|
-
year: "numeric",
|
|
620
|
-
month: "2-digit",
|
|
621
|
-
day: "2-digit",
|
|
622
|
-
hour: "2-digit",
|
|
623
|
-
minute: "2-digit",
|
|
624
|
-
second: "2-digit",
|
|
625
|
-
hourCycle: "h23"
|
|
626
|
-
});
|
|
627
|
-
formatters.set(timezone, value);
|
|
628
|
-
}
|
|
629
|
-
return value;
|
|
630
|
-
}
|
|
631
|
-
function localParts(date, timezone) {
|
|
632
|
-
const parts = Object.fromEntries(
|
|
633
|
-
formatter(timezone).formatToParts(date).map(({ type, value }) => [type, value])
|
|
634
|
-
);
|
|
635
|
-
return {
|
|
636
|
-
date: `${parts.year}-${parts.month}-${parts.day}`,
|
|
637
|
-
hour: Number(parts.hour),
|
|
638
|
-
minute: Number(parts.minute),
|
|
639
|
-
second: Number(parts.second)
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
function parseWallClock(value) {
|
|
643
|
-
const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
644
|
-
if (!match || Number(match[1]) > 23 || Number(match[2]) > 59 || Number(match[3] ?? 0) > 59) {
|
|
645
|
-
return void 0;
|
|
646
|
-
}
|
|
647
|
-
const hour24 = Number(match[1]);
|
|
648
|
-
return {
|
|
649
|
-
hour24,
|
|
650
|
-
hour12: hour24 % 12 || 12,
|
|
651
|
-
minute: Number(match[2]),
|
|
652
|
-
second: Number(match[3] ?? 0),
|
|
653
|
-
period: hour24 >= 12 ? "PM" : "AM"
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
function formatWallClock(value) {
|
|
657
|
-
const time = parseWallClock(value);
|
|
658
|
-
return time ? `${time.hour12}${time.minute ? `:${String(time.minute).padStart(2, "0")}` : ""} ${time.period}` : value;
|
|
659
|
-
}
|
|
660
|
-
function formatOpeningHours(times) {
|
|
661
|
-
const names = [
|
|
662
|
-
"Sunday",
|
|
663
|
-
"Monday",
|
|
664
|
-
"Tuesday",
|
|
665
|
-
"Wednesday",
|
|
666
|
-
"Thursday",
|
|
667
|
-
"Friday",
|
|
668
|
-
"Saturday"
|
|
669
|
-
];
|
|
670
|
-
const groups = [];
|
|
671
|
-
for (let day = 0; day < 7; day++) {
|
|
672
|
-
const intervals = times.filter((time) => !time.disabled && time.day === day).sort((a, b) => a.opens.localeCompare(b.opens));
|
|
673
|
-
if (!intervals.length) {
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
const hoursLabel = intervals.map(
|
|
677
|
-
(time) => `${formatWallClock(time.opens)} \u2013 ${formatWallClock(time.closes)}`
|
|
678
|
-
).join(", ");
|
|
679
|
-
const last = groups[groups.length - 1];
|
|
680
|
-
if (last && last.days[last.days.length - 1] === day - 1 && last.hoursLabel === hoursLabel) {
|
|
681
|
-
last.days.push(day);
|
|
682
|
-
last.dayLabel = `${names[last.days[0]]} \u2013 ${names[day]}`;
|
|
683
|
-
} else {
|
|
684
|
-
groups.push({ days: [day], dayLabel: names[day], hoursLabel });
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
return groups;
|
|
688
|
-
}
|
|
689
|
-
function dateOffset(date, days) {
|
|
690
|
-
return new Date(Date.parse(`${date}T00:00:00Z`) + days * DAY).toISOString().slice(0, 10);
|
|
691
|
-
}
|
|
692
|
-
function wallInstants(date, clock, timezone) {
|
|
693
|
-
const time = parseWallClock(clock);
|
|
694
|
-
if (!time) {
|
|
695
|
-
return [];
|
|
696
|
-
}
|
|
697
|
-
const wall = Date.parse(`${date}T00:00:00Z`) + ((time.hour24 * 60 + time.minute) * 60 + time.second) * 1e3;
|
|
698
|
-
const offsets = /* @__PURE__ */ new Set();
|
|
699
|
-
for (const shift of [-DAY, 0, DAY]) {
|
|
700
|
-
const sample = wall + shift;
|
|
701
|
-
const local = localParts(new Date(sample), timezone);
|
|
702
|
-
const asUTC = Date.parse(`${local.date}T00:00:00Z`) + ((local.hour * 60 + local.minute) * 60 + local.second) * 1e3;
|
|
703
|
-
offsets.add(asUTC - sample);
|
|
704
|
-
}
|
|
705
|
-
const matches = [];
|
|
706
|
-
for (const offset of offsets) {
|
|
707
|
-
const instant = wall - offset;
|
|
708
|
-
const local = localParts(new Date(instant), timezone);
|
|
709
|
-
if (local.date === date && local.hour === time.hour24 && local.minute === time.minute && local.second === time.second) {
|
|
710
|
-
matches.push(instant);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
return matches.sort((a, b) => a - b);
|
|
714
|
-
}
|
|
715
|
-
function openingIntervals(branch, timezone, now, days = 8) {
|
|
716
|
-
const today = localParts(now, timezone).date;
|
|
717
|
-
const intervals = [];
|
|
718
|
-
for (let offset = -1; offset < days; offset++) {
|
|
719
|
-
const date = dateOffset(today, offset);
|
|
720
|
-
const day = (/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay();
|
|
721
|
-
const special = branch.specialHours?.find((hours) => hours.date === date);
|
|
722
|
-
const times = special ? special.closed || !special.opens || !special.closes ? [] : [{ opens: special.opens, closes: special.closes }] : branch.openingTimes.filter(
|
|
723
|
-
(hours) => hours.day === day && !hours.disabled
|
|
724
|
-
);
|
|
725
|
-
for (const hours of times) {
|
|
726
|
-
const starts = wallInstants(date, hours.opens, timezone);
|
|
727
|
-
const overnight = hours.closes <= hours.opens;
|
|
728
|
-
const endDate = overnight ? dateOffset(date, 1) : date;
|
|
729
|
-
const ends = wallInstants(endDate, hours.closes, timezone);
|
|
730
|
-
const start = starts[0];
|
|
731
|
-
let end = ends[ends.length - 1];
|
|
732
|
-
if (overnight && branch.specialHours?.some(
|
|
733
|
-
(hours2) => hours2.date === endDate && hours2.closed
|
|
734
|
-
)) {
|
|
735
|
-
end = wallInstants(endDate, "00:00", timezone)[0];
|
|
736
|
-
}
|
|
737
|
-
if (start !== void 0 && end !== void 0 && start < end) {
|
|
738
|
-
intervals.push({ start, end });
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
const closedUntil = branch.closedUntil ? Date.parse(branch.closedUntil) : -Infinity;
|
|
743
|
-
const sorted = intervals.map(({ start, end }) => ({ start: Math.max(start, closedUntil), end })).filter(({ start, end }) => start < end).sort((a, b) => a.start - b.start);
|
|
744
|
-
const merged = [];
|
|
745
|
-
for (const interval of sorted) {
|
|
746
|
-
const last = merged[merged.length - 1];
|
|
747
|
-
if (last && interval.start <= last.end) {
|
|
748
|
-
last.end = Math.max(last.end, interval.end);
|
|
749
|
-
} else {
|
|
750
|
-
merged.push({ ...interval });
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
return merged;
|
|
754
|
-
}
|
|
755
|
-
function getOpenStatus(branch, timezone, now = /* @__PURE__ */ new Date()) {
|
|
756
|
-
const intervals = openingIntervals(branch, timezone, now);
|
|
757
|
-
const current = intervals.find(
|
|
758
|
-
({ start, end }) => start <= now.getTime() && end > now.getTime()
|
|
759
|
-
);
|
|
760
|
-
const next = current?.end ?? intervals.find(({ start }) => start > now.getTime())?.start;
|
|
761
|
-
if (next === void 0) {
|
|
762
|
-
return { isOpen: false, label: "Closed" };
|
|
763
|
-
}
|
|
764
|
-
const nextChange = new Date(next);
|
|
765
|
-
const local = localParts(nextChange, timezone);
|
|
766
|
-
const clock = formatWallClock(
|
|
767
|
-
`${String(local.hour).padStart(2, "0")}:${String(local.minute).padStart(2, "0")}`
|
|
768
|
-
).toLowerCase().replace(" ", "");
|
|
769
|
-
const today = localParts(now, timezone).date;
|
|
770
|
-
const suffix = local.date === today ? "" : local.date === dateOffset(today, 1) ? " tomorrow" : ` ${new Intl.DateTimeFormat("en", { timeZone: timezone, weekday: "long" }).format(nextChange)}`;
|
|
771
|
-
return {
|
|
772
|
-
isOpen: Boolean(current),
|
|
773
|
-
label: current ? `Open until ${clock}` : `Opens ${clock}${suffix}`,
|
|
774
|
-
nextChange
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// src/fulfilment/branch-picker.tsx
|
|
779
|
-
var Picker2 = forwardRef(function Picker3({ branches, value, onSelect, ...props }, ref) {
|
|
780
|
-
const cart = useCart();
|
|
781
|
-
const store = useStore();
|
|
782
|
-
const [isLoading, setLoading] = useState(false);
|
|
783
|
-
const [error, setError] = useState();
|
|
784
|
-
const selected = branches.find(
|
|
785
|
-
(branch) => branch.id === (value ?? cart.branchId)
|
|
786
|
-
);
|
|
787
|
-
const select = async (branch) => {
|
|
788
|
-
if (isLoading || branch.id === cart.branchId || !branches.some((candidate) => candidate.id === branch.id)) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
setLoading(true);
|
|
792
|
-
setError(void 0);
|
|
793
|
-
try {
|
|
794
|
-
await cart.actions.moveTo(branch.id);
|
|
795
|
-
onSelect?.(branch);
|
|
796
|
-
} catch (failure) {
|
|
797
|
-
setError(failure);
|
|
798
|
-
} finally {
|
|
799
|
-
setLoading(false);
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
return part(
|
|
803
|
-
"div",
|
|
804
|
-
props,
|
|
805
|
-
{
|
|
806
|
-
branches,
|
|
807
|
-
selected,
|
|
808
|
-
select,
|
|
809
|
-
isLoading,
|
|
810
|
-
error,
|
|
811
|
-
isOpenNow: (branch) => getOpenStatus(branch, branch.timezone ?? store.data?.timezone ?? "UTC").isOpen
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
role: "group",
|
|
815
|
-
"aria-busy": isLoading,
|
|
816
|
-
"data-state": isLoading ? "loading" : error ? "invalid" : "valid"
|
|
817
|
-
},
|
|
818
|
-
ref
|
|
819
|
-
);
|
|
820
|
-
});
|
|
821
|
-
var Branch = { Picker: Picker2 };
|
|
822
|
-
function scheduleSlots(branch, timezone, now, {
|
|
823
|
-
intervalMinutes = 30,
|
|
824
|
-
maxDays = 14,
|
|
825
|
-
noticeMinutes = 0,
|
|
826
|
-
menuSchedule
|
|
827
|
-
} = {}) {
|
|
828
|
-
if (!Number.isInteger(intervalMinutes) || intervalMinutes < 1 || !Number.isInteger(maxDays) || maxDays < 1 || maxDays > 90 || !Number.isFinite(noticeMinutes) || noticeMinutes < 0) {
|
|
829
|
-
throw new RangeError(
|
|
830
|
-
"Schedule requires a positive interval, 1\u201390 days and a non-negative notice"
|
|
831
|
-
);
|
|
832
|
-
}
|
|
833
|
-
if (!branch.fulfilment.scheduling) {
|
|
834
|
-
return [];
|
|
835
|
-
}
|
|
836
|
-
const preorder = menuSchedule?.preorder ? menuSchedule : void 0;
|
|
837
|
-
const days = preorder?.preorderMaxDays && preorder.preorderMaxDays > 0 ? Math.min(maxDays, preorder.preorderMaxDays) : maxDays;
|
|
838
|
-
const earliest = now.getTime() + Math.max(noticeMinutes, (preorder?.preorderNoticeHours ?? 0) * 60) * 6e4;
|
|
839
|
-
const latest = now.getTime() + days * 864e5;
|
|
840
|
-
const menuIntervals = menuSchedule?.openingTimes.length ? openingIntervals(
|
|
841
|
-
{
|
|
842
|
-
...branch,
|
|
843
|
-
openingTimes: menuSchedule.openingTimes,
|
|
844
|
-
specialHours: []
|
|
845
|
-
},
|
|
846
|
-
timezone,
|
|
847
|
-
now,
|
|
848
|
-
days + 1
|
|
849
|
-
) : void 0;
|
|
850
|
-
const slots = [];
|
|
851
|
-
for (const { start, end } of openingIntervals(
|
|
852
|
-
branch,
|
|
853
|
-
timezone,
|
|
854
|
-
now,
|
|
855
|
-
days + 1
|
|
856
|
-
)) {
|
|
857
|
-
const first = start + Math.max(0, Math.ceil((earliest - start) / (intervalMinutes * 6e4))) * intervalMinutes * 6e4;
|
|
858
|
-
for (let instant = first; instant < end && instant <= latest; instant += intervalMinutes * 6e4) {
|
|
859
|
-
if (menuIntervals && !menuIntervals.some(
|
|
860
|
-
(interval) => interval.start <= instant && interval.end > instant
|
|
861
|
-
)) {
|
|
862
|
-
continue;
|
|
863
|
-
}
|
|
864
|
-
const date = new Date(instant);
|
|
865
|
-
slots.push({
|
|
866
|
-
value: date.toISOString(),
|
|
867
|
-
date: localParts(date, timezone).date,
|
|
868
|
-
label: new Intl.DateTimeFormat("en", {
|
|
869
|
-
timeZone: timezone,
|
|
870
|
-
hour: "numeric",
|
|
871
|
-
minute: "2-digit",
|
|
872
|
-
timeZoneName: "shortOffset"
|
|
873
|
-
}).format(date)
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
return slots;
|
|
878
|
-
}
|
|
879
|
-
var Schedule = forwardRef(function Schedule2({
|
|
880
|
-
branch,
|
|
881
|
-
timezone: suppliedTimezone,
|
|
882
|
-
value,
|
|
883
|
-
defaultValue = null,
|
|
884
|
-
onValueChange,
|
|
885
|
-
intervalMinutes,
|
|
886
|
-
maxDays,
|
|
887
|
-
noticeMinutes,
|
|
888
|
-
...props
|
|
889
|
-
}, ref) {
|
|
890
|
-
const store = useStore();
|
|
891
|
-
const menu = useMenu(branch.id, { metadataOnly: true });
|
|
892
|
-
const timezone = suppliedTimezone ?? branch.timezone ?? store.data?.timezone ?? "UTC";
|
|
893
|
-
const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
|
|
894
|
-
useEffect(() => {
|
|
895
|
-
const timer = setTimeout(
|
|
896
|
-
() => setNow(/* @__PURE__ */ new Date()),
|
|
897
|
-
6e4 - Date.now() % 6e4
|
|
898
|
-
);
|
|
899
|
-
return () => clearTimeout(timer);
|
|
900
|
-
}, [now]);
|
|
901
|
-
const [selection, setSelection] = useControllableState({
|
|
902
|
-
value,
|
|
903
|
-
defaultValue,
|
|
904
|
-
onChange: onValueChange
|
|
905
|
-
});
|
|
906
|
-
const slots = useMemo(
|
|
907
|
-
() => menu.isLoading || menu.error ? [] : scheduleSlots(branch, timezone, now, {
|
|
908
|
-
intervalMinutes,
|
|
909
|
-
maxDays,
|
|
910
|
-
noticeMinutes,
|
|
911
|
-
menuSchedule: menu.data?.schedule
|
|
912
|
-
}),
|
|
913
|
-
[
|
|
914
|
-
branch,
|
|
915
|
-
timezone,
|
|
916
|
-
now,
|
|
917
|
-
intervalMinutes,
|
|
918
|
-
maxDays,
|
|
919
|
-
noticeMinutes,
|
|
920
|
-
menu.data?.schedule,
|
|
921
|
-
menu.isLoading,
|
|
922
|
-
menu.error
|
|
923
|
-
]
|
|
924
|
-
);
|
|
925
|
-
const days = Array.from(new Set(slots.map((slot) => slot.date))).map(
|
|
926
|
-
(date) => ({ date, slots: slots.filter((slot) => slot.date === date) })
|
|
927
|
-
);
|
|
928
|
-
const canAsap = !menu.isLoading && !menu.error && !menu.data?.schedule?.preorder && getOpenStatus(branch, timezone, now).isOpen;
|
|
929
|
-
const isValid = selection === null ? canAsap : slots.some((slot) => slot.value === selection);
|
|
930
|
-
const setValue = (next) => {
|
|
931
|
-
if (next === null ? canAsap : slots.some((slot) => slot.value === next)) {
|
|
932
|
-
setSelection(next);
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
return part(
|
|
936
|
-
"div",
|
|
937
|
-
props,
|
|
938
|
-
{
|
|
939
|
-
days,
|
|
940
|
-
slots,
|
|
941
|
-
value: selection,
|
|
942
|
-
setValue,
|
|
943
|
-
isAsap: selection === null,
|
|
944
|
-
canAsap,
|
|
945
|
-
isValid
|
|
946
|
-
},
|
|
947
|
-
{
|
|
948
|
-
role: "group",
|
|
949
|
-
"data-state": isValid ? "valid" : "invalid",
|
|
950
|
-
"data-invalid": isValid ? void 0 : ""
|
|
951
|
-
},
|
|
952
|
-
ref
|
|
953
|
-
);
|
|
954
|
-
});
|
|
955
|
-
var Switch = forwardRef(function Switch2({ value, defaultValue, onValueChange, ...props }, ref) {
|
|
956
|
-
const cart = useCart();
|
|
957
|
-
const store = useStore();
|
|
958
|
-
const [selection, setSelection] = useControllableState({
|
|
959
|
-
value,
|
|
960
|
-
defaultValue: defaultValue ?? cart.fulfilment,
|
|
961
|
-
onChange: onValueChange
|
|
962
|
-
});
|
|
963
|
-
const current = value === void 0 && defaultValue === void 0 ? cart.fulfilment : selection;
|
|
964
|
-
const branch = store.data?.branches.find(
|
|
965
|
-
(branch2) => branch2.id === (cart.branchId ?? current?.branchId)
|
|
966
|
-
) ?? store.data?.branches.find((branch2) => branch2.isDefault) ?? store.data?.branches[0];
|
|
967
|
-
const available = branch?.fulfilment;
|
|
968
|
-
const options = [];
|
|
969
|
-
if (available?.pickup) {
|
|
970
|
-
options.push("pickup");
|
|
971
|
-
}
|
|
972
|
-
if (available?.delivery) {
|
|
973
|
-
options.push("delivery");
|
|
974
|
-
}
|
|
975
|
-
if (available?.dineIn) {
|
|
976
|
-
options.push("dine_in");
|
|
977
|
-
}
|
|
978
|
-
const setValue = (next) => {
|
|
979
|
-
if (!options.includes(next.type) || next.branchId !== branch?.id) {
|
|
980
|
-
throw new ZappieError(
|
|
981
|
-
"invalid_request",
|
|
982
|
-
"Select an offered fulfilment mode for this branch"
|
|
983
|
-
);
|
|
984
|
-
}
|
|
985
|
-
cart.actions.setFulfilment(next);
|
|
986
|
-
setSelection(next);
|
|
987
|
-
};
|
|
988
|
-
return part(
|
|
989
|
-
"div",
|
|
990
|
-
props,
|
|
991
|
-
{ options, value: current, setValue },
|
|
992
|
-
{ role: "group" },
|
|
993
|
-
ref
|
|
994
|
-
);
|
|
995
|
-
});
|
|
996
|
-
var Fulfilment = { Switch };
|
|
997
|
-
var ItemFormStateContext = createContext(null);
|
|
998
|
-
function useItemForm() {
|
|
999
|
-
const value = useContext(ItemFormStateContext);
|
|
1000
|
-
if (!value) {
|
|
1001
|
-
throw new Error("ItemForm parts require ItemForm.Root");
|
|
1002
|
-
}
|
|
1003
|
-
return value;
|
|
1004
|
-
}
|
|
1005
|
-
var GroupRegistry = createContext(
|
|
1006
|
-
null
|
|
1007
|
-
);
|
|
1008
|
-
function Root3({
|
|
1009
|
-
item,
|
|
1010
|
-
value,
|
|
1011
|
-
defaultValue,
|
|
1012
|
-
onValueChange,
|
|
1013
|
-
defaultQuantity,
|
|
1014
|
-
onAdded,
|
|
1015
|
-
onCommit,
|
|
1016
|
-
...props
|
|
1017
|
-
}) {
|
|
1018
|
-
const client = useZappie();
|
|
1019
|
-
const menu = useCartCatalogue();
|
|
1020
|
-
const [input, setInput] = useControllableState({
|
|
1021
|
-
value,
|
|
1022
|
-
defaultValue: {
|
|
1023
|
-
...defaultValue,
|
|
1024
|
-
itemId: item.id,
|
|
1025
|
-
quantity: defaultQuantity ?? defaultValue?.quantity ?? item.quantity.min
|
|
1026
|
-
},
|
|
1027
|
-
onChange: onValueChange
|
|
1028
|
-
});
|
|
1029
|
-
const registry = useRef(/* @__PURE__ */ new Map());
|
|
1030
|
-
const [added, setAdded] = useState(false);
|
|
1031
|
-
const [attempted, setAttempted] = useState(false);
|
|
1032
|
-
const [failure, setFailure] = useState(null);
|
|
1033
|
-
const normalized = useMemo(
|
|
1034
|
-
() => ({ ...input, itemId: item.id }),
|
|
1035
|
-
[input, item.id]
|
|
1036
|
-
);
|
|
1037
|
-
const preview = useMemo(
|
|
1038
|
-
() => previewItem(item, normalized),
|
|
1039
|
-
[item, normalized]
|
|
1040
|
-
);
|
|
1041
|
-
const validation = useMemo(
|
|
1042
|
-
() => client.cart.validate(normalized),
|
|
1043
|
-
[client, normalized, menu]
|
|
1044
|
-
);
|
|
1045
|
-
const issues = failure ?? (attempted && !validation.ok ? validation.issues : []);
|
|
1046
|
-
const setValue = useCallback(
|
|
1047
|
-
(next) => {
|
|
1048
|
-
setAdded(false);
|
|
1049
|
-
setFailure(null);
|
|
1050
|
-
setInput({ ...next, itemId: item.id });
|
|
1051
|
-
},
|
|
1052
|
-
[setInput, item.id]
|
|
1053
|
-
);
|
|
1054
|
-
const setQuantity = useCallback(
|
|
1055
|
-
(quantity) => setValue({ ...normalized, quantity }),
|
|
1056
|
-
[setValue, normalized]
|
|
1057
|
-
);
|
|
1058
|
-
const focusIssue = useCallback((errors) => {
|
|
1059
|
-
for (const [groupId, element] of registry.current) {
|
|
1060
|
-
if (errors.some((issue) => issue.groupId === groupId)) {
|
|
1061
|
-
element.focus();
|
|
1062
|
-
break;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}, []);
|
|
1066
|
-
const add = useCallback(() => {
|
|
1067
|
-
const result = client.cart.validate(normalized);
|
|
1068
|
-
if (!result.ok) {
|
|
1069
|
-
setAttempted(true);
|
|
1070
|
-
setFailure(result.issues);
|
|
1071
|
-
focusIssue(result.issues);
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
try {
|
|
1075
|
-
const line = onCommit ? onCommit(normalized) : client.cart.add(item, normalized);
|
|
1076
|
-
setAttempted(false);
|
|
1077
|
-
setAdded(true);
|
|
1078
|
-
onAdded?.(line);
|
|
1079
|
-
return line;
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
if (error instanceof ZappieCartError) {
|
|
1082
|
-
setAttempted(true);
|
|
1083
|
-
setFailure([...error.issues]);
|
|
1084
|
-
focusIssue([...error.issues]);
|
|
1085
|
-
return null;
|
|
1086
|
-
}
|
|
1087
|
-
throw error;
|
|
1088
|
-
}
|
|
1089
|
-
}, [client, normalized, item, onAdded, onCommit, focusIssue]);
|
|
1090
|
-
const context = useMemo(
|
|
1091
|
-
() => ({
|
|
1092
|
-
item,
|
|
1093
|
-
...preview,
|
|
1094
|
-
quantity: normalized.quantity ?? item.quantity.min,
|
|
1095
|
-
setQuantity,
|
|
1096
|
-
issues,
|
|
1097
|
-
canAdd: validation.ok && !failure,
|
|
1098
|
-
add,
|
|
1099
|
-
status: added ? "added" : validation.ok ? "valid" : "incomplete",
|
|
1100
|
-
value: normalized,
|
|
1101
|
-
setValue
|
|
1102
|
-
}),
|
|
1103
|
-
[
|
|
1104
|
-
item,
|
|
1105
|
-
preview,
|
|
1106
|
-
normalized,
|
|
1107
|
-
setQuantity,
|
|
1108
|
-
issues,
|
|
1109
|
-
validation.ok,
|
|
1110
|
-
failure,
|
|
1111
|
-
add,
|
|
1112
|
-
added,
|
|
1113
|
-
setValue
|
|
1114
|
-
]
|
|
1115
|
-
);
|
|
1116
|
-
return /* @__PURE__ */ jsx(ItemFormStateContext.Provider, { value: context, children: /* @__PURE__ */ jsx(GroupRegistry.Provider, { value: registry.current, children: part("div", props, context, {
|
|
1117
|
-
"data-zappie-item": item.id,
|
|
1118
|
-
"data-state": issues.length ? "invalid" : "valid",
|
|
1119
|
-
"data-sold-out": item.isAvailable ? void 0 : ""
|
|
1120
|
-
}) }) });
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// src/item-form/options.tsx
|
|
1124
|
-
var Options = forwardRef(
|
|
1125
|
-
function Options2(props, ref) {
|
|
1126
|
-
return part("div", props, useItemForm(), {}, ref);
|
|
1127
|
-
}
|
|
1128
|
-
);
|
|
1129
|
-
var Add = forwardRef(
|
|
1130
|
-
function Add2(props, ref) {
|
|
1131
|
-
const context = useItemForm();
|
|
1132
|
-
return part(
|
|
1133
|
-
"button",
|
|
1134
|
-
props,
|
|
1135
|
-
context,
|
|
1136
|
-
{
|
|
1137
|
-
type: "button",
|
|
1138
|
-
onClick: context.add,
|
|
1139
|
-
"data-zappie-action": "add-to-cart"
|
|
1140
|
-
},
|
|
1141
|
-
ref
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
);
|
|
1145
|
-
var Context4 = createContext(null);
|
|
1146
|
-
function useGroup() {
|
|
1147
|
-
const value = useContext(Context4);
|
|
1148
|
-
if (!value) {
|
|
1149
|
-
throw new Error("Choice and OptionGroup.Input require OptionGroup");
|
|
1150
|
-
}
|
|
1151
|
-
return value;
|
|
1152
|
-
}
|
|
1153
|
-
var Group = forwardRef(function OptionGroup({ group: supplied, ...props }, ref) {
|
|
1154
|
-
const ctx = useItemForm();
|
|
1155
|
-
const registry = useContext(GroupRegistry);
|
|
1156
|
-
const group = ctx.groups.find((candidate) => candidate.id === supplied.id) ?? supplied;
|
|
1157
|
-
const mine = ctx.value.choices?.filter((choice) => choice.groupId === group.id) ?? [];
|
|
1158
|
-
const selected = mine.map((choice) => choice.choiceId);
|
|
1159
|
-
const quantities = Object.fromEntries(
|
|
1160
|
-
mine.map((choice) => [choice.choiceId, choice.quantity ?? 1])
|
|
1161
|
-
);
|
|
1162
|
-
const invalid = ctx.issues.some((issue) => issue.groupId === group.id);
|
|
1163
|
-
const input = ctx.value.inputs?.[group.id] ?? "";
|
|
1164
|
-
const latest = useRef({ ctx, group });
|
|
1165
|
-
latest.current = { ctx, group };
|
|
1166
|
-
const actions = useMemo(
|
|
1167
|
-
() => ({
|
|
1168
|
-
toggle: (id) => {
|
|
1169
|
-
const { ctx: ctx2, group: group2 } = latest.current;
|
|
1170
|
-
const choice = group2.choices.find((candidate) => candidate.id === id);
|
|
1171
|
-
if (!choice?.isAvailable) {
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
const previous = ctx2.value.choices ?? [];
|
|
1175
|
-
const chosen = previous.some(
|
|
1176
|
-
(candidate) => candidate.groupId === group2.id && candidate.choiceId === id
|
|
1177
|
-
);
|
|
1178
|
-
let choices = previous.filter(
|
|
1179
|
-
(candidate) => candidate.groupId !== group2.id || group2.selection.max !== 1 && candidate.choiceId !== id
|
|
1180
|
-
);
|
|
1181
|
-
if (!chosen || group2.selection.max === 1 && group2.required) {
|
|
1182
|
-
choices = [
|
|
1183
|
-
...choices,
|
|
1184
|
-
{
|
|
1185
|
-
groupId: group2.id,
|
|
1186
|
-
choiceId: id,
|
|
1187
|
-
quantity: choice.quantity?.min ?? 1
|
|
1188
|
-
}
|
|
1189
|
-
];
|
|
1190
|
-
}
|
|
1191
|
-
const inputs = { ...ctx2.value.inputs };
|
|
1192
|
-
delete inputs[group2.id];
|
|
1193
|
-
ctx2.setValue({ ...ctx2.value, choices, inputs });
|
|
1194
|
-
},
|
|
1195
|
-
setQuantity: (id, quantity) => {
|
|
1196
|
-
const { ctx: ctx2, group: group2 } = latest.current;
|
|
1197
|
-
const choice = group2.choices.find((candidate) => candidate.id === id);
|
|
1198
|
-
if (!choice?.isAvailable || !Number.isInteger(quantity)) {
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
const others = (ctx2.value.choices ?? []).filter(
|
|
1202
|
-
(candidate) => candidate.groupId !== group2.id || group2.selection.max !== 1 && candidate.choiceId !== id
|
|
1203
|
-
);
|
|
1204
|
-
if (quantity <= 0) {
|
|
1205
|
-
ctx2.setValue({
|
|
1206
|
-
...ctx2.value,
|
|
1207
|
-
choices: (ctx2.value.choices ?? []).filter(
|
|
1208
|
-
(candidate) => candidate.groupId !== group2.id || candidate.choiceId !== id
|
|
1209
|
-
)
|
|
1210
|
-
});
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
const min = choice.quantity?.min ?? 1;
|
|
1214
|
-
const max = choice.quantity?.max ?? 99;
|
|
1215
|
-
ctx2.setValue({
|
|
1216
|
-
...ctx2.value,
|
|
1217
|
-
choices: [
|
|
1218
|
-
...others,
|
|
1219
|
-
{
|
|
1220
|
-
groupId: group2.id,
|
|
1221
|
-
choiceId: id,
|
|
1222
|
-
quantity: Math.min(max, Math.max(min, quantity))
|
|
1223
|
-
}
|
|
1224
|
-
]
|
|
1225
|
-
});
|
|
1226
|
-
},
|
|
1227
|
-
setInput: (value) => {
|
|
1228
|
-
const { ctx: ctx2, group: group2 } = latest.current;
|
|
1229
|
-
ctx2.setValue({
|
|
1230
|
-
...ctx2.value,
|
|
1231
|
-
choices: (ctx2.value.choices ?? []).filter(
|
|
1232
|
-
(choice) => choice.groupId !== group2.id
|
|
1233
|
-
),
|
|
1234
|
-
inputs: {
|
|
1235
|
-
...ctx2.value.inputs,
|
|
1236
|
-
[group2.id]: value.slice(0, group2.input?.maxLength ?? 500)
|
|
1237
|
-
}
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
}),
|
|
1241
|
-
[]
|
|
1242
|
-
);
|
|
1243
|
-
const selectionKey = JSON.stringify(quantities);
|
|
1244
|
-
const state = useMemo(
|
|
1245
|
-
() => ({ group, selected, quantities, invalid, input, ...actions }),
|
|
1246
|
-
[group, selectionKey, invalid, input, actions]
|
|
1247
|
-
);
|
|
1248
|
-
const single = group.kind === "select" && group.selection.max === 1;
|
|
1249
|
-
return /* @__PURE__ */ jsx(Context4.Provider, { value: state, children: part(
|
|
1250
|
-
"div",
|
|
1251
|
-
props,
|
|
1252
|
-
state,
|
|
1253
|
-
{
|
|
1254
|
-
role: single ? "radiogroup" : "group",
|
|
1255
|
-
tabIndex: -1,
|
|
1256
|
-
"aria-label": group.name,
|
|
1257
|
-
"aria-required": single ? group.required : void 0,
|
|
1258
|
-
"aria-invalid": invalid || void 0,
|
|
1259
|
-
"data-invalid": flag(invalid),
|
|
1260
|
-
"data-state": invalid ? "invalid" : "valid",
|
|
1261
|
-
"data-zappie-option-group": group.id,
|
|
1262
|
-
onKeyDown: (event) => {
|
|
1263
|
-
if (!single || ![
|
|
1264
|
-
"ArrowDown",
|
|
1265
|
-
"ArrowRight",
|
|
1266
|
-
"ArrowUp",
|
|
1267
|
-
"ArrowLeft",
|
|
1268
|
-
"Home",
|
|
1269
|
-
"End"
|
|
1270
|
-
].includes(event.key)) {
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
const choices = Array.from(
|
|
1274
|
-
event.currentTarget.querySelectorAll(
|
|
1275
|
-
'[role="radio"]:not([aria-disabled="true"])'
|
|
1276
|
-
)
|
|
1277
|
-
);
|
|
1278
|
-
if (!choices.length) {
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
const current = choices.indexOf(
|
|
1282
|
-
document.activeElement
|
|
1283
|
-
);
|
|
1284
|
-
const index = event.key === "Home" ? 0 : event.key === "End" ? choices.length - 1 : (current + (event.key === "ArrowUp" || event.key === "ArrowLeft" ? -1 : 1) + choices.length) % choices.length;
|
|
1285
|
-
event.preventDefault();
|
|
1286
|
-
choices[index]?.focus();
|
|
1287
|
-
choices[index]?.click();
|
|
1288
|
-
}
|
|
1289
|
-
},
|
|
1290
|
-
composeRefs(ref, (node) => {
|
|
1291
|
-
if (node) {
|
|
1292
|
-
registry.set(group.id, node);
|
|
1293
|
-
} else {
|
|
1294
|
-
registry.delete(group.id);
|
|
1295
|
-
}
|
|
1296
|
-
})
|
|
1297
|
-
) });
|
|
1298
|
-
});
|
|
1299
|
-
var ChoiceView = memo(
|
|
1300
|
-
forwardRef(function ChoiceView2({
|
|
1301
|
-
choice,
|
|
1302
|
-
selected,
|
|
1303
|
-
single,
|
|
1304
|
-
tabIndex,
|
|
1305
|
-
quantity,
|
|
1306
|
-
toggleChoice,
|
|
1307
|
-
setChoiceQuantity,
|
|
1308
|
-
...props
|
|
1309
|
-
}, ref) {
|
|
1310
|
-
const toggle = () => toggleChoice(choice.id);
|
|
1311
|
-
const state = {
|
|
1312
|
-
choice,
|
|
1313
|
-
selected,
|
|
1314
|
-
disabled: !choice.isAvailable,
|
|
1315
|
-
toggle,
|
|
1316
|
-
quantity,
|
|
1317
|
-
min: choice.quantity?.min ?? 1,
|
|
1318
|
-
max: choice.quantity?.max ?? 99,
|
|
1319
|
-
setQuantity: (next) => setChoiceQuantity(choice.id, next)
|
|
1320
|
-
};
|
|
1321
|
-
return part(
|
|
1322
|
-
"button",
|
|
1323
|
-
props,
|
|
1324
|
-
state,
|
|
1325
|
-
{
|
|
1326
|
-
type: "button",
|
|
1327
|
-
role: single ? "radio" : "checkbox",
|
|
1328
|
-
tabIndex,
|
|
1329
|
-
"aria-checked": selected,
|
|
1330
|
-
"aria-disabled": !choice.isAvailable,
|
|
1331
|
-
disabled: !choice.isAvailable,
|
|
1332
|
-
"data-selected": flag(selected),
|
|
1333
|
-
"data-sold-out": flag(!choice.isAvailable),
|
|
1334
|
-
"data-disabled": flag(!choice.isAvailable),
|
|
1335
|
-
"data-state": selected ? "checked" : "unchecked",
|
|
1336
|
-
"data-zappie-option": choice.id,
|
|
1337
|
-
onClick: toggle
|
|
1338
|
-
},
|
|
1339
|
-
ref
|
|
1340
|
-
);
|
|
1341
|
-
})
|
|
1342
|
-
);
|
|
1343
|
-
var Choice = forwardRef(function Choice2(props, ref) {
|
|
1344
|
-
const ctx = useGroup();
|
|
1345
|
-
const single = ctx.group.selection.max === 1;
|
|
1346
|
-
const selected = ctx.selected.includes(props.choice.id);
|
|
1347
|
-
const first = ctx.group.choices.find((choice) => choice.isAvailable);
|
|
1348
|
-
const tabIndex = !single || selected || !ctx.selected.length && first?.id === props.choice.id ? 0 : -1;
|
|
1349
|
-
return /* @__PURE__ */ jsx(
|
|
1350
|
-
ChoiceView,
|
|
1351
|
-
{
|
|
1352
|
-
...props,
|
|
1353
|
-
ref,
|
|
1354
|
-
selected,
|
|
1355
|
-
single,
|
|
1356
|
-
tabIndex,
|
|
1357
|
-
quantity: ctx.quantities[props.choice.id] ?? 0,
|
|
1358
|
-
toggleChoice: ctx.toggle,
|
|
1359
|
-
setChoiceQuantity: ctx.setQuantity
|
|
1360
|
-
}
|
|
1361
|
-
);
|
|
1362
|
-
});
|
|
1363
|
-
var Input3 = forwardRef(
|
|
1364
|
-
function Input4(props, ref) {
|
|
1365
|
-
const ctx = useGroup();
|
|
1366
|
-
const id = usePartId(props.id);
|
|
1367
|
-
const toggle = ctx.group.kind === "toggle";
|
|
1368
|
-
return part(
|
|
1369
|
-
"input",
|
|
1370
|
-
props,
|
|
1371
|
-
ctx,
|
|
1372
|
-
{
|
|
1373
|
-
id,
|
|
1374
|
-
type: toggle ? "checkbox" : ctx.group.kind === "select" ? "text" : ctx.group.kind,
|
|
1375
|
-
value: toggle ? void 0 : ctx.input,
|
|
1376
|
-
checked: toggle ? ctx.input === "true" : void 0,
|
|
1377
|
-
required: ctx.group.required,
|
|
1378
|
-
"aria-label": ctx.group.name,
|
|
1379
|
-
"aria-invalid": ctx.invalid || void 0,
|
|
1380
|
-
"data-invalid": flag(ctx.invalid),
|
|
1381
|
-
maxLength: ctx.group.input?.maxLength ?? 500,
|
|
1382
|
-
placeholder: ctx.group.input?.placeholder,
|
|
1383
|
-
onChange: (event) => ctx.setInput(
|
|
1384
|
-
toggle ? String(event.target.checked) : event.target.value
|
|
1385
|
-
)
|
|
1386
|
-
},
|
|
1387
|
-
ref
|
|
1388
|
-
);
|
|
1389
|
-
}
|
|
1390
|
-
);
|
|
1391
|
-
var OptionGroup2 = Object.assign(Group, { Input: Input3 });
|
|
1392
|
-
|
|
1393
|
-
// src/item-form/index.ts
|
|
1394
|
-
var ItemForm = { Root: Root3, Options, Add };
|
|
1395
|
-
function Image({
|
|
1396
|
-
src,
|
|
1397
|
-
sizes = "100vw",
|
|
1398
|
-
priority = false,
|
|
1399
|
-
quality,
|
|
1400
|
-
mode = "resize",
|
|
1401
|
-
alt,
|
|
1402
|
-
...props
|
|
1403
|
-
}) {
|
|
1404
|
-
const responsive = mode === "resize" && /^https?:\/\//.test(src);
|
|
1405
|
-
return /* @__PURE__ */ jsx(
|
|
1406
|
-
"img",
|
|
1407
|
-
{
|
|
1408
|
-
...props,
|
|
1409
|
-
alt,
|
|
1410
|
-
src: responsive ? resized(src, 960, quality) : src,
|
|
1411
|
-
...responsive ? { srcSet: srcSet(src, quality), sizes } : {},
|
|
1412
|
-
loading: priority ? "eager" : "lazy",
|
|
1413
|
-
decoding: priority ? "sync" : "async",
|
|
1414
|
-
...priority ? { fetchPriority: "high" } : {}
|
|
1415
|
-
}
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
var Money = forwardRef(function Money2({ value, formatOptions, ...props }, ref) {
|
|
1419
|
-
const formatted = format.money(value, formatOptions);
|
|
1420
|
-
return part(
|
|
1421
|
-
"data",
|
|
1422
|
-
{ ...props, children: props.children ?? formatted },
|
|
1423
|
-
{ formatted },
|
|
1424
|
-
{ value: String(value.amountMinor) },
|
|
1425
|
-
ref
|
|
1426
|
-
);
|
|
1427
|
-
});
|
|
1428
|
-
function slotAttributes(index, length, value, disabled, update, focus) {
|
|
1429
|
-
return {
|
|
1430
|
-
type: "text",
|
|
1431
|
-
inputMode: "numeric",
|
|
1432
|
-
autoComplete: index === 0 ? "one-time-code" : "off",
|
|
1433
|
-
"aria-label": `Digit ${index + 1} of ${length}`,
|
|
1434
|
-
"data-otp-slot": index,
|
|
1435
|
-
value: value[index] ?? "",
|
|
1436
|
-
disabled,
|
|
1437
|
-
onChange: (event) => update(index, event.target.value),
|
|
1438
|
-
onPaste: (event) => {
|
|
1439
|
-
event.preventDefault();
|
|
1440
|
-
update(index, event.clipboardData.getData("text"));
|
|
1441
|
-
},
|
|
1442
|
-
onKeyDown: (event) => {
|
|
1443
|
-
if (event.key === "Backspace") {
|
|
1444
|
-
event.preventDefault();
|
|
1445
|
-
const target = value[index] ? index : Math.max(0, index - 1);
|
|
1446
|
-
update(target, "");
|
|
1447
|
-
focus(target);
|
|
1448
|
-
} else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
1449
|
-
event.preventDefault();
|
|
1450
|
-
focus(index + (event.key === "ArrowLeft" ? -1 : 1));
|
|
1451
|
-
}
|
|
1452
|
-
},
|
|
1453
|
-
onFocus: (event) => event.currentTarget.select()
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
var Input5 = forwardRef(function Input6({
|
|
1457
|
-
length = 6,
|
|
1458
|
-
value,
|
|
1459
|
-
defaultValue = "",
|
|
1460
|
-
onValueChange,
|
|
1461
|
-
onComplete,
|
|
1462
|
-
disabled = false,
|
|
1463
|
-
...props
|
|
1464
|
-
}, ref) {
|
|
1465
|
-
if (!Number.isInteger(length) || length < 1 || length > 12) {
|
|
1466
|
-
throw new RangeError("OTP length must be 1\u201312");
|
|
1467
|
-
}
|
|
1468
|
-
const [current, setValue] = useControllableState({
|
|
1469
|
-
value,
|
|
1470
|
-
defaultValue,
|
|
1471
|
-
onChange: onValueChange
|
|
1472
|
-
});
|
|
1473
|
-
const element = useRef(null);
|
|
1474
|
-
const previous = useRef("");
|
|
1475
|
-
const callback = useRef(onComplete);
|
|
1476
|
-
callback.current = onComplete;
|
|
1477
|
-
useEffect(() => {
|
|
1478
|
-
if (current.length === length && /^\d+$/.test(current) && previous.current !== current) {
|
|
1479
|
-
callback.current?.(current);
|
|
1480
|
-
}
|
|
1481
|
-
previous.current = current;
|
|
1482
|
-
}, [current, length]);
|
|
1483
|
-
const focus = (index) => element.current?.querySelector(
|
|
1484
|
-
`[data-otp-slot="${Math.max(0, Math.min(length - 1, index))}"]`
|
|
1485
|
-
)?.focus();
|
|
1486
|
-
const update = (index, text) => {
|
|
1487
|
-
if (disabled) {
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
const digits = text.replace(/\D/g, "");
|
|
1491
|
-
const start = digits.length >= length ? 0 : index;
|
|
1492
|
-
const next = (current.slice(0, start) + digits + current.slice(start + Math.max(1, digits.length))).slice(0, length);
|
|
1493
|
-
setValue(next);
|
|
1494
|
-
if (digits) {
|
|
1495
|
-
focus(start + digits.length);
|
|
1496
|
-
}
|
|
1497
|
-
};
|
|
1498
|
-
const adopt = useRef(update);
|
|
1499
|
-
adopt.current = update;
|
|
1500
|
-
useEffect(() => {
|
|
1501
|
-
const typed = Array.from(
|
|
1502
|
-
element.current?.querySelectorAll("[data-otp-slot]") ?? [],
|
|
1503
|
-
(input) => input.value
|
|
1504
|
-
).join("");
|
|
1505
|
-
if (typed && typed !== current) {
|
|
1506
|
-
adopt.current(0, typed);
|
|
1507
|
-
}
|
|
1508
|
-
}, []);
|
|
1509
|
-
const getSlotProps = (index) => slotAttributes(index, length, current, disabled, update, focus);
|
|
1510
|
-
const slots = Array.from({ length }, (_, index) => current[index] ?? "");
|
|
1511
|
-
return part(
|
|
1512
|
-
"div",
|
|
1513
|
-
{
|
|
1514
|
-
...props,
|
|
1515
|
-
children: props.children ?? slots.map((_, index) => /* @__PURE__ */ jsx("input", { ...getSlotProps(index) }, index))
|
|
1516
|
-
},
|
|
1517
|
-
{ value: current, slots, getSlotProps },
|
|
1518
|
-
{ role: "group", "data-disabled": disabled ? "" : void 0 },
|
|
1519
|
-
composeRefs(ref, element)
|
|
1520
|
-
);
|
|
1521
|
-
});
|
|
1522
|
-
var Otp = { Input: Input5 };
|
|
1523
|
-
var engineModule;
|
|
1524
|
-
var enginePromise;
|
|
1525
|
-
function loadOverlayEngine() {
|
|
1526
|
-
enginePromise ?? (enginePromise = import('./overlay/engine.js').then(
|
|
1527
|
-
(module) => engineModule = module,
|
|
1528
|
-
(error) => {
|
|
1529
|
-
enginePromise = void 0;
|
|
1530
|
-
throw error;
|
|
1531
|
-
}
|
|
1532
|
-
));
|
|
1533
|
-
return enginePromise;
|
|
1534
|
-
}
|
|
1535
|
-
function useOverlay(param, id, options) {
|
|
1536
|
-
const search = useSyncExternalStore(
|
|
1537
|
-
subscribeLocation,
|
|
1538
|
-
locationSnapshot,
|
|
1539
|
-
() => ""
|
|
1540
|
-
);
|
|
1541
|
-
const optionsRef = useRef(options);
|
|
1542
|
-
optionsRef.current = options;
|
|
1543
|
-
const open = useCallback(
|
|
1544
|
-
(query) => openOverlay(param, id, query),
|
|
1545
|
-
[param, id]
|
|
1546
|
-
);
|
|
1547
|
-
const close = useCallback(
|
|
1548
|
-
() => closeOverlay(param, id, optionsRef.current),
|
|
1549
|
-
[param, id]
|
|
1550
|
-
);
|
|
1551
|
-
return useMemo(
|
|
1552
|
-
() => ({
|
|
1553
|
-
isOpen: new URLSearchParams(search).get(param) === id,
|
|
1554
|
-
open,
|
|
1555
|
-
close
|
|
1556
|
-
}),
|
|
1557
|
-
[search, param, id, open, close]
|
|
1558
|
-
);
|
|
1559
|
-
}
|
|
1560
|
-
var BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 };
|
|
1561
|
-
function usePresentation(value) {
|
|
1562
|
-
const key = typeof value === "string" ? value : JSON.stringify(value);
|
|
1563
|
-
const resolve = useCallback(() => {
|
|
1564
|
-
const parsed = key.startsWith("{") ? JSON.parse(key) : key;
|
|
1565
|
-
if (typeof parsed === "string") {
|
|
1566
|
-
return parsed;
|
|
1567
|
-
}
|
|
1568
|
-
let current = parsed.base;
|
|
1569
|
-
for (const [name, width] of Object.entries(BREAKPOINTS)) {
|
|
1570
|
-
const at = parsed[name];
|
|
1571
|
-
if (at && window.matchMedia(`(min-width: ${width}px)`).matches) {
|
|
1572
|
-
current = at;
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
return current;
|
|
1576
|
-
}, [key]);
|
|
1577
|
-
const subscribe = useCallback(
|
|
1578
|
-
(onChange) => {
|
|
1579
|
-
if (!key.startsWith("{")) {
|
|
1580
|
-
return () => {
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1583
|
-
const queries = Object.values(BREAKPOINTS).map(
|
|
1584
|
-
(width) => window.matchMedia(`(min-width: ${width}px)`)
|
|
1585
|
-
);
|
|
1586
|
-
queries.forEach((query) => query.addEventListener("change", onChange));
|
|
1587
|
-
return () => queries.forEach(
|
|
1588
|
-
(query) => query.removeEventListener("change", onChange)
|
|
1589
|
-
);
|
|
1590
|
-
},
|
|
1591
|
-
[key]
|
|
1592
|
-
);
|
|
1593
|
-
return useSyncExternalStore(
|
|
1594
|
-
subscribe,
|
|
1595
|
-
resolve,
|
|
1596
|
-
() => typeof value === "string" ? value : value.base
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
var Context5 = createContext(null);
|
|
1600
|
-
var EngineContext = createContext(null);
|
|
1601
|
-
function useContextValue2() {
|
|
1602
|
-
const value = useContext(Context5);
|
|
1603
|
-
if (!value) {
|
|
1604
|
-
throw new Error("Overlay parts require Overlay.Root");
|
|
1605
|
-
}
|
|
1606
|
-
return value;
|
|
1607
|
-
}
|
|
1608
|
-
var prefetch = () => void loadOverlayEngine().catch(() => {
|
|
1609
|
-
});
|
|
1610
|
-
function Root4({
|
|
1611
|
-
param,
|
|
1612
|
-
id,
|
|
1613
|
-
presentation = "dialog",
|
|
1614
|
-
swipeDirection,
|
|
1615
|
-
fallback,
|
|
1616
|
-
omitParams,
|
|
1617
|
-
navigation,
|
|
1618
|
-
...props
|
|
1619
|
-
}) {
|
|
1620
|
-
const queryOverlay = useOverlay(param, id, { fallback, omitParams });
|
|
1621
|
-
const overlay = navigation ?? queryOverlay;
|
|
1622
|
-
const resolved = usePresentation(presentation);
|
|
1623
|
-
const generated = useId();
|
|
1624
|
-
const [contentId, setContentId] = useState(`zappie-overlay-${generated}`);
|
|
1625
|
-
const trigger = useRef(null);
|
|
1626
|
-
useEffect(() => {
|
|
1627
|
-
if (engineModule) {
|
|
1628
|
-
return void 0;
|
|
1629
|
-
}
|
|
1630
|
-
if (typeof window.requestIdleCallback === "function") {
|
|
1631
|
-
const handle2 = window.requestIdleCallback(prefetch);
|
|
1632
|
-
return () => window.cancelIdleCallback(handle2);
|
|
1633
|
-
}
|
|
1634
|
-
const handle = window.setTimeout(prefetch, 1500);
|
|
1635
|
-
return () => window.clearTimeout(handle);
|
|
1636
|
-
}, []);
|
|
1637
|
-
const context = useMemo(
|
|
1638
|
-
() => ({
|
|
1639
|
-
...overlay,
|
|
1640
|
-
presentation: resolved,
|
|
1641
|
-
swipeDirection: swipeDirection ?? (resolved === "sheet" ? "right" : "down"),
|
|
1642
|
-
contentId,
|
|
1643
|
-
setContentId,
|
|
1644
|
-
trigger
|
|
1645
|
-
}),
|
|
1646
|
-
[overlay, resolved, swipeDirection, contentId]
|
|
1647
|
-
);
|
|
1648
|
-
return /* @__PURE__ */ jsx(Context5.Provider, { value: context, children: part("div", props, overlay, {
|
|
1649
|
-
"data-state": overlay.isOpen ? "open" : "closed",
|
|
1650
|
-
"data-presentation": resolved
|
|
1651
|
-
}) });
|
|
1652
|
-
}
|
|
1653
|
-
var Trigger = forwardRef(
|
|
1654
|
-
function Trigger2(props, ref) {
|
|
1655
|
-
const ctx = useContextValue2();
|
|
1656
|
-
return part(
|
|
1657
|
-
"button",
|
|
1658
|
-
props,
|
|
1659
|
-
ctx,
|
|
1660
|
-
{
|
|
1661
|
-
type: "button",
|
|
1662
|
-
"aria-haspopup": "dialog",
|
|
1663
|
-
"aria-expanded": ctx.isOpen,
|
|
1664
|
-
"aria-controls": ctx.contentId,
|
|
1665
|
-
"data-state": ctx.isOpen ? "open" : "closed",
|
|
1666
|
-
onPointerEnter: prefetch,
|
|
1667
|
-
onFocus: prefetch,
|
|
1668
|
-
onClick: () => ctx.open()
|
|
1669
|
-
},
|
|
1670
|
-
composeRefs(ref, (node) => {
|
|
1671
|
-
ctx.trigger.current = node;
|
|
1672
|
-
})
|
|
1673
|
-
);
|
|
1674
|
-
}
|
|
1675
|
-
);
|
|
1676
|
-
var Content = forwardRef(function Content2({ onEscape, onInteractOutside, forceMount, container, ...props }, ref) {
|
|
1677
|
-
const ctx = useContextValue2();
|
|
1678
|
-
const [engine, setEngine] = useState(engineModule);
|
|
1679
|
-
const [opened, setOpened] = useState(ctx.isOpen);
|
|
1680
|
-
if (ctx.isOpen && !opened) {
|
|
1681
|
-
setOpened(true);
|
|
1682
|
-
}
|
|
1683
|
-
const customId = partId(props);
|
|
1684
|
-
const { setContentId } = ctx;
|
|
1685
|
-
useEffect(() => {
|
|
1686
|
-
if (customId) {
|
|
1687
|
-
setContentId(customId);
|
|
1688
|
-
}
|
|
1689
|
-
}, [customId, setContentId]);
|
|
1690
|
-
useEffect(() => {
|
|
1691
|
-
if (engine || !(ctx.isOpen || forceMount)) {
|
|
1692
|
-
return void 0;
|
|
1693
|
-
}
|
|
1694
|
-
let live = true;
|
|
1695
|
-
void loadOverlayEngine().then(
|
|
1696
|
-
(module) => {
|
|
1697
|
-
if (live) {
|
|
1698
|
-
setEngine(module);
|
|
1699
|
-
}
|
|
1700
|
-
},
|
|
1701
|
-
() => {
|
|
1702
|
-
}
|
|
1703
|
-
);
|
|
1704
|
-
return () => {
|
|
1705
|
-
live = false;
|
|
1706
|
-
};
|
|
1707
|
-
}, [engine, ctx.isOpen, forceMount]);
|
|
1708
|
-
if (!engine || !opened && !forceMount) {
|
|
1709
|
-
return null;
|
|
1710
|
-
}
|
|
1711
|
-
return /* @__PURE__ */ jsx(EngineContext.Provider, { value: engine, children: /* @__PURE__ */ jsx(
|
|
1712
|
-
engine.Surface,
|
|
1713
|
-
{
|
|
1714
|
-
open: ctx.isOpen,
|
|
1715
|
-
presentation: ctx.presentation,
|
|
1716
|
-
swipeDirection: ctx.swipeDirection,
|
|
1717
|
-
contentId: ctx.contentId,
|
|
1718
|
-
trigger: ctx.trigger,
|
|
1719
|
-
onOpen: () => ctx.open(),
|
|
1720
|
-
onClose: ctx.close,
|
|
1721
|
-
onEscape,
|
|
1722
|
-
onInteractOutside,
|
|
1723
|
-
forceMount,
|
|
1724
|
-
container,
|
|
1725
|
-
state: ctx,
|
|
1726
|
-
contentProps: props,
|
|
1727
|
-
contentRef: ref
|
|
1728
|
-
}
|
|
1729
|
-
) });
|
|
1730
|
-
});
|
|
1731
|
-
function useEngine() {
|
|
1732
|
-
const engine = useContext(EngineContext);
|
|
1733
|
-
if (!engine) {
|
|
1734
|
-
throw new Error(
|
|
1735
|
-
"Overlay.Title and Overlay.Close must be inside Overlay.Content"
|
|
1736
|
-
);
|
|
1737
|
-
}
|
|
1738
|
-
return engine;
|
|
1739
|
-
}
|
|
1740
|
-
var Title = forwardRef(
|
|
1741
|
-
function Title2(props, ref) {
|
|
1742
|
-
const ctx = useContextValue2();
|
|
1743
|
-
const { EngineTitle } = useEngine();
|
|
1744
|
-
return /* @__PURE__ */ jsx(EngineTitle, { ...props, state: ctx, ref });
|
|
1745
|
-
}
|
|
1746
|
-
);
|
|
1747
|
-
var Close = forwardRef(
|
|
1748
|
-
function Close2(props, ref) {
|
|
1749
|
-
const ctx = useContextValue2();
|
|
1750
|
-
const { EngineClose } = useEngine();
|
|
1751
|
-
return /* @__PURE__ */ jsx(EngineClose, { ...props, state: ctx, ref });
|
|
1752
|
-
}
|
|
1753
|
-
);
|
|
1754
|
-
var Overlay = { Root: Root4, Trigger, Content, Title, Close };
|
|
1755
|
-
var Context6 = createContext(null);
|
|
1756
|
-
function Root5({
|
|
1757
|
-
methods,
|
|
1758
|
-
value,
|
|
1759
|
-
defaultValue = null,
|
|
1760
|
-
onValueChange,
|
|
1761
|
-
...props
|
|
1762
|
-
}) {
|
|
1763
|
-
const [selected, set] = useControllableState({
|
|
1764
|
-
value,
|
|
1765
|
-
defaultValue,
|
|
1766
|
-
onChange: onValueChange
|
|
1767
|
-
});
|
|
1768
|
-
const normalized = methods.map(
|
|
1769
|
-
(method) => typeof method === "string" ? { value: method } : method
|
|
1770
|
-
);
|
|
1771
|
-
const setValue = (next) => {
|
|
1772
|
-
if (normalized.some((method) => method.value === next && !method.disabled)) {
|
|
1773
|
-
set(next);
|
|
1774
|
-
}
|
|
1775
|
-
};
|
|
1776
|
-
const state = { methods: normalized, value: selected, setValue };
|
|
1777
|
-
return /* @__PURE__ */ jsx(Context6.Provider, { value: state, children: part("div", props, state, {
|
|
1778
|
-
role: "radiogroup",
|
|
1779
|
-
onKeyDown: (event) => {
|
|
1780
|
-
if (![
|
|
1781
|
-
"ArrowRight",
|
|
1782
|
-
"ArrowDown",
|
|
1783
|
-
"ArrowLeft",
|
|
1784
|
-
"ArrowUp",
|
|
1785
|
-
"Home",
|
|
1786
|
-
"End"
|
|
1787
|
-
].includes(event.key)) {
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
const buttons = Array.from(
|
|
1791
|
-
event.currentTarget.querySelectorAll(
|
|
1792
|
-
'[role="radio"]:not([aria-disabled="true"])'
|
|
1793
|
-
)
|
|
1794
|
-
);
|
|
1795
|
-
if (!buttons.length) {
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
event.preventDefault();
|
|
1799
|
-
const current = buttons.indexOf(
|
|
1800
|
-
document.activeElement
|
|
1801
|
-
);
|
|
1802
|
-
const index = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (current + (["ArrowLeft", "ArrowUp"].includes(event.key) ? -1 : 1) + buttons.length) % buttons.length;
|
|
1803
|
-
buttons[index]?.focus();
|
|
1804
|
-
buttons[index]?.click();
|
|
1805
|
-
}
|
|
1806
|
-
}) });
|
|
1807
|
-
}
|
|
1808
|
-
var Item = forwardRef(function Item2({ value, ...props }, ref) {
|
|
1809
|
-
const ctx = useContext(Context6);
|
|
1810
|
-
const id = usePartId();
|
|
1811
|
-
if (!ctx) {
|
|
1812
|
-
throw new Error("PaymentMethods.Item requires PaymentMethods.Root");
|
|
1813
|
-
}
|
|
1814
|
-
const method = ctx.methods.find((method2) => method2.value === value);
|
|
1815
|
-
if (!method) {
|
|
1816
|
-
return null;
|
|
1817
|
-
}
|
|
1818
|
-
const selected = ctx.value === value;
|
|
1819
|
-
const first = ctx.methods.find((method2) => !method2.disabled)?.value;
|
|
1820
|
-
return part(
|
|
1821
|
-
"button",
|
|
1822
|
-
props,
|
|
1823
|
-
{ ...method, selected },
|
|
1824
|
-
{
|
|
1825
|
-
id,
|
|
1826
|
-
type: "button",
|
|
1827
|
-
role: "radio",
|
|
1828
|
-
"aria-checked": selected,
|
|
1829
|
-
"aria-disabled": method.disabled || void 0,
|
|
1830
|
-
"aria-description": method.reason,
|
|
1831
|
-
disabled: method.disabled,
|
|
1832
|
-
tabIndex: selected || !ctx.value && first === value ? 0 : -1,
|
|
1833
|
-
"data-disabled": method.disabled ? "" : void 0,
|
|
1834
|
-
"data-selected": selected ? "" : void 0,
|
|
1835
|
-
"data-state": selected ? "checked" : "unchecked",
|
|
1836
|
-
onClick: () => ctx.setValue(value)
|
|
1837
|
-
},
|
|
1838
|
-
ref
|
|
1839
|
-
);
|
|
1840
|
-
});
|
|
1841
|
-
var PaymentMethods = { Root: Root5, Item };
|
|
1842
|
-
|
|
1843
|
-
// src/search/engine.ts
|
|
1844
|
-
function normalizeSearch(text) {
|
|
1845
|
-
return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
1846
|
-
}
|
|
1847
|
-
function near(a, b) {
|
|
1848
|
-
if (Math.abs(a.length - b.length) > 1) {
|
|
1849
|
-
return false;
|
|
1850
|
-
}
|
|
1851
|
-
let i = 0;
|
|
1852
|
-
let j = 0;
|
|
1853
|
-
let edits = 0;
|
|
1854
|
-
while (i < a.length && j < b.length) {
|
|
1855
|
-
if (a[i] === b[j]) {
|
|
1856
|
-
i++;
|
|
1857
|
-
j++;
|
|
1858
|
-
continue;
|
|
1859
|
-
}
|
|
1860
|
-
if (++edits > 1) {
|
|
1861
|
-
return false;
|
|
1862
|
-
}
|
|
1863
|
-
if (a.length === b.length) {
|
|
1864
|
-
if (a[i] === b[j + 1] && a[i + 1] === b[j]) {
|
|
1865
|
-
i += 2;
|
|
1866
|
-
j += 2;
|
|
1867
|
-
} else {
|
|
1868
|
-
i++;
|
|
1869
|
-
j++;
|
|
1870
|
-
}
|
|
1871
|
-
} else if (a.length > b.length) {
|
|
1872
|
-
i++;
|
|
1873
|
-
} else {
|
|
1874
|
-
j++;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
return edits + (a.length - i) + (b.length - j) <= 1;
|
|
1878
|
-
}
|
|
1879
|
-
function createSearchIndex(menu) {
|
|
1880
|
-
const entries = [
|
|
1881
|
-
...Object.values(menu.items).map((item) => ({
|
|
1882
|
-
result: { type: "item", item },
|
|
1883
|
-
fields: [
|
|
1884
|
-
normalizeSearch(item.name),
|
|
1885
|
-
normalizeSearch(item.tags.join(" ")),
|
|
1886
|
-
normalizeSearch(item.description ?? "")
|
|
1887
|
-
]
|
|
1888
|
-
})),
|
|
1889
|
-
...menu.categories.map((category) => ({
|
|
1890
|
-
result: { type: "category", category },
|
|
1891
|
-
fields: [
|
|
1892
|
-
normalizeSearch(category.name),
|
|
1893
|
-
[],
|
|
1894
|
-
normalizeSearch(category.description ?? "")
|
|
1895
|
-
]
|
|
1896
|
-
}))
|
|
1897
|
-
];
|
|
1898
|
-
return (query) => {
|
|
1899
|
-
const tokens = normalizeSearch(query);
|
|
1900
|
-
if (!tokens.length) {
|
|
1901
|
-
return [];
|
|
1902
|
-
}
|
|
1903
|
-
const results = [];
|
|
1904
|
-
for (const entry of entries) {
|
|
1905
|
-
let score = 0;
|
|
1906
|
-
for (const token of tokens) {
|
|
1907
|
-
let best = 0;
|
|
1908
|
-
for (let field = 0; field < entry.fields.length; field++) {
|
|
1909
|
-
const weight = [30, 20, 10][field];
|
|
1910
|
-
for (const word of entry.fields[field]) {
|
|
1911
|
-
if (word.startsWith(token)) {
|
|
1912
|
-
best = Math.max(best, weight + (word === token ? 3 : 2));
|
|
1913
|
-
} else if (token.length >= 4 && near(token, word)) {
|
|
1914
|
-
best = Math.max(best, weight);
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
if (!best) {
|
|
1919
|
-
score = 0;
|
|
1920
|
-
break;
|
|
1921
|
-
}
|
|
1922
|
-
score += best;
|
|
1923
|
-
}
|
|
1924
|
-
if (score) {
|
|
1925
|
-
results.push({ ...entry.result, score });
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
return results.sort((a, b) => b.score - a.score);
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
var Context7 = createContext(null);
|
|
1932
|
-
function useSearch() {
|
|
1933
|
-
const ctx = useContext(Context7);
|
|
1934
|
-
if (!ctx) {
|
|
1935
|
-
throw new Error("Search parts require Search.Root");
|
|
1936
|
-
}
|
|
1937
|
-
return ctx;
|
|
1938
|
-
}
|
|
1939
|
-
function Root6(props) {
|
|
1940
|
-
if (props.source) {
|
|
1941
|
-
return /* @__PURE__ */ jsx(ControlledRoot, { ...props, source: props.source });
|
|
1942
|
-
}
|
|
1943
|
-
return /* @__PURE__ */ jsx(UncontrolledRoot, { ...props });
|
|
1944
|
-
}
|
|
1945
|
-
function UncontrolledRoot(props) {
|
|
1946
|
-
const cart = useCart();
|
|
1947
|
-
const menu = useMenu(cart.branchId ?? void 0);
|
|
1948
|
-
const [query, setQuery] = useControllableState({
|
|
1949
|
-
value: props.query,
|
|
1950
|
-
defaultValue: props.defaultQuery ?? "",
|
|
1951
|
-
onChange: props.onQueryChange
|
|
1952
|
-
});
|
|
1953
|
-
const index = useMemo(
|
|
1954
|
-
() => menu.data ? createSearchIndex(menu.data) : () => [],
|
|
1955
|
-
[menu.data]
|
|
1956
|
-
);
|
|
1957
|
-
const results = useMemo(() => index(query), [index, query]);
|
|
1958
|
-
return /* @__PURE__ */ jsx(
|
|
1959
|
-
ControlledRoot,
|
|
1960
|
-
{
|
|
1961
|
-
...props,
|
|
1962
|
-
query,
|
|
1963
|
-
onQueryChange: setQuery,
|
|
1964
|
-
source: { results, isLoading: menu.isLoading, isStale: menu.isStale }
|
|
1965
|
-
}
|
|
1966
|
-
);
|
|
1967
|
-
}
|
|
1968
|
-
function ControlledRoot({
|
|
1969
|
-
query,
|
|
1970
|
-
defaultQuery = "",
|
|
1971
|
-
onQueryChange,
|
|
1972
|
-
onSelect = () => {
|
|
1973
|
-
},
|
|
1974
|
-
announcement = countAnnouncement,
|
|
1975
|
-
source,
|
|
1976
|
-
...props
|
|
1977
|
-
}) {
|
|
1978
|
-
const client = useZappie();
|
|
1979
|
-
const [value, setValue] = useControllableState({
|
|
1980
|
-
value: query,
|
|
1981
|
-
defaultValue: defaultQuery,
|
|
1982
|
-
onChange: onQueryChange
|
|
1983
|
-
});
|
|
1984
|
-
const results = source.results;
|
|
1985
|
-
const combo = useCombobox(results, onSelect);
|
|
1986
|
-
const setQuery = (next) => {
|
|
1987
|
-
setValue(next);
|
|
1988
|
-
combo.setActiveIndex(-1);
|
|
1989
|
-
combo.setOpen(true);
|
|
1990
|
-
};
|
|
1991
|
-
useEffect(() => {
|
|
1992
|
-
if (!value.trim()) {
|
|
1993
|
-
return;
|
|
1994
|
-
}
|
|
1995
|
-
const timer = setTimeout(
|
|
1996
|
-
() => client._internal.emit("search", {
|
|
1997
|
-
query: value,
|
|
1998
|
-
resultCount: results.length
|
|
1999
|
-
}),
|
|
2000
|
-
600
|
|
2001
|
-
);
|
|
2002
|
-
return () => clearTimeout(timer);
|
|
2003
|
-
}, [client, value, results.length]);
|
|
2004
|
-
const state = {
|
|
2005
|
-
...combo,
|
|
2006
|
-
query: value,
|
|
2007
|
-
setQuery,
|
|
2008
|
-
results,
|
|
2009
|
-
setComposing: (value2) => source.onCompositionChange?.(value2),
|
|
2010
|
-
...source.error ? { error: source.error } : {},
|
|
2011
|
-
...source.retry ? { retry: source.retry } : {}
|
|
2012
|
-
};
|
|
2013
|
-
return /* @__PURE__ */ jsx(Context7.Provider, { value: state, children: part(
|
|
2014
|
-
"div",
|
|
2015
|
-
withLiveRegion(
|
|
2016
|
-
props,
|
|
2017
|
-
combo.isOpen && value.trim() ? announcement(results.length) : ""
|
|
2018
|
-
),
|
|
2019
|
-
state,
|
|
2020
|
-
{
|
|
2021
|
-
"data-state": source.error ? "error" : source.isLoading ? "loading" : source.isStale ? "stale" : "valid",
|
|
2022
|
-
onBlur: (event) => {
|
|
2023
|
-
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
2024
|
-
combo.setOpen(false);
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
) });
|
|
2029
|
-
}
|
|
2030
|
-
var Input7 = forwardRef(
|
|
2031
|
-
function Input8(props, ref) {
|
|
2032
|
-
const state = useSearch();
|
|
2033
|
-
return part(
|
|
2034
|
-
"input",
|
|
2035
|
-
props,
|
|
2036
|
-
state,
|
|
2037
|
-
{
|
|
2038
|
-
...state.inputProps,
|
|
2039
|
-
type: "search",
|
|
2040
|
-
onCompositionStart: () => state.setComposing(true),
|
|
2041
|
-
onCompositionEnd: () => state.setComposing(false),
|
|
2042
|
-
value: state.query,
|
|
2043
|
-
onChange: (event) => state.setQuery(event.target.value)
|
|
2044
|
-
},
|
|
2045
|
-
ref
|
|
2046
|
-
);
|
|
2047
|
-
}
|
|
2048
|
-
);
|
|
2049
|
-
var Results = forwardRef(
|
|
2050
|
-
function Results2(props, ref) {
|
|
2051
|
-
const state = useSearch();
|
|
2052
|
-
return part(
|
|
2053
|
-
"div",
|
|
2054
|
-
props,
|
|
2055
|
-
state,
|
|
2056
|
-
state.listProps,
|
|
2057
|
-
composeRefs(ref, (node) => {
|
|
2058
|
-
if (node?.id && node.id !== state.listProps.id) {
|
|
2059
|
-
state.setListId(node.id);
|
|
2060
|
-
}
|
|
2061
|
-
})
|
|
2062
|
-
);
|
|
2063
|
-
}
|
|
2064
|
-
);
|
|
2065
|
-
var Result = forwardRef(function Result2({ index, ...props }, ref) {
|
|
2066
|
-
const state = useSearch();
|
|
2067
|
-
return part(
|
|
2068
|
-
"div",
|
|
2069
|
-
props,
|
|
2070
|
-
state,
|
|
2071
|
-
state.getOptionProps(index),
|
|
2072
|
-
composeRefs(ref, (node) => {
|
|
2073
|
-
if (node?.id && node.id !== state.getOptionProps(index).id) {
|
|
2074
|
-
state.registerOption(index, node.id);
|
|
2075
|
-
}
|
|
2076
|
-
})
|
|
2077
|
-
);
|
|
2078
|
-
});
|
|
2079
|
-
var Search = { Root: Root6, Input: Input7, Results, Result };
|
|
2080
|
-
function useOpenStatus(branch, timezone) {
|
|
2081
|
-
const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
|
|
2082
|
-
const state = getOpenStatus(branch, timezone, now);
|
|
2083
|
-
const boundary = state.nextChange?.getTime();
|
|
2084
|
-
useEffect(() => {
|
|
2085
|
-
const delay = boundary !== void 0 ? boundary - Date.now() : 864e5;
|
|
2086
|
-
const timeout = setTimeout(
|
|
2087
|
-
() => setNow(/* @__PURE__ */ new Date()),
|
|
2088
|
-
Math.max(1, Math.min(2147483647, delay))
|
|
2089
|
-
);
|
|
2090
|
-
const refresh = () => setNow(/* @__PURE__ */ new Date());
|
|
2091
|
-
window.addEventListener("focus", refresh);
|
|
2092
|
-
return () => {
|
|
2093
|
-
clearTimeout(timeout);
|
|
2094
|
-
window.removeEventListener("focus", refresh);
|
|
2095
|
-
};
|
|
2096
|
-
}, [branch, timezone, boundary]);
|
|
2097
|
-
return state;
|
|
2098
|
-
}
|
|
2099
|
-
var OpenStatus = forwardRef(function OpenStatus2({ branch, timezone, ...props }, ref) {
|
|
2100
|
-
const store = useStore();
|
|
2101
|
-
const state = useOpenStatus(
|
|
2102
|
-
branch,
|
|
2103
|
-
timezone ?? branch.timezone ?? store.data?.timezone ?? "UTC"
|
|
2104
|
-
);
|
|
2105
|
-
return part(
|
|
2106
|
-
"span",
|
|
2107
|
-
props,
|
|
2108
|
-
state,
|
|
2109
|
-
{ "data-state": state.isOpen ? "open" : "closed" },
|
|
2110
|
-
ref
|
|
2111
|
-
);
|
|
2112
|
-
});
|
|
2113
2
|
|
|
2114
3
|
// src/index.ts
|
|
4
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
5
|
+
import { Account, AccountNotConfiguredError } from "./account/index.js";
|
|
6
|
+
import { Cart, Totals } from "./cart/index.js";
|
|
7
|
+
import { Address } from "./fulfilment/address-picker.js";
|
|
8
|
+
import { Branch } from "./fulfilment/branch-picker.js";
|
|
9
|
+
import { Schedule, scheduleSlots } from "./fulfilment/schedule.js";
|
|
10
|
+
import { Fulfilment } from "./fulfilment/switch.js";
|
|
11
|
+
import { useControllableState } from "./internal/use-controllable-state.js";
|
|
12
|
+
export * from "./item-form/index.js";
|
|
13
|
+
import { Image } from "./media/image.js";
|
|
14
|
+
import { Money } from "./money.js";
|
|
15
|
+
import { Otp } from "./otp/input.js";
|
|
16
|
+
import { Overlay, useOverlay } from "./overlay/react.js";
|
|
17
|
+
import { PaymentMethods } from "./payment/methods.js";
|
|
18
|
+
import { createSearchIndex, Search } from "./search/index.js";
|
|
19
|
+
import {
|
|
20
|
+
formatOpeningHours,
|
|
21
|
+
formatWallClock,
|
|
22
|
+
getOpenStatus,
|
|
23
|
+
parseWallClock
|
|
24
|
+
} from "./status/hours.js";
|
|
25
|
+
import { OpenStatus } from "./status/open-status.js";
|
|
2115
26
|
var FOUNDATIONS_VERSION = PACKAGE_VERSION;
|
|
2116
|
-
|
|
2117
|
-
|
|
27
|
+
export {
|
|
28
|
+
Account,
|
|
29
|
+
AccountNotConfiguredError,
|
|
30
|
+
Address,
|
|
31
|
+
Branch,
|
|
32
|
+
Cart,
|
|
33
|
+
FOUNDATIONS_VERSION,
|
|
34
|
+
Fulfilment,
|
|
35
|
+
Image,
|
|
36
|
+
Money,
|
|
37
|
+
OpenStatus,
|
|
38
|
+
Otp,
|
|
39
|
+
Overlay,
|
|
40
|
+
PaymentMethods,
|
|
41
|
+
Schedule,
|
|
42
|
+
Search,
|
|
43
|
+
Totals,
|
|
44
|
+
createSearchIndex,
|
|
45
|
+
formatOpeningHours,
|
|
46
|
+
formatWallClock,
|
|
47
|
+
getOpenStatus,
|
|
48
|
+
parseWallClock,
|
|
49
|
+
scheduleSlots,
|
|
50
|
+
useControllableState,
|
|
51
|
+
useOverlay
|
|
52
|
+
};
|
|
2118
53
|
//# sourceMappingURL=index.js.map
|
|
2119
|
-
//# sourceMappingURL=index.js.map
|