@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/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("(prefers-reduced-motion: reduce)");
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 heightRef = react.useRef(void 0);
39
- const timerRef = react.useRef(null);
40
- const [height, setHeight] = react.useState();
41
- const [animating, setAnimating] = react.useState(false);
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 || typeof ResizeObserver === "undefined") {
62
- window.addEventListener("resize", measure);
63
- return () => window.removeEventListener("resize", measure);
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
- const onKeyDown = (event) => {
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
- state,
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 today = slotlineCore.todayKey(timeZone);
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
- () => monthGrid.find((day) => slotlineCore.monthKeyOf(day) === state.month && isDayBookable(day)),
338
- [monthGrid, state.month, isDayBookable]
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 && monthGrid.includes(focusKey)) return focusKey;
342
- if (state.selectedDate && monthGrid.includes(state.selectedDate)) return state.selectedDate;
343
- return firstBookable ?? monthGrid[0] ?? `${state.month}-01`;
344
- }, [focusKey, monthGrid, state.selectedDate, firstBookable, state.month]);
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, monthGrid]);
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 = monthGrid.find(
356
- (candidate) => slotlineCore.monthKeyOf(candidate) === state.month && candidate.slice(8, 10) === dayOfMonth
284
+ const sameDay = month.grid.find(
285
+ (candidate) => slotlineCore.monthKeyOf(candidate) === month.key && candidate.slice(8, 10) === dayOfMonth
357
286
  );
358
- const target = sameDay ?? monthGrid.find((candidate) => slotlineCore.monthKeyOf(candidate) === state.month);
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
- }, [state.month, monthGrid]);
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 = monthGrid[monthGrid.length - 1];
407
- const first = monthGrid[0];
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, monthGrid, moveFocus, weekStartsOn]
342
+ [actions, month.grid, month.key, month.weekStartsOn]
414
343
  );
415
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("calendar"), "data-status": state.daysStatus, children: [
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: monthLabel }),
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
- state.daysStatus === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
446
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.daysError }),
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
- ] }) : null,
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": state.daysStatus === "loading",
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 outside = slotlineCore.monthKeyOf(day) !== state.month;
460
- const bookable = isDayBookable(day);
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": selected || void 0,
397
+ "aria-selected": cell?.isSelected || void 0,
475
398
  "aria-disabled": !bookable || void 0,
476
- "aria-current": day === today ? "date" : void 0,
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
- outside && "is-outside",
481
- selected && "is-selected",
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
- state.month
428
+ month.key
506
429
  ),
507
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-timezone", children: slotlineCore.format(messages.timeZoneHint, { zone: timeZoneName ?? timeZone, timeZone }) })
430
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sl-timezone", children: view.time.zoneHint })
508
431
  ] });
509
432
  }
510
433
  function ConfirmedStep() {
511
- const { state, messages, selectedDateLabel, selectedTimeLabel, durationLabel, actions } = useFlow();
434
+ const { snapshot, actions } = useFlow();
512
435
  const cls = useClassNames();
513
- const inviteeEmail = (typeof state.result?.inviteeEmail === "string" ? state.result.inviteeEmail : void 0) ?? state.details.email;
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: messages.bookedTitle }),
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: slotlineCore.format(messages.bookedSummary, {
519
- date: selectedDateLabel,
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: messages.bookAnother })
445
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cls("secondaryButton"), onClick: actions.reset, children: confirmation.bookAnotherLabel })
526
446
  ] });
527
447
  }
528
- function Field({
529
- name,
530
- label,
531
- value,
532
- type = "text",
533
- error,
534
- disabled,
535
- autoFocus,
536
- onChange,
537
- onBlur
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 errorId = react.useId();
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
- id: name,
546
- name,
547
- type,
548
- inputMode: type === "email" ? "email" : void 0,
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
- "aria-label": label,
552
- "aria-invalid": error ? true : void 0,
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
- state,
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 submitting = state.submitStatus === "submitting";
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: selectedDateLabel }),
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
- selectedTimeLabel,
546
+ view.time.label,
587
547
  " \xB7 ",
