@nickkupriyanov/slotline-react 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -21
- package/dist/index.cjs +544 -642
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -77
- package/dist/index.d.ts +76 -77
- package/dist/index.js +378 -634
- package/dist/index.js.map +1 -1
- package/dist/motion.cjs +544 -642
- package/dist/motion.cjs.map +1 -1
- package/dist/motion.d.cts +4 -3
- package/dist/motion.d.ts +4 -3
- package/dist/motion.js +378 -634
- package/dist/motion.js.map +1 -1
- package/dist/slotline.css +18 -11
- package/package.json +2 -2
package/dist/motion.cjs
CHANGED
|
@@ -3,85 +3,41 @@
|
|
|
3
3
|
|
|
4
4
|
var react = require('motion/react');
|
|
5
5
|
var react$1 = require('react');
|
|
6
|
+
var dom = require('@nickkupriyanov/slotline-core/dom');
|
|
6
7
|
var slotlineCore = require('@nickkupriyanov/slotline-core');
|
|
7
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
9
|
|
|
9
|
-
function subscribe(query, listener) {
|
|
10
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
|
|
11
|
-
};
|
|
12
|
-
const list = window.matchMedia(query);
|
|
13
|
-
const handler = (event) => listener(event.matches);
|
|
14
|
-
listener(list.matches);
|
|
15
|
-
list.addEventListener("change", handler);
|
|
16
|
-
return () => list.removeEventListener("change", handler);
|
|
17
|
-
}
|
|
18
10
|
function useMediaQuery(query) {
|
|
19
11
|
const [matches, setMatches] = react$1.useState(false);
|
|
20
|
-
react$1.useEffect(() =>
|
|
21
|
-
let active = true;
|
|
22
|
-
const unsubscribe = subscribe(query, (next) => {
|
|
23
|
-
if (active) setMatches(next);
|
|
24
|
-
});
|
|
25
|
-
return () => {
|
|
26
|
-
active = false;
|
|
27
|
-
unsubscribe();
|
|
28
|
-
};
|
|
29
|
-
}, [query]);
|
|
12
|
+
react$1.useEffect(() => dom.observeMediaQuery(query, setMatches), [query]);
|
|
30
13
|
return matches;
|
|
31
14
|
}
|
|
32
15
|
function useReducedMotion() {
|
|
33
|
-
return useMediaQuery(
|
|
16
|
+
return useMediaQuery(dom.REDUCED_MOTION_QUERY);
|
|
34
17
|
}
|
|
35
18
|
|
|
36
19
|
// src/hooks/useAnimatedHeight.ts
|
|
37
20
|
function useAnimatedHeight(durationMs = 320) {
|
|
38
21
|
const contentRef = react$1.useRef(null);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
const [state, setState] = react$1.useState({
|
|
23
|
+
height: void 0,
|
|
24
|
+
animating: false
|
|
25
|
+
});
|
|
43
26
|
const reducedMotion = useReducedMotion();
|
|
44
|
-
const measure = react$1.useCallback(() => {
|
|
45
|
-
const element = contentRef.current;
|
|
46
|
-
if (!element) return;
|
|
47
|
-
const next = element.getBoundingClientRect().height;
|
|
48
|
-
if (!(next > 0)) return;
|
|
49
|
-
const previous = heightRef.current;
|
|
50
|
-
if (previous !== void 0 && Math.abs(previous - next) >= 1) {
|
|
51
|
-
setAnimating(true);
|
|
52
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
53
|
-
timerRef.current = setTimeout(() => setAnimating(false), durationMs);
|
|
54
|
-
}
|
|
55
|
-
heightRef.current = next;
|
|
56
|
-
setHeight((current) => current === next ? current : next);
|
|
57
|
-
}, [durationMs]);
|
|
58
27
|
react$1.useEffect(() => {
|
|
59
|
-
if (reducedMotion) return;
|
|
60
|
-
measure();
|
|
61
28
|
const element = contentRef.current;
|
|
62
|
-
if (!element ||
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
const observer = new ResizeObserver(() => measure());
|
|
67
|
-
observer.observe(element);
|
|
68
|
-
return () => observer.disconnect();
|
|
69
|
-
}, [measure, reducedMotion]);
|
|
70
|
-
react$1.useEffect(
|
|
71
|
-
() => () => {
|
|
72
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
73
|
-
},
|
|
74
|
-
[]
|
|
75
|
-
);
|
|
29
|
+
if (!element || reducedMotion) return;
|
|
30
|
+
return dom.observeAnimatedHeight(element, { durationMs, onChange: setState });
|
|
31
|
+
}, [durationMs, reducedMotion]);
|
|
76
32
|
if (reducedMotion) {
|
|
77
33
|
return { contentRef, style: void 0, animating: false };
|
|
78
34
|
}
|
|
79
|
-
const style = height === void 0 ? void 0 : {
|
|
80
|
-
height,
|
|
81
|
-
overflow: animating ? "hidden" : "visible",
|
|
35
|
+
const style = state.height === void 0 ? void 0 : {
|
|
36
|
+
height: state.height,
|
|
37
|
+
overflow: state.animating ? "hidden" : "visible",
|
|
82
38
|
transition: `height ${durationMs}ms var(--sl-ease-soft)`
|
|
83
39
|
};
|
|
84
|
-
return { contentRef, style, animating };
|
|
40
|
+
return { contentRef, style, animating: state.animating };
|
|
85
41
|
}
|
|
86
42
|
var spring = { type: "spring", stiffness: 420, damping: 34, mass: 0.7 };
|
|
87
43
|
var MotionButton = react.motion.button;
|
|
@@ -169,7 +125,11 @@ var useFlow = useSlotlineContext;
|
|
|
169
125
|
var UIContext = react$1.createContext({
|
|
170
126
|
animations: cssAnimations,
|
|
171
127
|
autoFocus: false,
|
|
172
|
-
trapFocus: false
|
|
128
|
+
trapFocus: false,
|
|
129
|
+
canDismiss: false,
|
|
130
|
+
messages: slotlineCore.defaultMessages.en,
|
|
131
|
+
locale: "en",
|
|
132
|
+
theme: "dark"
|
|
173
133
|
});
|
|
174
134
|
function useUI() {
|
|
175
135
|
return react$1.useContext(UIContext);
|
|
@@ -184,57 +144,37 @@ function useClassNames() {
|
|
|
184
144
|
[classNames]
|
|
185
145
|
);
|
|
186
146
|
}
|
|
187
|
-
var FOCUSABLE = [
|
|
188
|
-
"a[href]",
|
|
189
|
-
"button:not([disabled])",
|
|
190
|
-
'input:not([disabled]):not([type="hidden"])',
|
|
191
|
-
"select:not([disabled])",
|
|
192
|
-
"textarea:not([disabled])",
|
|
193
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
194
|
-
].join(",");
|
|
195
|
-
function focusableWithin(container) {
|
|
196
|
-
return Array.from(container.querySelectorAll(FOCUSABLE)).filter(
|
|
197
|
-
(element) => element.offsetParent !== null || element === document.activeElement || element.tabIndex >= 0
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
147
|
function useFocusTrap(containerRef, { active, onEscape }) {
|
|
201
148
|
react$1.useEffect(() => {
|
|
202
|
-
if (!active) return;
|
|
203
149
|
const container = containerRef.current;
|
|
204
|
-
if (!container) return;
|
|
205
|
-
|
|
206
|
-
if (event.key === "Escape" && onEscape) {
|
|
207
|
-
event.stopPropagation();
|
|
208
|
-
onEscape();
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (event.key !== "Tab") return;
|
|
212
|
-
const focusable = focusableWithin(container);
|
|
213
|
-
if (focusable.length === 0) {
|
|
214
|
-
event.preventDefault();
|
|
215
|
-
container.focus();
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const first = focusable[0];
|
|
219
|
-
const last = focusable[focusable.length - 1];
|
|
220
|
-
const activeElement = document.activeElement;
|
|
221
|
-
if (!activeElement || !container.contains(activeElement)) {
|
|
222
|
-
event.preventDefault();
|
|
223
|
-
(event.shiftKey ? last : first).focus();
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (!event.shiftKey && activeElement === last) {
|
|
227
|
-
event.preventDefault();
|
|
228
|
-
first.focus();
|
|
229
|
-
} else if (event.shiftKey && activeElement === first) {
|
|
230
|
-
event.preventDefault();
|
|
231
|
-
last.focus();
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
container.addEventListener("keydown", onKeyDown);
|
|
235
|
-
return () => container.removeEventListener("keydown", onKeyDown);
|
|
150
|
+
if (!active || !container) return;
|
|
151
|
+
return dom.trapFocus(container, onEscape ? { onEscape } : {});
|
|
236
152
|
}, [active, containerRef, onEscape]);
|
|
237
153
|
}
|
|
154
|
+
function AttemptStep() {
|
|
155
|
+
const { snapshot, actions } = useFlow();
|
|
156
|
+
const { messages, onCheckStatus } = useUI();
|
|
157
|
+
const cls = useClassNames();
|
|
158
|
+
const attempt = snapshot.view.attempt;
|
|
159
|
+
if (!attempt) return null;
|
|
160
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("empty"), "data-attempt": attempt.status, children: [
|
|
161
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-empty-title", children: attempt.title }),
|
|
162
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-empty-body", children: attempt.description }),
|
|
163
|
+
attempt.instructions ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-empty-body", children: attempt.instructions }) : null,
|
|
164
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("summary"), "aria-label": attempt.summaryLabel, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sl-summary-body", children: [
|
|
165
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-summary-date", children: attempt.summary.dateLabel }),
|
|
166
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-summary-meta", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
167
|
+
attempt.summary.timeLabel,
|
|
168
|
+
" \xB7 ",
|
|
169
|
+
attempt.summary.durationLabel
|
|
170
|
+
] }) })
|
|
171
|
+
] }) }),
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-empty-body", children: attempt.retryDescription }),
|
|
173
|
+
attempt.retryAllowed ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("submit"), onClick: actions.retry, children: attempt.retryLabel }) : null,
|
|
174
|
+
attempt.canCheckStatus && onCheckStatus ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("secondaryButton"), onClick: () => onCheckStatus(), children: attempt.checkStatusLabel }) : null,
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sl-visually-hidden", children: messages.attemptSummaryLabel })
|
|
176
|
+
] });
|
|
177
|
+
}
|
|
238
178
|
function ChevronLeftIcon(props) {
|
|
239
179
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", focusable: "false", ...props, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
240
180
|
"path",
|
|
@@ -353,55 +293,44 @@ function chunk(values, size) {
|
|
|
353
293
|
return rows;
|
|
354
294
|
}
|
|
355
295
|
function CalendarStep() {
|
|
356
|
-
const {
|
|
357
|
-
|
|
358
|
-
messages,
|
|
359
|
-
locale,
|
|
360
|
-
timeZone,
|
|
361
|
-
timeZoneName,
|
|
362
|
-
monthLabel,
|
|
363
|
-
monthGrid,
|
|
364
|
-
weekdayHeaders,
|
|
365
|
-
weekdayLongNames,
|
|
366
|
-
weekStartsOn,
|
|
367
|
-
actions,
|
|
368
|
-
availabilityOf,
|
|
369
|
-
isDayBookable
|
|
370
|
-
} = useFlow();
|
|
296
|
+
const { snapshot, actions } = useFlow();
|
|
297
|
+
const { messages } = useUI();
|
|
371
298
|
const cls = useClassNames();
|
|
299
|
+
const view = snapshot.view;
|
|
300
|
+
const { month, days } = view;
|
|
301
|
+
const selectedDate = view.selection.date;
|
|
372
302
|
const gridRef = react$1.useRef(null);
|
|
373
303
|
const pendingFocusRef = react$1.useRef(false);
|
|
374
304
|
const pendingMonthFocusRef = react$1.useRef(null);
|
|
375
305
|
const [focusKey, setFocusKey] = react$1.useState(null);
|
|
376
|
-
const
|
|
377
|
-
const rows = react$1.useMemo(() => chunk(monthGrid, 7), [monthGrid]);
|
|
306
|
+
const rows = react$1.useMemo(() => chunk(month.grid, 7), [month.grid]);
|
|
378
307
|
const firstBookable = react$1.useMemo(
|
|
379
|
-
() =>
|
|
380
|
-
[
|
|
308
|
+
() => month.grid.find((day) => slotlineCore.monthKeyOf(day) === month.key && days[day]?.bookable),
|
|
309
|
+
[month.grid, month.key, days]
|
|
381
310
|
);
|
|
382
311
|
const focusTarget = react$1.useMemo(() => {
|
|
383
|
-
if (focusKey &&
|
|
384
|
-
if (
|
|
385
|
-
return firstBookable ??
|
|
386
|
-
}, [focusKey,
|
|
312
|
+
if (focusKey && month.grid.includes(focusKey)) return focusKey;
|
|
313
|
+
if (selectedDate && month.grid.includes(selectedDate)) return selectedDate;
|
|
314
|
+
return firstBookable ?? month.grid[0] ?? `${month.key}-01`;
|
|
315
|
+
}, [focusKey, month.grid, selectedDate, firstBookable, month.key]);
|
|
387
316
|
react$1.useEffect(() => {
|
|
388
317
|
if (!pendingFocusRef.current || !focusKey) return;
|
|
389
318
|
pendingFocusRef.current = false;
|
|
390
319
|
const node = gridRef.current?.querySelector(`[data-day="${focusKey}"]`);
|
|
391
320
|
node?.focus();
|
|
392
|
-
}, [focusKey,
|
|
321
|
+
}, [focusKey, month.grid]);
|
|
393
322
|
react$1.useEffect(() => {
|
|
394
323
|
const dayOfMonth = pendingMonthFocusRef.current;
|
|
395
324
|
if (!dayOfMonth) return;
|
|
396
325
|
pendingMonthFocusRef.current = null;
|
|
397
|
-
const sameDay =
|
|
398
|
-
(candidate) => slotlineCore.monthKeyOf(candidate) ===
|
|
326
|
+
const sameDay = month.grid.find(
|
|
327
|
+
(candidate) => slotlineCore.monthKeyOf(candidate) === month.key && candidate.slice(8, 10) === dayOfMonth
|
|
399
328
|
);
|
|
400
|
-
const target = sameDay ??
|
|
329
|
+
const target = sameDay ?? month.grid.find((candidate) => slotlineCore.monthKeyOf(candidate) === month.key);
|
|
401
330
|
if (!target) return;
|
|
402
331
|
pendingFocusRef.current = true;
|
|
403
332
|
setFocusKey(target);
|
|
404
|
-
}, [
|
|
333
|
+
}, [month.key, month.grid]);
|
|
405
334
|
const moveFocus = react$1.useCallback((nextKey) => {
|
|
406
335
|
pendingFocusRef.current = true;
|
|
407
336
|
setFocusKey(nextKey);
|
|
@@ -410,7 +339,7 @@ function CalendarStep() {
|
|
|
410
339
|
(event) => {
|
|
411
340
|
const current = event.target.dataset?.day;
|
|
412
341
|
if (!current) return;
|
|
413
|
-
const offsetFromWeekStart = (slotlineCore.weekdayIndex(current) - weekStartsOn + 7) % 7;
|
|
342
|
+
const offsetFromWeekStart = (slotlineCore.weekdayIndex(current) - month.weekStartsOn + 7) % 7;
|
|
414
343
|
let next;
|
|
415
344
|
switch (event.key) {
|
|
416
345
|
case "ArrowLeft":
|
|
@@ -445,18 +374,18 @@ function CalendarStep() {
|
|
|
445
374
|
return;
|
|
446
375
|
}
|
|
447
376
|
event.preventDefault();
|
|
448
|
-
const last =
|
|
449
|
-
const first =
|
|
377
|
+
const last = month.grid[month.grid.length - 1];
|
|
378
|
+
const first = month.grid[0];
|
|
450
379
|
if (last && first && (next < first || next > last)) {
|
|
451
380
|
actions.goToMonth(slotlineCore.monthKeyOf(next));
|
|
452
381
|
}
|
|
453
382
|
moveFocus(next);
|
|
454
383
|
},
|
|
455
|
-
[actions,
|
|
384
|
+
[actions, month.grid, month.key, month.weekStartsOn]
|
|
456
385
|
);
|
|
457
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("calendar"), "data-status":
|
|
386
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("calendar"), "data-status": month.status, children: [
|
|
458
387
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("calendarHeader"), children: [
|
|
459
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: cls("monthLabel"), children:
|
|
388
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: cls("monthLabel"), children: month.label }),
|
|
460
389
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sl-calendar-nav", children: [
|
|
461
390
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
462
391
|
"button",
|
|
@@ -480,14 +409,14 @@ function CalendarStep() {
|
|
|
480
409
|
)
|
|
481
410
|
] })
|
|
482
411
|
] }),
|
|
483
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("weekdays"), children: weekdayHeaders.map((label, index) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cls("weekday"), children: [
|
|
412
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("weekdays"), children: month.weekdayHeaders.map((label, index) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cls("weekday"), children: [
|
|
484
413
|
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: label }),
|
|
485
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sl-visually-hidden", children: weekdayLongNames[index] })
|
|
486
|
-
] }, weekdayLongNames[index] ?? index)) }),
|
|
487
|
-
|
|
488
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
414
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sl-visually-hidden", children: month.weekdayLongNames[index] })
|
|
415
|
+
] }, month.weekdayLongNames[index] ?? index)) }),
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
|
|
417
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: month.error }),
|
|
489
418
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "sl-link-button", onClick: actions.reloadAvailability, children: messages.retry })
|
|
490
|
-
] })
|
|
419
|
+
] }),
|
|
491
420
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
492
421
|
"div",
|
|
493
422
|
{
|
|
@@ -495,17 +424,11 @@ function CalendarStep() {
|
|
|
495
424
|
className: cls("grid"),
|
|
496
425
|
role: "grid",
|
|
497
426
|
"aria-label": messages.calendarLabel,
|
|
498
|
-
"aria-busy":
|
|
427
|
+
"aria-busy": month.status === "loading",
|
|
499
428
|
onKeyDown,
|
|
500
429
|
children: rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-grid-row", role: "row", children: row.map((day, cellIndex) => {
|
|
501
|
-
const
|
|
502
|
-
const bookable =
|
|
503
|
-
const count = slotlineCore.countSlots(availabilityOf(day));
|
|
504
|
-
const selected = state.selectedDate === day;
|
|
505
|
-
const label = [
|
|
506
|
-
slotlineCore.formatFullDay(day, locale),
|
|
507
|
-
count !== void 0 && count > 0 ? slotlineCore.format(messages.slotsCount, { count }) : messages.dayUnavailable
|
|
508
|
-
].join(", ");
|
|
430
|
+
const cell = days[day];
|
|
431
|
+
const bookable = cell?.bookable ?? false;
|
|
509
432
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
510
433
|
"button",
|
|
511
434
|
{
|
|
@@ -513,14 +436,14 @@ function CalendarStep() {
|
|
|
513
436
|
role: "gridcell",
|
|
514
437
|
"data-day": day,
|
|
515
438
|
tabIndex: day === focusTarget ? 0 : -1,
|
|
516
|
-
"aria-selected":
|
|
439
|
+
"aria-selected": cell?.isSelected || void 0,
|
|
517
440
|
"aria-disabled": !bookable || void 0,
|
|
518
|
-
"aria-current":
|
|
519
|
-
"aria-label": label,
|
|
441
|
+
"aria-current": cell?.isToday ? "date" : void 0,
|
|
442
|
+
"aria-label": cell?.label,
|
|
520
443
|
className: cls(
|
|
521
444
|
"day",
|
|
522
|
-
|
|
523
|
-
|
|
445
|
+
cell?.isOutside && "is-outside",
|
|
446
|
+
cell?.isSelected && "is-selected",
|
|
524
447
|
bookable && "is-bookable"
|
|
525
448
|
),
|
|
526
449
|
style: { "--sl-index": rowIndex * 7 + cellIndex },
|
|
@@ -544,90 +467,127 @@ function CalendarStep() {
|
|
|
544
467
|
);
|
|
545
468
|
}) }, row[0]))
|
|
546
469
|
},
|
|
547
|
-
|
|
470
|
+
month.key
|
|
548
471
|
),
|
|
549
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-timezone", children:
|
|
472
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-timezone", children: view.time.zoneHint })
|
|
550
473
|
] });
|
|
551
474
|
}
|
|
552
475
|
function ConfirmedStep() {
|
|
553
|
-
const {
|
|
476
|
+
const { snapshot, actions } = useFlow();
|
|
554
477
|
const cls = useClassNames();
|
|
555
|
-
const
|
|
478
|
+
const confirmation = snapshot.view.confirmation;
|
|
479
|
+
if (!confirmation) return null;
|
|
556
480
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("confirmation"), children: [
|
|
557
481
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cls("confirmationIcon"), "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) }),
|
|
558
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: cls("confirmationTitle"), children:
|
|
482
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: cls("confirmationTitle"), children: confirmation.title }),
|
|
559
483
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("confirmationText"), children: [
|
|
560
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { children:
|
|
561
|
-
|
|
562
|
-
time: selectedTimeLabel,
|
|
563
|
-
duration: durationLabel
|
|
564
|
-
}) }),
|
|
565
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { children: slotlineCore.format(messages.inviteSent, { email: inviteeEmail }) })
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: confirmation.summary }),
|
|
485
|
+
confirmation.invitation ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: confirmation.invitation }) : null
|
|
566
486
|
] }),
|
|
567
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("secondaryButton"), onClick: actions.reset, children:
|
|
487
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("secondaryButton"), onClick: actions.reset, children: confirmation.bookAnotherLabel })
|
|
568
488
|
] });
|
|
569
489
|
}
|
|
570
|
-
function
|
|
571
|
-
name
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
490
|
+
function autoCompleteOf(name) {
|
|
491
|
+
if (name === "name") return "name";
|
|
492
|
+
if (name === "email") return "email";
|
|
493
|
+
if (name === "tel" || name === "phone") return "tel";
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
function inputTypeOf(field) {
|
|
497
|
+
if (field.type === "email") return "email";
|
|
498
|
+
if (field.type === "tel") return "tel";
|
|
499
|
+
if (field.type === "number") return "number";
|
|
500
|
+
return "text";
|
|
501
|
+
}
|
|
502
|
+
function textOf(value) {
|
|
503
|
+
if (value === void 0 || value === null || typeof value === "boolean") return "";
|
|
504
|
+
return String(value);
|
|
505
|
+
}
|
|
506
|
+
function Field({ field, value, error, disabled, autoFocus, onChange, onBlur }) {
|
|
581
507
|
const cls = useClassNames();
|
|
582
|
-
const
|
|
508
|
+
const generatedId = react$1.useId();
|
|
509
|
+
const errorId = `${generatedId}-error`;
|
|
510
|
+
const hintId = `${generatedId}-hint`;
|
|
511
|
+
const shared = {
|
|
512
|
+
id: generatedId,
|
|
513
|
+
name: field.name,
|
|
514
|
+
"aria-label": field.label,
|
|
515
|
+
"aria-invalid": error ? true : void 0,
|
|
516
|
+
"aria-describedby": error ? errorId : field.hint ? hintId : void 0,
|
|
517
|
+
disabled
|
|
518
|
+
};
|
|
583
519
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("field"), "data-invalid": error ? "true" : void 0, children: [
|
|
584
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
520
|
+
field.type === "textarea" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
521
|
+
"textarea",
|
|
522
|
+
{
|
|
523
|
+
...shared,
|
|
524
|
+
className: cls("input"),
|
|
525
|
+
placeholder: field.placeholder ?? field.label,
|
|
526
|
+
value: textOf(value),
|
|
527
|
+
onChange: (event) => onChange(event.target.value),
|
|
528
|
+
onBlur
|
|
529
|
+
}
|
|
530
|
+
) : field.type === "select" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
531
|
+
"select",
|
|
532
|
+
{
|
|
533
|
+
...shared,
|
|
534
|
+
className: cls("input"),
|
|
535
|
+
value: textOf(value),
|
|
536
|
+
onChange: (event) => onChange(event.target.value),
|
|
537
|
+
onBlur,
|
|
538
|
+
children: [
|
|
539
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: field.placeholder ?? "" }),
|
|
540
|
+
(field.options ?? []).map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
|
|
541
|
+
]
|
|
542
|
+
}
|
|
543
|
+
) : field.type === "checkbox" ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sl-checkbox", children: [
|
|
544
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
545
|
+
"input",
|
|
546
|
+
{
|
|
547
|
+
...shared,
|
|
548
|
+
type: "checkbox",
|
|
549
|
+
checked: value === true,
|
|
550
|
+
onChange: (event) => onChange(event.target.checked),
|
|
551
|
+
onBlur
|
|
552
|
+
}
|
|
553
|
+
),
|
|
554
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: field.label })
|
|
555
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
585
556
|
"input",
|
|
586
557
|
{
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
type,
|
|
590
|
-
|
|
591
|
-
autoComplete: name === "email" ? "email" : "name",
|
|
558
|
+
...shared,
|
|
559
|
+
type: inputTypeOf(field),
|
|
560
|
+
inputMode: field.type === "number" ? "numeric" : field.type === "email" ? "email" : void 0,
|
|
561
|
+
autoComplete: autoCompleteOf(field.name),
|
|
592
562
|
className: cls("input"),
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
"aria-describedby": error ? errorId : void 0,
|
|
596
|
-
placeholder: label,
|
|
597
|
-
value,
|
|
598
|
-
disabled,
|
|
563
|
+
placeholder: field.placeholder ?? field.label,
|
|
564
|
+
value: textOf(value),
|
|
599
565
|
autoFocus,
|
|
600
566
|
onChange: (event) => onChange(event.target.value),
|
|
601
567
|
onBlur
|
|
602
568
|
}
|
|
603
569
|
),
|
|
570
|
+
field.hint && !error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-field-hint", id: hintId, children: field.hint }) : null,
|
|
604
571
|
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-field-error", id: errorId, role: "alert", children: error }) : null
|
|
605
572
|
] });
|
|
606
573
|
}
|
|
607
574
|
function DetailsStep() {
|
|
608
|
-
const {
|
|
609
|
-
|
|
610
|
-
messages,
|
|
611
|
-
selectedDateLabel,
|
|
612
|
-
selectedTimeLabel,
|
|
613
|
-
durationLabel,
|
|
614
|
-
actions,
|
|
615
|
-
shownFieldErrors,
|
|
616
|
-
detailsValid
|
|
617
|
-
} = useFlow();
|
|
575
|
+
const { snapshot, actions } = useFlow();
|
|
576
|
+
const { messages } = useUI();
|
|
618
577
|
const cls = useClassNames();
|
|
619
|
-
const
|
|
578
|
+
const view = snapshot.view;
|
|
579
|
+
const form = view.form;
|
|
620
580
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sl-details-step", children: [
|
|
621
581
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("summary"), children: [
|
|
622
582
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sl-summary-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(CalendarIcon, {}) }),
|
|
623
583
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sl-summary-body", children: [
|
|
624
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-summary-date", children:
|
|
584
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-summary-date", children: view.time.dateLabel }),
|
|
625
585
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "sl-summary-meta", children: [
|
|
626
586
|
/* @__PURE__ */ jsxRuntime.jsx(ClockIcon, {}),
|
|
627
587
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
628
|
-
|
|
588
|
+
view.time.label,
|
|
629
589
|
" \xB7 ",
|
|
630
|
-
|
|
590
|
+
view.duration.label
|
|
631
591
|
] })
|
|
632
592
|
] })
|
|
633
593
|
] })
|
|
@@ -642,54 +602,34 @@ function DetailsStep() {
|
|
|
642
602
|
void actions.submit();
|
|
643
603
|
},
|
|
644
604
|
children: [
|
|
645
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
646
|
-
Field,
|
|
647
|
-
{
|
|
648
|
-
name: "name",
|
|
649
|
-
label: messages.nameLabel,
|
|
650
|
-
value: state.details.name,
|
|
651
|
-
error: shownFieldErrors.name,
|
|
652
|
-
disabled: submitting,
|
|
653
|
-
autoFocus: true,
|
|
654
|
-
onChange: (value) => actions.setDetails({ name: value }),
|
|
655
|
-
onBlur: () => actions.touchField("name")
|
|
656
|
-
}
|
|
657
|
-
),
|
|
658
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
605
|
+
form.fields.map((field, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
659
606
|
Field,
|
|
660
607
|
{
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
),
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
674
|
-
|
|
675
|
-
{
|
|
676
|
-
type: "button",
|
|
677
|
-
className: "sl-link-button",
|
|
678
|
-
onClick: () => void actions.submit(),
|
|
679
|
-
children: messages.retry
|
|
680
|
-
}
|
|
681
|
-
)
|
|
608
|
+
field,
|
|
609
|
+
value: form.values[field.name],
|
|
610
|
+
error: form.visibleErrors[field.name],
|
|
611
|
+
disabled: form.submitting,
|
|
612
|
+
autoFocus: index === 0,
|
|
613
|
+
onChange: (value) => actions.setValues({ [field.name]: value }),
|
|
614
|
+
onBlur: () => actions.touchField(field.name)
|
|
615
|
+
},
|
|
616
|
+
field.name
|
|
617
|
+
)),
|
|
618
|
+
form.formError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("error"), role: "alert", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: form.formError }) }) : null,
|
|
619
|
+
form.submitError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
|
|
620
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: form.submitError }),
|
|
621
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "sl-link-button", onClick: () => void actions.submit(), children: messages.retry })
|
|
682
622
|
] }) : null,
|
|
683
623
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
684
624
|
"button",
|
|
685
625
|
{
|
|
686
626
|
type: "submit",
|
|
687
627
|
className: cls("submit"),
|
|
688
|
-
disabled: submitting
|
|
689
|
-
"data-loading": submitting ? "true" : void 0,
|
|
628
|
+
disabled: form.submitting,
|
|
629
|
+
"data-loading": form.submitting ? "true" : void 0,
|
|
690
630
|
children: [
|
|
691
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
692
|
-
submitting ? /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { className: "sl-spinner" }) : /* @__PURE__ */ jsxRuntime.jsx(ArrowRightIcon, { className: "sl-submit-arrow" })
|
|
631
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: form.submitLabel }),
|
|
632
|
+
form.submitting ? /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { className: "sl-spinner" }) : /* @__PURE__ */ jsxRuntime.jsx(ArrowRightIcon, { className: "sl-submit-arrow" })
|
|
693
633
|
]
|
|
694
634
|
}
|
|
695
635
|
)
|
|
@@ -699,12 +639,15 @@ function DetailsStep() {
|
|
|
699
639
|
] });
|
|
700
640
|
}
|
|
701
641
|
function DurationFooter() {
|
|
702
|
-
const {
|
|
642
|
+
const { snapshot, actions } = useFlow();
|
|
643
|
+
const { messages } = useUI();
|
|
703
644
|
const cls = useClassNames();
|
|
645
|
+
const duration = snapshot.view.duration;
|
|
646
|
+
if (!duration.showSelector) return null;
|
|
704
647
|
return /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: cls("footer"), children: [
|
|
705
648
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sl-duration-label", "aria-hidden": "true", children: messages.durationLabel }),
|
|
706
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-duration-group", role: "radiogroup", "aria-label": messages.durationGroupLabel, children:
|
|
707
|
-
const active = minutes ===
|
|
649
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-duration-group", role: "radiogroup", "aria-label": messages.durationGroupLabel, children: duration.options.map((minutes) => {
|
|
650
|
+
const active = minutes === duration.current;
|
|
708
651
|
const label = slotlineCore.format(messages.duration, { count: minutes });
|
|
709
652
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
710
653
|
"button",
|
|
@@ -724,26 +667,23 @@ function DurationFooter() {
|
|
|
724
667
|
] });
|
|
725
668
|
}
|
|
726
669
|
function Progress() {
|
|
727
|
-
const {
|
|
670
|
+
const { snapshot } = useFlow();
|
|
728
671
|
const cls = useClassNames();
|
|
729
|
-
const
|
|
672
|
+
const { progress } = snapshot.view;
|
|
730
673
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
731
674
|
"div",
|
|
732
675
|
{
|
|
733
676
|
className: cls("progress"),
|
|
734
677
|
role: "progressbar",
|
|
735
678
|
"aria-valuemin": 1,
|
|
736
|
-
"aria-valuemax":
|
|
737
|
-
"aria-valuenow":
|
|
738
|
-
"aria-valuetext":
|
|
739
|
-
|
|
740
|
-
total: slotlineCore.FLOW_STEPS.length
|
|
741
|
-
}),
|
|
742
|
-
children: slotlineCore.FLOW_STEPS.map((step, position) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
679
|
+
"aria-valuemax": progress.total,
|
|
680
|
+
"aria-valuenow": progress.current,
|
|
681
|
+
"aria-valuetext": progress.label,
|
|
682
|
+
children: progress.steps.map((step, position) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
743
683
|
"span",
|
|
744
684
|
{
|
|
745
685
|
className: "sl-progress-pill",
|
|
746
|
-
"data-state": position
|
|
686
|
+
"data-state": position < progress.current ? "done" : "todo",
|
|
747
687
|
"aria-hidden": "true"
|
|
748
688
|
},
|
|
749
689
|
step
|
|
@@ -753,17 +693,19 @@ function Progress() {
|
|
|
753
693
|
}
|
|
754
694
|
var SKELETON_COUNT = 9;
|
|
755
695
|
function SlotsStep() {
|
|
756
|
-
const {
|
|
696
|
+
const { snapshot, actions } = useFlow();
|
|
697
|
+
const { messages } = useUI();
|
|
757
698
|
const cls = useClassNames();
|
|
758
|
-
const
|
|
759
|
-
const
|
|
699
|
+
const Animations = useUI().animations;
|
|
700
|
+
const view = snapshot.view;
|
|
701
|
+
const { slots, time } = view;
|
|
760
702
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sl-slots-step", children: [
|
|
761
703
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("slotsHeader"), children: [
|
|
762
704
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sl-slots-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(CalendarIcon, {}) }),
|
|
763
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "sl-slots-title", children:
|
|
764
|
-
|
|
705
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "sl-slots-title", children: time.dateLabel }),
|
|
706
|
+
slots.countLabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cls("slotsCount"), children: slots.countLabel }) : null
|
|
765
707
|
] }),
|
|
766
|
-
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("slots"), "aria-hidden": "true", children: Array.from({ length: SKELETON_COUNT }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
708
|
+
slots.loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("slots"), "aria-hidden": "true", children: Array.from({ length: SKELETON_COUNT }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
767
709
|
"div",
|
|
768
710
|
{
|
|
769
711
|
className: cls("skeleton"),
|
|
@@ -771,38 +713,38 @@ function SlotsStep() {
|
|
|
771
713
|
},
|
|
772
714
|
index
|
|
773
715
|
)) }) : null,
|
|
774
|
-
|
|
775
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
716
|
+
slots.status === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
|
|
717
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: slots.error }),
|
|
776
718
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "sl-link-button", onClick: actions.reloadSlots, children: messages.retry })
|
|
777
719
|
] }) : null,
|
|
778
|
-
|
|
720
|
+
slots.empty ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("empty"), children: [
|
|
779
721
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-empty-title", children: messages.noSlotsTitle }),
|
|
780
722
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-empty-body", children: messages.noSlotsBody }),
|
|
781
723
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("secondaryButton"), onClick: actions.back, children: messages.pickAnotherDay })
|
|
782
724
|
] }) : null,
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
);
|
|
796
|
-
}) }) : null
|
|
725
|
+
slots.status === "ready" && slots.count > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("slots"), role: "group", "aria-label": slots.label, children: slots.items.map((slot, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
726
|
+
Animations.Item,
|
|
727
|
+
{
|
|
728
|
+
index,
|
|
729
|
+
className: cls("slot", slot.selected && "is-selected"),
|
|
730
|
+
"aria-current": slot.selected ? "true" : void 0,
|
|
731
|
+
"aria-label": slot.utcOffsetLabel ? `${slot.label} ${slot.utcOffsetLabel}` : void 0,
|
|
732
|
+
onClick: () => actions.selectSlot(slot),
|
|
733
|
+
children: slot.label
|
|
734
|
+
},
|
|
735
|
+
slot.id
|
|
736
|
+
)) }) : null
|
|
797
737
|
] });
|
|
798
738
|
}
|
|
799
739
|
function StepBody() {
|
|
800
|
-
const {
|
|
801
|
-
switch (
|
|
740
|
+
const { snapshot } = useFlow();
|
|
741
|
+
switch (snapshot.view.step) {
|
|
802
742
|
case "time":
|
|
803
743
|
return /* @__PURE__ */ jsxRuntime.jsx(SlotsStep, {});
|
|
804
744
|
case "details":
|
|
805
745
|
return /* @__PURE__ */ jsxRuntime.jsx(DetailsStep, {});
|
|
746
|
+
case "attempt":
|
|
747
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AttemptStep, {});
|
|
806
748
|
case "confirmed":
|
|
807
749
|
return /* @__PURE__ */ jsxRuntime.jsx(ConfirmedStep, {});
|
|
808
750
|
case "date":
|
|
@@ -811,15 +753,15 @@ function StepBody() {
|
|
|
811
753
|
}
|
|
812
754
|
}
|
|
813
755
|
function SlotlineView() {
|
|
814
|
-
const
|
|
815
|
-
const { state, messages, title, durationLabel, actions, canGoBack } = flow;
|
|
816
|
-
const submitting = state.submitStatus === "submitting";
|
|
756
|
+
const { snapshot, actions } = useFlow();
|
|
817
757
|
const ui = useUI();
|
|
818
758
|
const cls = useClassNames();
|
|
819
759
|
const Animations = ui.animations;
|
|
760
|
+
const view = snapshot.view;
|
|
820
761
|
const cardRef = react$1.useRef(null);
|
|
821
762
|
const panelRef = react$1.useRef(null);
|
|
822
763
|
const mountedRef = react$1.useRef(false);
|
|
764
|
+
const canGoBack = view.actions.canGoBack || (view.step === "date" || view.step === "confirmed") && ui.canDismiss;
|
|
823
765
|
useFocusTrap(cardRef, {
|
|
824
766
|
active: ui.trapFocus,
|
|
825
767
|
...canGoBack ? { onEscape: actions.back } : {}
|
|
@@ -832,22 +774,14 @@ function SlotlineView() {
|
|
|
832
774
|
}
|
|
833
775
|
const active = typeof document === "undefined" ? null : document.activeElement;
|
|
834
776
|
if (!active || active === document.body) panelRef.current?.focus();
|
|
835
|
-
}, [
|
|
836
|
-
const announcement = state.step === "confirmed" ? `${messages.bookedTitle}. ${slotlineCore.format(messages.bookedSummary, {
|
|
837
|
-
date: flow.selectedDateLabel,
|
|
838
|
-
time: flow.selectedTimeLabel,
|
|
839
|
-
duration: durationLabel
|
|
840
|
-
})}` : slotlineCore.format(messages.progressLabel, {
|
|
841
|
-
step: slotlineCore.stepIndex(state.step) + 1,
|
|
842
|
-
total: slotlineCore.FLOW_STEPS.length
|
|
843
|
-
});
|
|
777
|
+
}, [view.step, ui.autoFocus]);
|
|
844
778
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
845
779
|
"div",
|
|
846
780
|
{
|
|
847
781
|
className: cls("root", ui.className),
|
|
848
|
-
"data-sl-theme":
|
|
849
|
-
"data-sl-step":
|
|
850
|
-
lang:
|
|
782
|
+
"data-sl-theme": ui.theme,
|
|
783
|
+
"data-sl-step": view.step,
|
|
784
|
+
lang: ui.locale,
|
|
851
785
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("card"), ref: cardRef, children: [
|
|
852
786
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: cls("header"), children: [
|
|
853
787
|
canGoBack ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -856,373 +790,183 @@ function SlotlineView() {
|
|
|
856
790
|
type: "button",
|
|
857
791
|
className: "sl-icon-button",
|
|
858
792
|
onClick: actions.back,
|
|
859
|
-
"aria-label": messages.back,
|
|
860
|
-
disabled: submitting,
|
|
793
|
+
"aria-label": ui.messages.back,
|
|
794
|
+
disabled: view.form.submitting,
|
|
861
795
|
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon, {})
|
|
862
796
|
}
|
|
863
797
|
) : null,
|
|
864
798
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sl-header-main", children: [
|
|
865
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: cls("title"), children: title }),
|
|
866
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cls("badge"), children:
|
|
799
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: cls("title"), children: view.title }),
|
|
800
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cls("badge"), children: view.duration.label })
|
|
867
801
|
] }),
|
|
868
802
|
/* @__PURE__ */ jsxRuntime.jsx(Progress, {})
|
|
869
803
|
] }),
|
|
870
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("panel"), ref: panelRef, tabIndex: -1, children: /* @__PURE__ */ jsxRuntime.jsx(Animations.Height, { children: /* @__PURE__ */ jsxRuntime.jsx(Animations.StepHost, { stepKey:
|
|
871
|
-
|
|
872
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-visually-hidden", role: "status", "aria-live": "polite", children:
|
|
804
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("panel"), ref: panelRef, tabIndex: -1, children: /* @__PURE__ */ jsxRuntime.jsx(Animations.Height, { children: /* @__PURE__ */ jsxRuntime.jsx(Animations.StepHost, { stepKey: view.step, direction: view.direction, children: /* @__PURE__ */ jsxRuntime.jsx(StepBody, {}) }) }) }),
|
|
805
|
+
view.step === "date" ? /* @__PURE__ */ jsxRuntime.jsx(DurationFooter, {}) : null,
|
|
806
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-visually-hidden", role: "status", "aria-live": "polite", children: view.live.message })
|
|
873
807
|
] })
|
|
874
808
|
}
|
|
875
809
|
);
|
|
876
810
|
}
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
onChange
|
|
881
|
-
}) {
|
|
882
|
-
const [internal, setInternal] = react$1.useState(defaultValue);
|
|
883
|
-
const isControlled = value !== void 0;
|
|
884
|
-
const current = isControlled ? value : internal;
|
|
885
|
-
const onChangeRef = react$1.useRef(onChange);
|
|
886
|
-
onChangeRef.current = onChange;
|
|
887
|
-
const setValue = react$1.useCallback(
|
|
888
|
-
(next) => {
|
|
889
|
-
if (!isControlled) setInternal(next);
|
|
890
|
-
onChangeRef.current?.(next);
|
|
891
|
-
},
|
|
892
|
-
[isControlled]
|
|
893
|
-
);
|
|
894
|
-
return [current, setValue];
|
|
811
|
+
function hostTheme() {
|
|
812
|
+
if (!dom.canMatchMedia()) return "dark";
|
|
813
|
+
return dom.matchesMediaQuery(dom.DARK_SCHEME_QUERY) ? "dark" : "light";
|
|
895
814
|
}
|
|
896
815
|
function useResolvedTheme(theme) {
|
|
897
|
-
const [
|
|
898
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "dark";
|
|
899
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
900
|
-
});
|
|
816
|
+
const [resolved, setResolved] = react$1.useState(hostTheme);
|
|
901
817
|
react$1.useEffect(() => {
|
|
902
818
|
if (theme !== "system") return;
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
return () => list.removeEventListener("change", handler);
|
|
819
|
+
return dom.observeMediaQuery(
|
|
820
|
+
dom.DARK_SCHEME_QUERY,
|
|
821
|
+
(matches) => setResolved(matches ? "dark" : "light")
|
|
822
|
+
);
|
|
908
823
|
}, [theme]);
|
|
909
|
-
|
|
910
|
-
return theme;
|
|
824
|
+
return theme === "system" ? resolved : theme;
|
|
911
825
|
}
|
|
912
826
|
|
|
913
827
|
// src/useBookingFlow.ts
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const error = toError(value);
|
|
919
|
-
return error.message && error.message !== "undefined" ? error.message : fallback;
|
|
828
|
+
var cachedBrowserDefaults = null;
|
|
829
|
+
function readBrowserDefaults() {
|
|
830
|
+
cachedBrowserDefaults ??= { locale: slotlineCore.detectLocale(), timeZone: slotlineCore.resolveTimeZone() };
|
|
831
|
+
return cachedBrowserDefaults;
|
|
920
832
|
}
|
|
921
|
-
function
|
|
922
|
-
const
|
|
923
|
-
adapter,
|
|
924
|
-
durations = [15, 30, 60],
|
|
925
|
-
duration: durationProp,
|
|
926
|
-
defaultDuration,
|
|
927
|
-
onDurationChange,
|
|
928
|
-
timeZone: timeZoneProp,
|
|
929
|
-
locale: localeProp,
|
|
930
|
-
messages: messagesProp,
|
|
931
|
-
theme = "dark",
|
|
932
|
-
minDate: minDateProp,
|
|
933
|
-
maxDate: maxDateProp,
|
|
934
|
-
weekStartsOn = 0,
|
|
935
|
-
onBooked,
|
|
936
|
-
onError,
|
|
937
|
-
onDismiss
|
|
938
|
-
} = props;
|
|
939
|
-
const locale = localeProp ?? slotlineCore.detectLocale();
|
|
940
|
-
const timeZone = timeZoneProp ?? slotlineCore.resolveTimeZone();
|
|
941
|
-
const resolvedTheme = useResolvedTheme(theme);
|
|
942
|
-
const messages = react$1.useMemo(() => slotlineCore.resolveMessages(locale, messagesProp), [locale, messagesProp]);
|
|
943
|
-
const durationOptions = react$1.useMemo(
|
|
944
|
-
() => durations.length > 0 ? durations : [15, 30, 60],
|
|
945
|
-
[durations]
|
|
946
|
-
);
|
|
947
|
-
const [duration, setDurationValue] = useControllableState({
|
|
948
|
-
value: durationProp,
|
|
949
|
-
defaultValue: defaultDuration ?? durationOptions[0] ?? 15,
|
|
950
|
-
onChange: onDurationChange
|
|
951
|
-
});
|
|
952
|
-
const minDate = react$1.useMemo(() => slotlineCore.normalizeDateInput(minDateProp, timeZone), [minDateProp, timeZone]);
|
|
953
|
-
const maxDate = react$1.useMemo(() => slotlineCore.normalizeDateInput(maxDateProp, timeZone), [maxDateProp, timeZone]);
|
|
954
|
-
const today = slotlineCore.todayKey(timeZone);
|
|
955
|
-
const earliestDate = minDate ?? today;
|
|
956
|
-
const initialMonth = slotlineCore.monthKeyOf(slotlineCore.clampDayKey(earliestDate, minDate, maxDate));
|
|
957
|
-
const [state, dispatch] = react$1.useReducer(
|
|
958
|
-
slotlineCore.flowReducer,
|
|
959
|
-
void 0,
|
|
960
|
-
() => slotlineCore.createFlowState({ duration, month: initialMonth })
|
|
961
|
-
);
|
|
962
|
-
const adapterRef = react$1.useRef(adapter);
|
|
963
|
-
adapterRef.current = adapter;
|
|
964
|
-
const stateRef = react$1.useRef(state);
|
|
965
|
-
stateRef.current = state;
|
|
966
|
-
const messagesRef = react$1.useRef(messages);
|
|
967
|
-
messagesRef.current = messages;
|
|
968
|
-
const onErrorRef = react$1.useRef(onError);
|
|
969
|
-
onErrorRef.current = onError;
|
|
970
|
-
const onBookedRef = react$1.useRef(onBooked);
|
|
971
|
-
onBookedRef.current = onBooked;
|
|
972
|
-
const submitLockRef = react$1.useRef(false);
|
|
973
|
-
const daysTokenRef = react$1.useRef(0);
|
|
974
|
-
const slotsTokenRef = react$1.useRef(0);
|
|
975
|
-
const [availabilityNonce, setAvailabilityNonce] = react$1.useReducer((n) => n + 1, 0);
|
|
976
|
-
const [slotsNonce, setSlotsNonce] = react$1.useReducer((n) => n + 1, 0);
|
|
977
|
-
react$1.useEffect(() => {
|
|
978
|
-
dispatch({ type: "setDuration", duration });
|
|
979
|
-
}, [duration]);
|
|
980
|
-
react$1.useEffect(() => {
|
|
981
|
-
const controller = new AbortController();
|
|
982
|
-
const token = daysTokenRef.current += 1;
|
|
983
|
-
const from = slotlineCore.firstDayOfMonth(state.month);
|
|
984
|
-
const to = slotlineCore.lastDayOfMonth(state.month);
|
|
985
|
-
dispatch({ type: "daysLoading", token });
|
|
986
|
-
void (async () => {
|
|
987
|
-
try {
|
|
988
|
-
const days = await adapterRef.current.getAvailability({
|
|
989
|
-
from,
|
|
990
|
-
to,
|
|
991
|
-
duration,
|
|
992
|
-
timeZone,
|
|
993
|
-
signal: controller.signal
|
|
994
|
-
});
|
|
995
|
-
if (controller.signal.aborted) return;
|
|
996
|
-
dispatch({ type: "daysLoaded", token, days: Array.isArray(days) ? days : [] });
|
|
997
|
-
} catch (error) {
|
|
998
|
-
if (controller.signal.aborted) return;
|
|
999
|
-
dispatch({
|
|
1000
|
-
type: "daysFailed",
|
|
1001
|
-
token,
|
|
1002
|
-
error: messageOf(error, messagesRef.current.genericError)
|
|
1003
|
-
});
|
|
1004
|
-
onErrorRef.current?.(toError(error));
|
|
1005
|
-
}
|
|
1006
|
-
})();
|
|
1007
|
-
return () => controller.abort();
|
|
1008
|
-
}, [state.month, duration, timeZone, availabilityNonce]);
|
|
1009
|
-
const daysRef = react$1.useRef(state.days);
|
|
1010
|
-
daysRef.current = state.days;
|
|
833
|
+
function useBrowserDefaults() {
|
|
834
|
+
const [defaults, setDefaults] = react$1.useState(null);
|
|
1011
835
|
react$1.useEffect(() => {
|
|
1012
|
-
|
|
1013
|
-
if (!date) return;
|
|
1014
|
-
const token = slotsTokenRef.current += 1;
|
|
1015
|
-
dispatch({ type: "slotsLoading", token });
|
|
1016
|
-
const inline = daysRef.current[date]?.slots;
|
|
1017
|
-
if (inline && inline.length > 0) {
|
|
1018
|
-
dispatch({ type: "slotsLoaded", token, slots: inline });
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
const controller = new AbortController();
|
|
1022
|
-
void (async () => {
|
|
1023
|
-
try {
|
|
1024
|
-
const slots = await adapterRef.current.getSlots({
|
|
1025
|
-
date,
|
|
1026
|
-
duration,
|
|
1027
|
-
timeZone,
|
|
1028
|
-
signal: controller.signal
|
|
1029
|
-
});
|
|
1030
|
-
if (controller.signal.aborted) return;
|
|
1031
|
-
dispatch({ type: "slotsLoaded", token, slots: Array.isArray(slots) ? slots : [] });
|
|
1032
|
-
} catch (error) {
|
|
1033
|
-
if (controller.signal.aborted) return;
|
|
1034
|
-
dispatch({
|
|
1035
|
-
type: "slotsFailed",
|
|
1036
|
-
token,
|
|
1037
|
-
error: messageOf(error, messagesRef.current.genericError)
|
|
1038
|
-
});
|
|
1039
|
-
onErrorRef.current?.(toError(error));
|
|
1040
|
-
}
|
|
1041
|
-
})();
|
|
1042
|
-
return () => controller.abort();
|
|
1043
|
-
}, [state.selectedDate, state.selectionNonce, duration, timeZone, slotsNonce]);
|
|
1044
|
-
const selectDate = react$1.useCallback((dayKey) => {
|
|
1045
|
-
dispatch({ type: "selectDate", date: dayKey });
|
|
1046
|
-
}, []);
|
|
1047
|
-
const selectSlot = react$1.useCallback((slot) => {
|
|
1048
|
-
dispatch({ type: "selectSlot", slot });
|
|
1049
|
-
}, []);
|
|
1050
|
-
const setDuration = react$1.useCallback(
|
|
1051
|
-
(minutes) => {
|
|
1052
|
-
setDurationValue(minutes);
|
|
1053
|
-
},
|
|
1054
|
-
[setDurationValue]
|
|
1055
|
-
);
|
|
1056
|
-
const setDetails = react$1.useCallback(
|
|
1057
|
-
(details) => {
|
|
1058
|
-
dispatch({ type: "setDetails", details });
|
|
1059
|
-
},
|
|
1060
|
-
[]
|
|
1061
|
-
);
|
|
1062
|
-
const goToMonth = react$1.useCallback((monthKey) => {
|
|
1063
|
-
dispatch({ type: "setMonth", month: monthKey });
|
|
1064
|
-
}, []);
|
|
1065
|
-
const touchField = react$1.useCallback((field) => {
|
|
1066
|
-
dispatch({ type: "touch", field });
|
|
1067
|
-
}, []);
|
|
1068
|
-
const reset = react$1.useCallback(() => {
|
|
1069
|
-
dispatch({ type: "reset" });
|
|
836
|
+
setDefaults(readBrowserDefaults());
|
|
1070
837
|
}, []);
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
if (current.step === "date" || current.step === "confirmed") {
|
|
1075
|
-
onDismiss?.();
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
dispatch({ type: "back" });
|
|
1079
|
-
}, [onDismiss]);
|
|
1080
|
-
const reloadAvailability = react$1.useCallback(() => {
|
|
1081
|
-
setAvailabilityNonce();
|
|
1082
|
-
}, []);
|
|
1083
|
-
const reloadSlots = react$1.useCallback(() => {
|
|
1084
|
-
setSlotsNonce();
|
|
1085
|
-
}, []);
|
|
1086
|
-
const submit = react$1.useCallback(async () => {
|
|
1087
|
-
if (submitLockRef.current) return;
|
|
1088
|
-
const snapshot = stateRef.current;
|
|
1089
|
-
const currentMessages = messagesRef.current;
|
|
1090
|
-
dispatch({ type: "touch", field: "name" });
|
|
1091
|
-
dispatch({ type: "touch", field: "email" });
|
|
1092
|
-
const errors = slotlineCore.validateDetails(snapshot.details, currentMessages);
|
|
1093
|
-
if (slotlineCore.hasErrors(errors) || !snapshot.selectedSlot || !snapshot.selectedDate) return;
|
|
1094
|
-
const request = {
|
|
1095
|
-
slot: { ...snapshot.selectedSlot, end: slotlineCore.endOf(snapshot.selectedSlot, snapshot.duration) },
|
|
1096
|
-
date: snapshot.selectedDate,
|
|
1097
|
-
duration: snapshot.duration,
|
|
1098
|
-
timeZone,
|
|
1099
|
-
details: {
|
|
1100
|
-
name: snapshot.details.name.trim(),
|
|
1101
|
-
email: snapshot.details.email.trim(),
|
|
1102
|
-
...snapshot.details.notes ? { notes: snapshot.details.notes } : {}
|
|
1103
|
-
}
|
|
1104
|
-
};
|
|
1105
|
-
const scenario = snapshot.scenarioId;
|
|
1106
|
-
const stillCurrent = () => {
|
|
1107
|
-
const current = stateRef.current;
|
|
1108
|
-
return (
|
|
1109
|
-
// `scenarioId` also covers "reset, then book the very same slot again":
|
|
1110
|
-
// the date and the slot can match a second time, the scenario cannot.
|
|
1111
|
-
current.scenarioId === scenario && current.selectedDate === request.date && current.selectedSlot?.start === request.slot.start
|
|
1112
|
-
);
|
|
1113
|
-
};
|
|
1114
|
-
submitLockRef.current = true;
|
|
1115
|
-
dispatch({ type: "submitStart" });
|
|
1116
|
-
try {
|
|
1117
|
-
const result = await adapterRef.current.createBooking(request);
|
|
1118
|
-
onBookedRef.current?.(result ?? {}, request);
|
|
1119
|
-
if (stillCurrent()) dispatch({ type: "submitSucceeded", scenario, result: result ?? {} });
|
|
1120
|
-
} catch (error) {
|
|
1121
|
-
onErrorRef.current?.(toError(error));
|
|
1122
|
-
if (stillCurrent()) {
|
|
1123
|
-
dispatch({
|
|
1124
|
-
type: "submitFailed",
|
|
1125
|
-
scenario,
|
|
1126
|
-
error: messageOf(error, currentMessages.genericError)
|
|
1127
|
-
});
|
|
1128
|
-
}
|
|
1129
|
-
} finally {
|
|
1130
|
-
submitLockRef.current = false;
|
|
1131
|
-
}
|
|
1132
|
-
}, [timeZone]);
|
|
1133
|
-
const monthGrid = react$1.useMemo(
|
|
1134
|
-
() => slotlineCore.buildMonthGrid(state.month, weekStartsOn),
|
|
1135
|
-
[state.month, weekStartsOn]
|
|
1136
|
-
);
|
|
1137
|
-
const weekdayHeaders = react$1.useMemo(
|
|
1138
|
-
() => slotlineCore.rotateToWeekStart(slotlineCore.shortWeekdayNames(locale, messages.weekdays), weekStartsOn),
|
|
1139
|
-
[locale, messages.weekdays, weekStartsOn]
|
|
1140
|
-
);
|
|
1141
|
-
const weekdayLong = react$1.useMemo(
|
|
1142
|
-
() => slotlineCore.rotateToWeekStart(slotlineCore.weekdayNames(locale), weekStartsOn),
|
|
1143
|
-
[locale, weekStartsOn]
|
|
1144
|
-
);
|
|
1145
|
-
const fieldErrors = react$1.useMemo(
|
|
1146
|
-
() => slotlineCore.validateDetails(state.details, messages),
|
|
1147
|
-
[state.details, messages]
|
|
1148
|
-
);
|
|
1149
|
-
const availabilityOf = react$1.useCallback((dayKey) => state.days[dayKey], [state.days]);
|
|
1150
|
-
const isDayBookable = react$1.useCallback(
|
|
1151
|
-
(dayKey) => {
|
|
1152
|
-
if (slotlineCore.compareDayKeys(dayKey, earliestDate) < 0) return false;
|
|
1153
|
-
if (maxDate && slotlineCore.compareDayKeys(dayKey, maxDate) > 0) return false;
|
|
1154
|
-
const availability = state.days[dayKey];
|
|
1155
|
-
if (!availability) return false;
|
|
1156
|
-
const count = slotlineCore.countSlots(availability);
|
|
1157
|
-
return count === void 0 ? true : count > 0;
|
|
1158
|
-
},
|
|
1159
|
-
[state.days, earliestDate, maxDate]
|
|
1160
|
-
);
|
|
1161
|
-
const selectedDateLabel = state.selectedDate ? slotlineCore.formatFullDay(state.selectedDate, locale) : "";
|
|
1162
|
-
const selectedTimeLabel = state.selectedSlot ? slotlineCore.formatTime(state.selectedSlot.start, timeZone, locale) : "";
|
|
838
|
+
return defaults;
|
|
839
|
+
}
|
|
840
|
+
function optionsOf(props, browser) {
|
|
1163
841
|
return {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
timeZoneName: state.step === "date" ? slotlineCore.formatTimeZoneName(timeZone, locale) : void 0,
|
|
1169
|
-
theme: resolvedTheme,
|
|
1170
|
-
title: state.step === "confirmed" ? messages.bookedHeader : props.title ?? messages.title,
|
|
1171
|
-
durations: durationOptions,
|
|
1172
|
-
weekStartsOn,
|
|
1173
|
-
...minDate ? { minDate } : {},
|
|
1174
|
-
...maxDate ? { maxDate } : {},
|
|
1175
|
-
monthGrid,
|
|
1176
|
-
weekdayHeaders,
|
|
1177
|
-
weekdayLongNames: weekdayLong,
|
|
1178
|
-
monthLabel: slotlineCore.formatMonthYear(state.month, locale),
|
|
1179
|
-
selectedDateLabel,
|
|
1180
|
-
selectedTimeLabel,
|
|
1181
|
-
durationLabel: slotlineCore.format(messages.duration, { count: duration }),
|
|
1182
|
-
availabilityOf,
|
|
1183
|
-
isDayBookable,
|
|
1184
|
-
// The confirmation is terminal: there is nothing to go back to, so the back
|
|
1185
|
-
// affordance only appears there when the consumer supplied `onDismiss`.
|
|
1186
|
-
canGoBack: state.step !== "date" && state.step !== "confirmed" || Boolean(onDismiss),
|
|
1187
|
-
fieldErrors,
|
|
1188
|
-
shownFieldErrors: slotlineCore.visibleErrors(fieldErrors, state.touched),
|
|
1189
|
-
detailsValid: !slotlineCore.hasErrors(fieldErrors),
|
|
1190
|
-
actions: {
|
|
1191
|
-
selectDate,
|
|
1192
|
-
selectSlot,
|
|
1193
|
-
setDuration,
|
|
1194
|
-
setDetails,
|
|
1195
|
-
touchField,
|
|
1196
|
-
back,
|
|
1197
|
-
submit,
|
|
1198
|
-
reset,
|
|
1199
|
-
goToMonth,
|
|
1200
|
-
previousMonth: () => goToMonth(slotlineCore.addMonths(stateRef.current.month, -1)),
|
|
1201
|
-
nextMonth: () => goToMonth(slotlineCore.addMonths(stateRef.current.month, 1)),
|
|
1202
|
-
reloadAvailability,
|
|
1203
|
-
reloadSlots
|
|
1204
|
-
}
|
|
842
|
+
adapter: adapterOf(props),
|
|
843
|
+
config: configOf(props, browser),
|
|
844
|
+
inputs: inputsOf(props, browser),
|
|
845
|
+
...props.attempt == null ? {} : { initialAttempt: props.attempt }
|
|
1205
846
|
};
|
|
1206
847
|
}
|
|
1207
|
-
function
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
848
|
+
function adapterOf(props) {
|
|
849
|
+
return props.adapter;
|
|
850
|
+
}
|
|
851
|
+
function configOf(props, browser) {
|
|
852
|
+
const locale = props.locale ?? browser?.locale;
|
|
853
|
+
return {
|
|
854
|
+
...locale === void 0 ? {} : { locale },
|
|
855
|
+
...props.messages === void 0 ? {} : { messages: props.messages },
|
|
856
|
+
...props.weekStartsOn === void 0 ? {} : { weekStartsOn: props.weekStartsOn },
|
|
857
|
+
...props.durations === void 0 ? {} : { durationOptions: props.durations },
|
|
858
|
+
...props.minDate === void 0 ? {} : { minDate: props.minDate },
|
|
859
|
+
...props.maxDate === void 0 ? {} : { maxDate: props.maxDate },
|
|
860
|
+
...props.title === void 0 ? {} : { title: props.title },
|
|
861
|
+
...props.context === void 0 ? {} : { context: props.context }
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function inputsOf(props, browser) {
|
|
865
|
+
const inputs = {};
|
|
866
|
+
if (props.timeZone !== void 0) inputs.timeZone = props.timeZone;
|
|
867
|
+
else {
|
|
868
|
+
const timeZone = props.defaultTimeZone ?? browser?.timeZone;
|
|
869
|
+
if (timeZone !== void 0) inputs.defaultTimeZone = timeZone;
|
|
870
|
+
}
|
|
871
|
+
if (props.duration !== void 0) inputs.duration = props.duration;
|
|
872
|
+
if (props.defaultDuration !== void 0) inputs.defaultDuration = props.defaultDuration;
|
|
873
|
+
if (props.timeFormat !== void 0) inputs.timeFormat = props.timeFormat;
|
|
874
|
+
else if (props.defaultTimeFormat !== void 0)
|
|
875
|
+
inputs.defaultTimeFormat = props.defaultTimeFormat;
|
|
876
|
+
return inputs;
|
|
877
|
+
}
|
|
878
|
+
function useBookingController(props) {
|
|
879
|
+
const browser = useBrowserDefaults();
|
|
880
|
+
const propsRef = react$1.useRef(props);
|
|
881
|
+
propsRef.current = props;
|
|
882
|
+
const browserRef = react$1.useRef(browser);
|
|
883
|
+
browserRef.current = browser;
|
|
884
|
+
const controllerRef = react$1.useRef(null);
|
|
885
|
+
if (controllerRef.current === null) {
|
|
886
|
+
controllerRef.current = slotlineCore.createBookingController({
|
|
887
|
+
...optionsOf(props, browser),
|
|
888
|
+
handlers: {
|
|
889
|
+
// Every handler reads the latest props, so a re-render never leaves a
|
|
890
|
+
// stale callback behind.
|
|
891
|
+
onBooked: (result, request) => (
|
|
892
|
+
// The flow carries the payload as plain values; the adapter's own type is
|
|
893
|
+
// what the application declared for its form.
|
|
894
|
+
propsRef.current.onBooked?.(result, request)
|
|
895
|
+
),
|
|
896
|
+
onError: (error, context) => propsRef.current.onError?.(error, context),
|
|
897
|
+
onDismiss: () => propsRef.current.onDismiss?.(),
|
|
898
|
+
onDurationChange: (duration, metadata) => propsRef.current.onDurationChange?.(duration, metadata),
|
|
899
|
+
onTimeFormatChange: (timeFormat, metadata) => propsRef.current.onTimeFormatChange?.(timeFormat, metadata),
|
|
900
|
+
onContextChange: (context, metadata) => propsRef.current.onContextChange?.(context, metadata)
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const controller = controllerRef.current;
|
|
905
|
+
react$1.useEffect(() => {
|
|
906
|
+
controller.updateAdapter(adapterOf(propsRef.current));
|
|
907
|
+
controller.updateConfig(configOf(propsRef.current, browserRef.current));
|
|
908
|
+
controller.updateInputs(inputsOf(propsRef.current, browserRef.current));
|
|
909
|
+
});
|
|
910
|
+
react$1.useEffect(() => {
|
|
911
|
+
controller.start();
|
|
912
|
+
return () => controller.stop();
|
|
913
|
+
}, [controller]);
|
|
914
|
+
react$1.useEffect(() => dom.observeVisibilityChange(() => controller.refreshTime()), [controller]);
|
|
915
|
+
return controller;
|
|
916
|
+
}
|
|
917
|
+
function useSubscribedFlow(controller) {
|
|
918
|
+
const snapshot = react$1.useSyncExternalStore(
|
|
919
|
+
controller.subscribe,
|
|
920
|
+
controller.getSnapshot,
|
|
921
|
+
controller.getSnapshot
|
|
922
|
+
);
|
|
923
|
+
return react$1.useMemo(() => ({ snapshot, actions: controller.actions }), [snapshot, controller]);
|
|
924
|
+
}
|
|
925
|
+
function useBookingFlow(props) {
|
|
926
|
+
return useSubscribedFlow(useBookingController(props));
|
|
927
|
+
}
|
|
928
|
+
function useSlotlineUI(props) {
|
|
929
|
+
const browser = useBrowserDefaults();
|
|
930
|
+
const locale = props.locale ?? browser?.locale ?? "en";
|
|
931
|
+
const messages = react$1.useMemo(() => slotlineCore.resolveMessages(locale, props.messages), [locale, props.messages]);
|
|
932
|
+
const theme = useResolvedTheme(props.theme ?? "dark");
|
|
933
|
+
return react$1.useMemo(
|
|
1211
934
|
() => ({
|
|
1212
935
|
animations: props.animations ?? cssAnimations,
|
|
1213
936
|
classNames: props.classNames,
|
|
937
|
+
className: props.className,
|
|
1214
938
|
autoFocus: props.autoFocus ?? false,
|
|
1215
939
|
trapFocus: props.trapFocus ?? false,
|
|
1216
|
-
|
|
940
|
+
canDismiss: Boolean(props.onDismiss),
|
|
941
|
+
onCheckStatus: props.onCheckStatus,
|
|
942
|
+
messages,
|
|
943
|
+
locale,
|
|
944
|
+
theme
|
|
1217
945
|
}),
|
|
1218
946
|
[
|
|
1219
947
|
props.animations,
|
|
1220
948
|
props.classNames,
|
|
949
|
+
props.className,
|
|
1221
950
|
props.autoFocus,
|
|
1222
951
|
props.trapFocus,
|
|
1223
|
-
props.
|
|
952
|
+
props.onDismiss,
|
|
953
|
+
props.onCheckStatus,
|
|
954
|
+
messages,
|
|
955
|
+
locale,
|
|
956
|
+
theme
|
|
1224
957
|
]
|
|
1225
958
|
);
|
|
959
|
+
}
|
|
960
|
+
function SlotlineProvider(props) {
|
|
961
|
+
const { children } = props;
|
|
962
|
+
const controller = useBookingController(props);
|
|
963
|
+
const ui = useSlotlineUI(props);
|
|
964
|
+
const snapshot = react$1.useSyncExternalStore(
|
|
965
|
+
controller.subscribe,
|
|
966
|
+
controller.getSnapshot,
|
|
967
|
+
controller.getSnapshot
|
|
968
|
+
);
|
|
969
|
+
const flow = react$1.useMemo(() => ({ snapshot, actions: controller.actions }), [snapshot, controller]);
|
|
1226
970
|
return /* @__PURE__ */ jsxRuntime.jsx(FlowContext.Provider, { value: flow, children: /* @__PURE__ */ jsxRuntime.jsx(UIContext.Provider, { value: ui, children }) });
|
|
1227
971
|
}
|
|
1228
972
|
function createSlotline(animations) {
|
|
@@ -1237,9 +981,45 @@ createSlotline(cssAnimations);
|
|
|
1237
981
|
// src/motion.ts
|
|
1238
982
|
var Slotline2 = createSlotline(motionAnimations);
|
|
1239
983
|
|
|
1240
|
-
Object.defineProperty(exports, "
|
|
984
|
+
Object.defineProperty(exports, "BOOKING_ERROR_CODES", {
|
|
985
|
+
enumerable: true,
|
|
986
|
+
get: function () { return slotlineCore.BOOKING_ERROR_CODES; }
|
|
987
|
+
});
|
|
988
|
+
Object.defineProperty(exports, "BookingError", {
|
|
989
|
+
enumerable: true,
|
|
990
|
+
get: function () { return slotlineCore.BookingError; }
|
|
991
|
+
});
|
|
992
|
+
Object.defineProperty(exports, "DEFAULT_BOOKING_CONTEXT", {
|
|
993
|
+
enumerable: true,
|
|
994
|
+
get: function () { return slotlineCore.DEFAULT_BOOKING_CONTEXT; }
|
|
995
|
+
});
|
|
996
|
+
Object.defineProperty(exports, "DEFAULT_BOOKING_FIELDS", {
|
|
997
|
+
enumerable: true,
|
|
998
|
+
get: function () { return slotlineCore.DEFAULT_BOOKING_FIELDS; }
|
|
999
|
+
});
|
|
1000
|
+
Object.defineProperty(exports, "DEFAULT_CONTEXT_KEY", {
|
|
1241
1001
|
enumerable: true,
|
|
1242
|
-
get: function () { return slotlineCore.
|
|
1002
|
+
get: function () { return slotlineCore.DEFAULT_CONTEXT_KEY; }
|
|
1003
|
+
});
|
|
1004
|
+
Object.defineProperty(exports, "DEFAULT_DURATION_OPTIONS", {
|
|
1005
|
+
enumerable: true,
|
|
1006
|
+
get: function () { return slotlineCore.DEFAULT_DURATION_OPTIONS; }
|
|
1007
|
+
});
|
|
1008
|
+
Object.defineProperty(exports, "DEFAULT_TIME_FORMAT", {
|
|
1009
|
+
enumerable: true,
|
|
1010
|
+
get: function () { return slotlineCore.DEFAULT_TIME_FORMAT; }
|
|
1011
|
+
});
|
|
1012
|
+
Object.defineProperty(exports, "FALLBACK_LOCALE", {
|
|
1013
|
+
enumerable: true,
|
|
1014
|
+
get: function () { return slotlineCore.FALLBACK_LOCALE; }
|
|
1015
|
+
});
|
|
1016
|
+
Object.defineProperty(exports, "FALLBACK_TIME_ZONE", {
|
|
1017
|
+
enumerable: true,
|
|
1018
|
+
get: function () { return slotlineCore.FALLBACK_TIME_ZONE; }
|
|
1019
|
+
});
|
|
1020
|
+
Object.defineProperty(exports, "FLOW_STEP_ORDER", {
|
|
1021
|
+
enumerable: true,
|
|
1022
|
+
get: function () { return slotlineCore.FLOW_STEP_ORDER; }
|
|
1243
1023
|
});
|
|
1244
1024
|
Object.defineProperty(exports, "SLOT_CLASS", {
|
|
1245
1025
|
enumerable: true,
|
|
@@ -1253,26 +1033,54 @@ Object.defineProperty(exports, "addMonths", {
|
|
|
1253
1033
|
enumerable: true,
|
|
1254
1034
|
get: function () { return slotlineCore.addMonths; }
|
|
1255
1035
|
});
|
|
1036
|
+
Object.defineProperty(exports, "bookingReducer", {
|
|
1037
|
+
enumerable: true,
|
|
1038
|
+
get: function () { return slotlineCore.bookingReducer; }
|
|
1039
|
+
});
|
|
1256
1040
|
Object.defineProperty(exports, "buildMonthGrid", {
|
|
1257
1041
|
enumerable: true,
|
|
1258
1042
|
get: function () { return slotlineCore.buildMonthGrid; }
|
|
1259
1043
|
});
|
|
1044
|
+
Object.defineProperty(exports, "clampDayKey", {
|
|
1045
|
+
enumerable: true,
|
|
1046
|
+
get: function () { return slotlineCore.clampDayKey; }
|
|
1047
|
+
});
|
|
1260
1048
|
Object.defineProperty(exports, "compareDayKeys", {
|
|
1261
1049
|
enumerable: true,
|
|
1262
1050
|
get: function () { return slotlineCore.compareDayKeys; }
|
|
1263
1051
|
});
|
|
1052
|
+
Object.defineProperty(exports, "contextKeyOf", {
|
|
1053
|
+
enumerable: true,
|
|
1054
|
+
get: function () { return slotlineCore.contextKeyOf; }
|
|
1055
|
+
});
|
|
1264
1056
|
Object.defineProperty(exports, "countSlots", {
|
|
1265
1057
|
enumerable: true,
|
|
1266
1058
|
get: function () { return slotlineCore.countSlots; }
|
|
1267
1059
|
});
|
|
1268
|
-
Object.defineProperty(exports, "
|
|
1060
|
+
Object.defineProperty(exports, "createBookingController", {
|
|
1061
|
+
enumerable: true,
|
|
1062
|
+
get: function () { return slotlineCore.createBookingController; }
|
|
1063
|
+
});
|
|
1064
|
+
Object.defineProperty(exports, "createBookingState", {
|
|
1269
1065
|
enumerable: true,
|
|
1270
|
-
get: function () { return slotlineCore.
|
|
1066
|
+
get: function () { return slotlineCore.createBookingState; }
|
|
1067
|
+
});
|
|
1068
|
+
Object.defineProperty(exports, "createEmptyValues", {
|
|
1069
|
+
enumerable: true,
|
|
1070
|
+
get: function () { return slotlineCore.createEmptyValues; }
|
|
1071
|
+
});
|
|
1072
|
+
Object.defineProperty(exports, "createIdempotencyKey", {
|
|
1073
|
+
enumerable: true,
|
|
1074
|
+
get: function () { return slotlineCore.createIdempotencyKey; }
|
|
1271
1075
|
});
|
|
1272
1076
|
Object.defineProperty(exports, "createMockAdapter", {
|
|
1273
1077
|
enumerable: true,
|
|
1274
1078
|
get: function () { return slotlineCore.createMockAdapter; }
|
|
1275
1079
|
});
|
|
1080
|
+
Object.defineProperty(exports, "createTimeAnchor", {
|
|
1081
|
+
enumerable: true,
|
|
1082
|
+
get: function () { return slotlineCore.createTimeAnchor; }
|
|
1083
|
+
});
|
|
1276
1084
|
Object.defineProperty(exports, "cx", {
|
|
1277
1085
|
enumerable: true,
|
|
1278
1086
|
get: function () { return slotlineCore.cx; }
|
|
@@ -1289,9 +1097,17 @@ Object.defineProperty(exports, "defaultMessages", {
|
|
|
1289
1097
|
enumerable: true,
|
|
1290
1098
|
get: function () { return slotlineCore.defaultMessages; }
|
|
1291
1099
|
});
|
|
1292
|
-
Object.defineProperty(exports, "
|
|
1100
|
+
Object.defineProperty(exports, "deriveBookingView", {
|
|
1293
1101
|
enumerable: true,
|
|
1294
|
-
get: function () { return slotlineCore.
|
|
1102
|
+
get: function () { return slotlineCore.deriveBookingView; }
|
|
1103
|
+
});
|
|
1104
|
+
Object.defineProperty(exports, "detectLocale", {
|
|
1105
|
+
enumerable: true,
|
|
1106
|
+
get: function () { return slotlineCore.detectLocale; }
|
|
1107
|
+
});
|
|
1108
|
+
Object.defineProperty(exports, "endOf", {
|
|
1109
|
+
enumerable: true,
|
|
1110
|
+
get: function () { return slotlineCore.endOf; }
|
|
1295
1111
|
});
|
|
1296
1112
|
Object.defineProperty(exports, "format", {
|
|
1297
1113
|
enumerable: true,
|
|
@@ -1313,35 +1129,121 @@ Object.defineProperty(exports, "formatTimeZoneName", {
|
|
|
1313
1129
|
enumerable: true,
|
|
1314
1130
|
get: function () { return slotlineCore.formatTimeZoneName; }
|
|
1315
1131
|
});
|
|
1132
|
+
Object.defineProperty(exports, "formatUtcOffset", {
|
|
1133
|
+
enumerable: true,
|
|
1134
|
+
get: function () { return slotlineCore.formatUtcOffset; }
|
|
1135
|
+
});
|
|
1136
|
+
Object.defineProperty(exports, "guaranteesNotCreated", {
|
|
1137
|
+
enumerable: true,
|
|
1138
|
+
get: function () { return slotlineCore.guaranteesNotCreated; }
|
|
1139
|
+
});
|
|
1140
|
+
Object.defineProperty(exports, "instantOf", {
|
|
1141
|
+
enumerable: true,
|
|
1142
|
+
get: function () { return slotlineCore.instantOf; }
|
|
1143
|
+
});
|
|
1144
|
+
Object.defineProperty(exports, "instantOfZonedTime", {
|
|
1145
|
+
enumerable: true,
|
|
1146
|
+
get: function () { return slotlineCore.instantOfZonedTime; }
|
|
1147
|
+
});
|
|
1148
|
+
Object.defineProperty(exports, "isAbortError", {
|
|
1149
|
+
enumerable: true,
|
|
1150
|
+
get: function () { return slotlineCore.isAbortError; }
|
|
1151
|
+
});
|
|
1152
|
+
Object.defineProperty(exports, "isBookingError", {
|
|
1153
|
+
enumerable: true,
|
|
1154
|
+
get: function () { return slotlineCore.isBookingError; }
|
|
1155
|
+
});
|
|
1156
|
+
Object.defineProperty(exports, "isBookingErrorCode", {
|
|
1157
|
+
enumerable: true,
|
|
1158
|
+
get: function () { return slotlineCore.isBookingErrorCode; }
|
|
1159
|
+
});
|
|
1316
1160
|
Object.defineProperty(exports, "isDayKey", {
|
|
1317
1161
|
enumerable: true,
|
|
1318
1162
|
get: function () { return slotlineCore.isDayKey; }
|
|
1319
1163
|
});
|
|
1164
|
+
Object.defineProperty(exports, "isRepeatedLocalTime", {
|
|
1165
|
+
enumerable: true,
|
|
1166
|
+
get: function () { return slotlineCore.isRepeatedLocalTime; }
|
|
1167
|
+
});
|
|
1168
|
+
Object.defineProperty(exports, "isRetrySafe", {
|
|
1169
|
+
enumerable: true,
|
|
1170
|
+
get: function () { return slotlineCore.isRetrySafe; }
|
|
1171
|
+
});
|
|
1172
|
+
Object.defineProperty(exports, "midnightAfter", {
|
|
1173
|
+
enumerable: true,
|
|
1174
|
+
get: function () { return slotlineCore.midnightAfter; }
|
|
1175
|
+
});
|
|
1320
1176
|
Object.defineProperty(exports, "monthKeyOf", {
|
|
1321
1177
|
enumerable: true,
|
|
1322
1178
|
get: function () { return slotlineCore.monthKeyOf; }
|
|
1323
1179
|
});
|
|
1180
|
+
Object.defineProperty(exports, "resolveBookingConfig", {
|
|
1181
|
+
enumerable: true,
|
|
1182
|
+
get: function () { return slotlineCore.resolveBookingConfig; }
|
|
1183
|
+
});
|
|
1184
|
+
Object.defineProperty(exports, "resolveBookingFields", {
|
|
1185
|
+
enumerable: true,
|
|
1186
|
+
get: function () { return slotlineCore.resolveBookingFields; }
|
|
1187
|
+
});
|
|
1324
1188
|
Object.defineProperty(exports, "resolveMessages", {
|
|
1325
1189
|
enumerable: true,
|
|
1326
1190
|
get: function () { return slotlineCore.resolveMessages; }
|
|
1327
1191
|
});
|
|
1328
|
-
Object.defineProperty(exports, "
|
|
1192
|
+
Object.defineProperty(exports, "resolveTimeZone", {
|
|
1193
|
+
enumerable: true,
|
|
1194
|
+
get: function () { return slotlineCore.resolveTimeZone; }
|
|
1195
|
+
});
|
|
1196
|
+
Object.defineProperty(exports, "retryWindowClosesAt", {
|
|
1197
|
+
enumerable: true,
|
|
1198
|
+
get: function () { return slotlineCore.retryWindowClosesAt; }
|
|
1199
|
+
});
|
|
1200
|
+
Object.defineProperty(exports, "soonestBoundary", {
|
|
1201
|
+
enumerable: true,
|
|
1202
|
+
get: function () { return slotlineCore.soonestBoundary; }
|
|
1203
|
+
});
|
|
1204
|
+
Object.defineProperty(exports, "systemClock", {
|
|
1205
|
+
enumerable: true,
|
|
1206
|
+
get: function () { return slotlineCore.systemClock; }
|
|
1207
|
+
});
|
|
1208
|
+
Object.defineProperty(exports, "timeAnchorKey", {
|
|
1329
1209
|
enumerable: true,
|
|
1330
|
-
get: function () { return slotlineCore.
|
|
1210
|
+
get: function () { return slotlineCore.timeAnchorKey; }
|
|
1211
|
+
});
|
|
1212
|
+
Object.defineProperty(exports, "toBookingError", {
|
|
1213
|
+
enumerable: true,
|
|
1214
|
+
get: function () { return slotlineCore.toBookingError; }
|
|
1331
1215
|
});
|
|
1332
1216
|
Object.defineProperty(exports, "todayKey", {
|
|
1333
1217
|
enumerable: true,
|
|
1334
1218
|
get: function () { return slotlineCore.todayKey; }
|
|
1335
1219
|
});
|
|
1336
|
-
Object.defineProperty(exports, "
|
|
1220
|
+
Object.defineProperty(exports, "unmappedFieldNames", {
|
|
1221
|
+
enumerable: true,
|
|
1222
|
+
get: function () { return slotlineCore.unmappedFieldNames; }
|
|
1223
|
+
});
|
|
1224
|
+
Object.defineProperty(exports, "userSteps", {
|
|
1225
|
+
enumerable: true,
|
|
1226
|
+
get: function () { return slotlineCore.userSteps; }
|
|
1227
|
+
});
|
|
1228
|
+
Object.defineProperty(exports, "validateBookingValues", {
|
|
1229
|
+
enumerable: true,
|
|
1230
|
+
get: function () { return slotlineCore.validateBookingValues; }
|
|
1231
|
+
});
|
|
1232
|
+
Object.defineProperty(exports, "visibleFieldErrors", {
|
|
1233
|
+
enumerable: true,
|
|
1234
|
+
get: function () { return slotlineCore.visibleFieldErrors; }
|
|
1235
|
+
});
|
|
1236
|
+
Object.defineProperty(exports, "wallKeyOf", {
|
|
1237
|
+
enumerable: true,
|
|
1238
|
+
get: function () { return slotlineCore.wallKeyOf; }
|
|
1239
|
+
});
|
|
1240
|
+
Object.defineProperty(exports, "zoneOffsetMs", {
|
|
1337
1241
|
enumerable: true,
|
|
1338
|
-
get: function () { return slotlineCore.
|
|
1242
|
+
get: function () { return slotlineCore.zoneOffsetMs; }
|
|
1339
1243
|
});
|
|
1340
|
-
exports.FlowContext = FlowContext;
|
|
1341
1244
|
exports.Slotline = Slotline2;
|
|
1342
1245
|
exports.SlotlineProvider = SlotlineProvider;
|
|
1343
1246
|
exports.SlotlineView = SlotlineView;
|
|
1344
|
-
exports.UIContext = UIContext;
|
|
1345
1247
|
exports.createSlotline = createSlotline;
|
|
1346
1248
|
exports.cssAnimations = cssAnimations;
|
|
1347
1249
|
exports.motionAnimations = motionAnimations;
|