@nickkupriyanov/slotline-react 0.1.0 → 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/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("(prefers-reduced-motion: reduce)");
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 heightRef = react$1.useRef(void 0);
40
- const timerRef = react$1.useRef(null);
41
- const [height, setHeight] = react$1.useState();
42
- const [animating, setAnimating] = react$1.useState(false);
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 || typeof ResizeObserver === "undefined") {
63
- window.addEventListener("resize", measure);
64
- return () => window.removeEventListener("resize", measure);
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
- const onKeyDown = (event) => {
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
- state,
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 today = slotlineCore.todayKey(timeZone);
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
- () => monthGrid.find((day) => slotlineCore.monthKeyOf(day) === state.month && isDayBookable(day)),
380
- [monthGrid, state.month, isDayBookable]
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 && monthGrid.includes(focusKey)) return focusKey;
384
- if (state.selectedDate && monthGrid.includes(state.selectedDate)) return state.selectedDate;
385
- return firstBookable ?? monthGrid[0] ?? `${state.month}-01`;
386
- }, [focusKey, monthGrid, state.selectedDate, firstBookable, state.month]);
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, monthGrid]);
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 = monthGrid.find(
398
- (candidate) => slotlineCore.monthKeyOf(candidate) === state.month && candidate.slice(8, 10) === dayOfMonth
326
+ const sameDay = month.grid.find(
327
+ (candidate) => slotlineCore.monthKeyOf(candidate) === month.key && candidate.slice(8, 10) === dayOfMonth
399
328
  );
400
- const target = sameDay ?? monthGrid.find((candidate) => slotlineCore.monthKeyOf(candidate) === state.month);
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
- }, [state.month, monthGrid]);
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 = monthGrid[monthGrid.length - 1];
449
- const first = monthGrid[0];
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, monthGrid, moveFocus, weekStartsOn]
384
+ [actions, month.grid, month.key, month.weekStartsOn]
456
385
  );
457
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("calendar"), "data-status": state.daysStatus, children: [
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: monthLabel }),
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
- state.daysStatus === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
488
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.daysError }),
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
- ] }) : null,
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": state.daysStatus === "loading",
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 outside = slotlineCore.monthKeyOf(day) !== state.month;
502
- const bookable = isDayBookable(day);
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": selected || void 0,
439
+ "aria-selected": cell?.isSelected || void 0,
517
440
  "aria-disabled": !bookable || void 0,
518
- "aria-current": day === today ? "date" : void 0,
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
- outside && "is-outside",
523
- selected && "is-selected",
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
- state.month
470
+ month.key
548
471
  ),
549
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-timezone", children: slotlineCore.format(messages.timeZoneHint, { zone: timeZoneName ?? timeZone, timeZone }) })
472
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-timezone", children: view.time.zoneHint })
550
473
  ] });
551
474
  }
552
475
  function ConfirmedStep() {
553
- const { state, messages, selectedDateLabel, selectedTimeLabel, durationLabel, actions } = useFlow();
476
+ const { snapshot, actions } = useFlow();
554
477
  const cls = useClassNames();
555
- const inviteeEmail = (typeof state.result?.inviteeEmail === "string" ? state.result.inviteeEmail : void 0) ?? state.details.email;
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: messages.bookedTitle }),
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: slotlineCore.format(messages.bookedSummary, {
561
- date: selectedDateLabel,
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: messages.bookAnother })
487
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("secondaryButton"), onClick: actions.reset, children: confirmation.bookAnotherLabel })
568
488
  ] });
569
489
  }
570
- function Field({
571
- name,
572
- label,
573
- value,
574
- type = "text",
575
- error,
576
- disabled,
577
- autoFocus,
578
- onChange,
579
- onBlur
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 errorId = react$1.useId();
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
- id: name,
588
- name,
589
- type,
590
- inputMode: type === "email" ? "email" : void 0,
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
- "aria-label": label,
594
- "aria-invalid": error ? true : void 0,
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
- state,
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 submitting = state.submitStatus === "submitting";
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: selectedDateLabel }),
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
- selectedTimeLabel,
588
+ view.time.label,
629
589
  " \xB7 ",