588
- durationLabel
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
- name: "email",
620
- label: messages.emailLabel,
621
- type: "email",
622
- value: state.details.email,
623
- error: shownFieldErrors.email,
624
- disabled: submitting,
625
- onChange: (value) => actions.setDetails({ email: value }),
626
- onBlur: () => actions.touchField("email")
627
- }
628
- ),
629
- state.submitError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
630
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.submitError }),
631
- /* @__PURE__ */ jsxRuntime.jsx(
632
- "button",
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 || !detailsValid,
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: submitting ? messages.confirming : messages.confirm }),
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 { state, messages, durations, actions } = useFlow();
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: durations.map((minutes) => {
665
- const active = minutes === state.duration;
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 { state, messages } = useFlow();
628
+ const { snapshot } = useFlow();
686
629
  const cls = useClassNames();
687
- const index = slotlineCore.stepIndex(state.step);
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": slotlineCore.FLOW_STEPS.length,
695
- "aria-valuenow": index + 1,
696
- "aria-valuetext": slotlineCore.format(messages.progressLabel, {
697
- step: index + 1,
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 <= index ? "done" : "todo",
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 { state, messages, locale, timeZone, selectedDateLabel, actions } = useFlow();
654
+ const { snapshot, actions } = useFlow();
655
+ const { messages } = useUI();
715
656
  const cls = useClassNames();
716
- const { Item: Item2 } = useAnimations();
717
- const loading = state.slotsStatus === "loading" || state.slotsStatus === "idle";
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: selectedDateLabel }),
722
- state.slotsStatus === "ready" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cls("slotsCount"), children: slotlineCore.format(messages.slotsCount, { count: state.slots.length }) }) : null
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
- state.slotsStatus === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("error"), role: "alert", children: [
733
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.slotsError }),
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
- state.slotsStatus === "ready" && state.slots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls("empty"), children: [
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
- 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) => {
742
- const selected = state.selectedSlot?.start === slot.start;
743
- return /* @__PURE__ */ jsxRuntime.jsx(
744
- Item2,
745
- {
746
- index,
747
- className: cls("slot", selected && "is-selected"),
748
- "aria-current": selected ? "true" : void 0,
749
- onClick: () => actions.selectSlot(slot),
750
- children: slotlineCore.formatTime(slot.start, timeZone, locale)
751
- },
752
- slot.start
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 { state } = useFlow();
759
- switch (state.step) {
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 flow = useFlow();
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
- }, [state.step, ui.autoFocus]);
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": flow.theme,
807
- "data-sl-step": state.step,
808
- lang: flow.locale,
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: durationLabel })
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: state.step, direction: state.direction, children: /* @__PURE__ */ jsxRuntime.jsx(StepBody, {}) }) }) }),
829
- state.step === "date" ? /* @__PURE__ */ jsxRuntime.jsx(DurationFooter, {}) : null,
830
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sl-visually-hidden", role: "status", "aria-live": "polite", children: announcement })
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 useControllableState({
836
- value,
837
- defaultValue,
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 [systemTheme, setSystemTheme] = react.useState(() => {
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
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
862
- const list = window.matchMedia("(prefers-color-scheme: dark)");
863
- const handler = (event) => setSystemTheme(event.matches ? "dark" : "light");
864
- list.addEventListener("change", handler);
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
- if (theme === "system") return systemTheme;
868
- return theme;
782
+ return theme === "system" ? resolved : theme;
869
783
  }
870
784
 
871
785
  // src/useBookingFlow.ts
872
- function toError(value) {
873
- return value instanceof Error ? value : new Error(String(value));
874
- }
875
- function messageOf(value, fallback) {
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 useBookingFlow(props) {
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
- const date = state.selectedDate;
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
- const back = react.useCallback(() => {
1030
- const current = stateRef.current;
1031
- if (current.submitStatus === "submitting") return;
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
- state,
1123
- messages,
1124
- locale,
1125
- timeZone,
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 SlotlineProvider(props) {
1166
- const { children } = props;
1167
- const flow = useBookingFlow(props);
1168
- const ui = react.useMemo(
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
- className: props.className
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.className
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, "FLOW_STEPS", {
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.FLOW_STEPS; }
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, "createFlowState", {
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.createFlowState; }
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, "flowReducer", {
1055
+ Object.defineProperty(exports, "deriveBookingView", {
1248
1056
  enumerable: true,
1249
- get: function () { return slotlineCore.flowReducer; }
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, "stepIndex", {
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.stepIndex; }
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, "validateDetails", {
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.validateDetails; }
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;