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