630
- durationLabel
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
- name: "email",
662
- label: messages.emailLabel,
663
- type: "email",
664
- value: state.details.email,
665
- error: shownFieldErrors.email,
666
- disabled: submitting,
667
- onChange: (value) => actions.setDetails({ email: value }),
668
- onBlur: () => actions.touchField("email")
669
- }
670
- ),
671
- state.submitError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
672
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.submitError }),
673
- /* @__PURE__ */ jsxRuntime.jsx(
674
- "button",
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 || !detailsValid,
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: submitting ? messages.confirming : messages.confirm }),
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 { state, messages, durations, actions } = useFlow();
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: durations.map((minutes) => {
707
- const active = minutes === state.duration;
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 { state, messages } = useFlow();
670
+ const { snapshot } = useFlow();
728
671
  const cls = useClassNames();
729
- const index = slotlineCore.stepIndex(state.step);
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": slotlineCore.FLOW_STEPS.length,
737
- "aria-valuenow": index + 1,
738
- "aria-valuetext": slotlineCore.format(messages.progressLabel, {
739
- step: index + 1,
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 <= index ? "done" : "todo",
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 { state, messages, locale, timeZone, selectedDateLabel, actions } = useFlow();
696
+ const { snapshot, actions } = useFlow();
697
+ const { messages } = useUI();
757
698
  const cls = useClassNames();
758
- const { Item: Item3 } = useAnimations();
759
- const loading = state.slotsStatus === "loading" || state.slotsStatus === "idle";
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: selectedDateLabel }),
764
- state.slotsStatus === "ready" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cls("slotsCount"), children: slotlineCore.format(messages.slotsCount, { count: state.slots.length }) }) : null
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
- state.slotsStatus === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
775
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.slotsError }),
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
- state.slotsStatus === "ready" && state.slots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("empty"), children: [
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
- state.slotsStatus === "ready" && state.slots.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls("slots"), role: "group", "aria-label": messages.slotsLabel, children: state.slots.map((slot, index) => {
784
- const selected = state.selectedSlot?.start === slot.start;
785
- return /* @__PURE__ */ jsxRuntime.jsx(
786
- Item3,
787
- {
788
- index,
789
- className: cls("slot", selected && "is-selected"),
790
- "aria-current": selected ? "true" : void 0,
791
- onClick: () => actions.selectSlot(slot),
792
- children: slotlineCore.formatTime(slot.start, timeZone, locale)
793
- },
794
- slot.start
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 { state } = useFlow();
801
- switch (state.step) {
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 flow = useFlow();
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
- }, [state.step, ui.autoFocus]);
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": flow.theme,
849
- "data-sl-step": state.step,
850
- lang: flow.locale,
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: durationLabel })
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: state.step, direction: state.direction, children: /* @__PURE__ */ jsxRuntime.jsx(StepBody, {}) }) }) }),
871
- state.step === "date" ? /* @__PURE__ */ jsxRuntime.jsx(DurationFooter, {}) : null,
872
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-visually-hidden", role: "status", "aria-live": "polite", children: announcement })
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 useControllableState({
878
- value,
879
- defaultValue,
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 [systemTheme, setSystemTheme] = react$1.useState(() => {
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
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
904
- const list = window.matchMedia("(prefers-color-scheme: dark)");
905
- const handler = (event) => setSystemTheme(event.matches ? "dark" : "light");
906
- list.addEventListener("change", handler);
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
- if (theme === "system") return systemTheme;
910
- return theme;
824
+ return theme === "system" ? resolved : theme;
911
825
  }
912
826
 
913
827
  // src/useBookingFlow.ts
914
- function toError(value) {
915
- return value instanceof Error ? value : new Error(String(value));
916
- }
917
- function messageOf(value, fallback) {
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 useBookingFlow(props) {
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
- const date = state.selectedDate;
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
- const back = react$1.useCallback(() => {
1072
- const current = stateRef.current;
1073
- if (current.submitStatus === "submitting") return;
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
- state,
1165
- messages,
1166
- locale,
1167
- timeZone,
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 SlotlineProvider(props) {
1208
- const { children } = props;
1209
- const flow = useBookingFlow(props);
1210
- const ui = react$1.useMemo(
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
- className: props.className
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.className
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, "FLOW_STEPS", {
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.FLOW_STEPS; }
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, "createFlowState", {
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.createFlowState; }
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, "flowReducer", {
1100
+ Object.defineProperty(exports, "deriveBookingView", {
1293
1101
  enumerable: true,
1294
- get: function () { return slotlineCore.flowReducer; }
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, "stepIndex", {
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.stepIndex; }
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, "validateDetails", {
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.validateDetails; }
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;