@joewinke/jatui 0.41.1 → 0.43.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.
Files changed (75) hide show
  1. package/CLAUDE.md +32 -6
  2. package/THIRD_PARTY_NOTICES.md +14 -0
  3. package/dist/actions/dismissOutside.d.ts +52 -0
  4. package/dist/actions/dismissOutside.js +80 -0
  5. package/dist/components/BookingScheduler.svelte +412 -0
  6. package/dist/components/BookingScheduler.svelte.d.ts +66 -0
  7. package/dist/components/CalendarPicker.svelte +190 -205
  8. package/dist/components/CalendarPicker.svelte.d.ts +19 -1
  9. package/dist/components/DatePicker.svelte +419 -0
  10. package/dist/components/DatePicker.svelte.d.ts +44 -0
  11. package/dist/components/DateRangePicker.svelte +471 -5
  12. package/dist/components/DateRangePicker.svelte.d.ts +32 -3
  13. package/dist/components/EventCalendar.svelte +1324 -0
  14. package/dist/components/EventCalendar.svelte.d.ts +101 -0
  15. package/dist/components/FileDropzone.svelte +165 -3
  16. package/dist/components/FileDropzone.svelte.d.ts +9 -0
  17. package/dist/components/TextInput.svelte +71 -20
  18. package/dist/components/TextInput.svelte.d.ts +7 -0
  19. package/dist/components/ThumbnailGrid.svelte +106 -1
  20. package/dist/components/ThumbnailGrid.svelte.d.ts +7 -0
  21. package/dist/components/TimeSlotPicker.svelte +174 -108
  22. package/dist/components/TimeSlotPicker.svelte.d.ts +27 -0
  23. package/dist/components/basic/Alert.svelte +113 -16
  24. package/dist/components/basic/Alert.svelte.d.ts +17 -1
  25. package/dist/components/basic/Checkbox.svelte +53 -1
  26. package/dist/components/basic/Checkbox.svelte.d.ts +6 -0
  27. package/dist/components/basic/RadioGroup.svelte +59 -3
  28. package/dist/components/basic/RadioGroup.svelte.d.ts +15 -0
  29. package/dist/components/basic/Slider.svelte +257 -9
  30. package/dist/components/basic/Slider.svelte.d.ts +14 -2
  31. package/dist/components/basic/sliderMath.d.ts +29 -0
  32. package/dist/components/basic/sliderMath.js +64 -0
  33. package/dist/components/calendar/CalendarHeader.svelte +98 -0
  34. package/dist/components/calendar/CalendarHeader.svelte.d.ts +24 -0
  35. package/dist/components/calendar/MonthGrid.svelte +361 -0
  36. package/dist/components/calendar/MonthGrid.svelte.d.ts +55 -0
  37. package/dist/components/calendar/calendarGrid.d.ts +219 -0
  38. package/dist/components/calendar/calendarGrid.js +494 -0
  39. package/dist/components/calendar/createCalendarGrid.d.ts +25 -0
  40. package/dist/components/calendar/createCalendarGrid.js +57 -0
  41. package/dist/components/calendar/dayOverlap.d.ts +92 -0
  42. package/dist/components/calendar/dayOverlap.js +147 -0
  43. package/dist/components/calendar/eventCalendar.d.ts +75 -0
  44. package/dist/components/calendar/eventCalendar.js +187 -0
  45. package/dist/components/calendar/index.d.ts +32 -0
  46. package/dist/components/calendar/index.js +25 -0
  47. package/dist/components/calendar/monthPacking.d.ts +97 -0
  48. package/dist/components/calendar/monthPacking.js +254 -0
  49. package/dist/components/calendar/timeSlots.d.ts +57 -0
  50. package/dist/components/calendar/timeSlots.js +163 -0
  51. package/dist/components/field/FieldHint.svelte +29 -0
  52. package/dist/components/field/FieldHint.svelte.d.ts +17 -0
  53. package/dist/components/field/FieldLabel.svelte +100 -0
  54. package/dist/components/field/FieldLabel.svelte.d.ts +29 -0
  55. package/dist/components/form-cards/CheckGlyph.svelte +138 -0
  56. package/dist/components/form-cards/CheckGlyph.svelte.d.ts +29 -0
  57. package/dist/components/form-cards/CheckboxCard.svelte +120 -0
  58. package/dist/components/form-cards/CheckboxCard.svelte.d.ts +33 -0
  59. package/dist/components/form-cards/ChoiceCard.svelte +156 -0
  60. package/dist/components/form-cards/ChoiceCard.svelte.d.ts +42 -0
  61. package/dist/components/form-cards/SwitchCard.svelte +114 -0
  62. package/dist/components/form-cards/SwitchCard.svelte.d.ts +28 -0
  63. package/dist/components/toast/ToastHost.svelte +56 -3
  64. package/dist/components/toast/ToastHost.svelte.d.ts +9 -1
  65. package/dist/components/toast/ToastItem.svelte +75 -23
  66. package/dist/components/toast/ToastItem.svelte.d.ts +2 -0
  67. package/dist/components/uploadProgress.d.ts +30 -0
  68. package/dist/components/uploadProgress.js +47 -0
  69. package/dist/index.d.ts +29 -2
  70. package/dist/index.js +22 -1
  71. package/dist/stores/toast.d.ts +35 -0
  72. package/dist/stores/toast.js +15 -1
  73. package/dist/styles/motion.css +72 -0
  74. package/dist/styles/rail.css +51 -0
  75. package/package.json +6 -1
@@ -1,21 +1,74 @@
1
+ <script module lang="ts">
2
+ export type ToastPosition =
3
+ | 'bottom-end'
4
+ | 'bottom-start'
5
+ | 'bottom-center'
6
+ | 'top-end'
7
+ | 'top-start'
8
+ | 'top-center';
9
+ </script>
10
+
1
11
  <script lang="ts">
2
12
  import { getToasts } from '../../stores/toast.js';
3
13
  import ToastItem from './ToastItem.svelte';
4
14
 
15
+ interface Props {
16
+ /**
17
+ * Viewport corner/edge. Logical: `end` is the right edge in LTR and the
18
+ * left edge in RTL. Default 'bottom-end' (the original bottom-right).
19
+ */
20
+ position?: ToastPosition;
21
+ }
22
+
23
+ let { position = 'bottom-end' }: Props = $props();
24
+
5
25
  const toasts = $derived(getToasts());
26
+ const edge = $derived<'top' | 'bottom'>(position.startsWith('top') ? 'top' : 'bottom');
6
27
  </script>
7
28
 
8
29
  <!--
9
30
  Mount once in your app root (e.g. +layout.svelte).
10
- Fixed bottom-right, pointer-events only on the toast list.
31
+ Fixed to a viewport corner, pointer-events only on the toast list.
32
+ Newest toast sits on top of the stack; at top positions that puts it
33
+ nearest the edge, so the stack reads newest-first downward.
11
34
  -->
12
35
  <div
13
36
  aria-label="Notifications"
14
- class="toast-host pointer-events-none fixed right-4 bottom-4 z-[9999] flex flex-col-reverse items-end gap-2 w-80 max-w-[calc(100vw-2rem)]"
37
+ class="toast-host toast-host--{position} pointer-events-none fixed z-[9999] flex flex-col-reverse gap-2 w-80 max-w-[calc(100vw-2rem)]"
38
+ data-position={position}
15
39
  >
16
40
  <ul class="contents">
17
41
  {#each toasts as toast, i (toast.id)}
18
- <ToastItem {toast} index={i} total={toasts.length} />
42
+ <ToastItem {toast} index={i} total={toasts.length} {edge} />
19
43
  {/each}
20
44
  </ul>
21
45
  </div>
46
+
47
+ <style>
48
+ .toast-host--bottom-end,
49
+ .toast-host--bottom-start,
50
+ .toast-host--bottom-center {
51
+ bottom: 1rem;
52
+ }
53
+ .toast-host--top-end,
54
+ .toast-host--top-start,
55
+ .toast-host--top-center {
56
+ top: 1rem;
57
+ }
58
+ .toast-host--bottom-end,
59
+ .toast-host--top-end {
60
+ inset-inline-end: 1rem;
61
+ align-items: flex-end;
62
+ }
63
+ .toast-host--bottom-start,
64
+ .toast-host--top-start {
65
+ inset-inline-start: 1rem;
66
+ align-items: flex-start;
67
+ }
68
+ .toast-host--bottom-center,
69
+ .toast-host--top-center {
70
+ inset-inline: 0;
71
+ margin-inline: auto;
72
+ align-items: center;
73
+ }
74
+ </style>
@@ -1,3 +1,11 @@
1
- declare const ToastHost: import("svelte").Component<Record<string, never>, {}, "">;
1
+ export type ToastPosition = 'bottom-end' | 'bottom-start' | 'bottom-center' | 'top-end' | 'top-start' | 'top-center';
2
+ interface Props {
3
+ /**
4
+ * Viewport corner/edge. Logical: `end` is the right edge in LTR and the
5
+ * left edge in RTL. Default 'bottom-end' (the original bottom-right).
6
+ */
7
+ position?: ToastPosition;
8
+ }
9
+ declare const ToastHost: import("svelte").Component<Props, {}, "">;
2
10
  type ToastHost = ReturnType<typeof ToastHost>;
3
11
  export default ToastHost;
@@ -1,13 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { dismissToast, type Toast } from '../../stores/toast.js';
3
+ import CloseButton from '../CloseButton.svelte';
3
4
 
4
5
  type Props = {
5
6
  toast: Toast;
6
7
  index: number;
7
8
  total: number;
9
+ /** Which viewport edge the host is pinned to — sets the entrance direction. */
10
+ edge?: 'top' | 'bottom';
8
11
  };
9
12
 
10
- let { toast, index, total }: Props = $props();
13
+ let { toast, index, total, edge = 'bottom' }: Props = $props();
14
+ const enterSign = $derived(edge === 'top' ? -1 : 1);
11
15
 
12
16
  // Stack compression: older toasts (lower index) scale down and fade behind newer ones
13
17
  // index 0 = oldest (back), index total-1 = newest (front)
@@ -172,10 +176,10 @@ const detailsId = $derived(`toast-details-${toast.id}`);
172
176
  isDragging
173
177
  ? `translateX(${dragX}px) rotate(${dragX * 0.02}deg)`
174
178
  : exiting
175
- ? `translateY(20px) scale(0.9)`
179
+ ? `translateY(${20 * enterSign}px) scale(0.9)`
176
180
  : mounted
177
181
  ? `translateX(0) rotate(0deg)`
178
- : `translateY(40px) scale(0.92)`
182
+ : `translateY(${40 * enterSign}px) scale(0.92)`
179
183
  );
180
184
 
181
185
  const itemOpacity = $derived(
@@ -192,13 +196,25 @@ const detailsId = $derived(`toast-details-${toast.id}`);
192
196
  isDragging
193
197
  ? 'none'
194
198
  : exiting
195
- ? 'transform var(--motion-enter) var(--motion-glide), opacity var(--motion-enter) var(--motion-glide)'
196
- : 'transform var(--motion-enter) var(--motion-spring), opacity var(--motion-fast) var(--motion-glide)'
199
+ ? 'transform var(--motion-enter) var(--motion-glide), opacity var(--motion-enter) var(--motion-glide), filter var(--motion-enter) var(--motion-glide)'
200
+ : 'transform var(--motion-enter) var(--motion-spring), opacity var(--motion-fast) var(--motion-glide), filter var(--motion-enter) var(--motion-glide)'
201
+ );
202
+
203
+ // Blur on enter/exit (BoardUI notification idea). The class only marks the
204
+ // hidden state; CSS drops the blur under data-motion="calm" and reduced motion.
205
+ const veiled = $derived(!isDragging && (exiting || !mounted));
206
+
207
+ const titleId = $derived(`toast-title-${toast.id}`);
208
+ // Legacy toasts from an older store shape may lack the normalised list.
209
+ const actionList = $derived(
210
+ toast.actions ?? (toast.action ? [{ ...toast.action, variant: 'ghost' as const }] : [])
197
211
  );
198
212
  </script>
199
213
 
200
214
  <li
201
215
  class="toast-item pointer-events-auto w-full"
216
+ class:toast-veiled={veiled}
217
+ aria-labelledby={toast.title ? titleId : undefined}
202
218
  style:transform={itemTransform}
203
219
  style:opacity={itemOpacity}
204
220
  style:transition={dragTransition}
@@ -219,6 +235,27 @@ const detailsId = $derived(`toast-details-${toast.id}`);
219
235
  style:transition="transform var(--motion-move) var(--motion-glide), opacity var(--motion-move) var(--motion-glide)"
220
236
  >
221
237
  <!-- Undo countdown bar -->
238
+ {#if toast.showProgress && !toast.onUndo}
239
+ <span
240
+ class="toast-progress absolute bottom-0 left-0 h-0.5 w-full origin-left bg-current opacity-40"
241
+ style:animation-duration="{toast.durationMs}ms"
242
+ style:animation-play-state={paused ? 'paused' : 'running'}
243
+ aria-hidden="true"
244
+ ></span>
245
+ {/if}
246
+
247
+ {#if toast.avatar}
248
+ <img
249
+ src={toast.avatar.src}
250
+ alt={toast.avatar.alt}
251
+ class="toast-avatar size-8 shrink-0 self-start rounded-full object-cover"
252
+ />
253
+ {:else if toast.icon}
254
+ <span class="toast-icon flex size-5 shrink-0 items-center justify-center self-start" aria-hidden="true">
255
+ {#if typeof toast.icon === 'string'}{toast.icon}{:else}{@render toast.icon()}{/if}
256
+ </span>
257
+ {/if}
258
+
222
259
  {#if toast.onUndo && !undoDone}
223
260
  <span
224
261
  class="toast-progress absolute bottom-0 left-0 h-0.5 w-full origin-left bg-current opacity-40"
@@ -228,7 +265,10 @@ const detailsId = $derived(`toast-details-${toast.id}`);
228
265
  {/if}
229
266
 
230
267
  <div class="flex-1 min-w-0">
231
- <span class="text-sm">{toast.message}</span>
268
+ {#if toast.title}
269
+ <p id={titleId} class="toast-title text-sm font-semibold">{toast.title}</p>
270
+ {/if}
271
+ <span class="text-sm" class:opacity-80={!!toast.title}>{toast.message}</span>
232
272
 
233
273
  {#if toast.details}
234
274
  <button
@@ -270,26 +310,26 @@ const detailsId = $derived(`toast-details-${toast.id}`);
270
310
  >
271
311
  Undo
272
312
  </button>
273
- {:else if toast.action}
274
- <button
275
- type="button"
276
- class="btn btn-ghost btn-xs font-semibold"
277
- onclick={() => { toast.action!.onClick(); triggerDismiss(); }}
278
- >
279
- {toast.action.label}
280
- </button>
313
+ {:else if actionList.length > 0}
314
+ <div class="toast-actions flex shrink-0 flex-wrap items-center gap-1">
315
+ {#each actionList as act, ai (ai)}
316
+ <button
317
+ type="button"
318
+ class="btn btn-xs font-semibold {act.variant === 'primary' ? 'btn-primary' : 'btn-ghost'}"
319
+ onclick={() => { act.onClick(); triggerDismiss(); }}
320
+ >
321
+ {act.label}
322
+ </button>
323
+ {/each}
324
+ </div>
281
325
  {/if}
282
326
 
283
- <button
284
- type="button"
285
- class="btn btn-ghost btn-xs btn-square"
286
- aria-label="Dismiss notification"
327
+ <CloseButton
328
+ label="Dismiss notification"
329
+ size="xs"
330
+ shape="square"
287
331
  onclick={triggerDismiss}
288
- >
289
- <svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
290
- <path d="M18 6L6 18M6 6l12 12"/>
291
- </svg>
292
- </button>
332
+ />
293
333
  </div>
294
334
  </li>
295
335
 
@@ -300,6 +340,14 @@ const detailsId = $derived(`toast-details-${toast.id}`);
300
340
  user-select: none;
301
341
  }
302
342
 
343
+ .toast-item.toast-veiled {
344
+ filter: blur(4px);
345
+ }
346
+
347
+ :global([data-motion='calm']) .toast-item.toast-veiled {
348
+ filter: none;
349
+ }
350
+
303
351
  .toast-progress {
304
352
  animation-name: drain;
305
353
  animation-timing-function: linear;
@@ -328,5 +376,9 @@ const detailsId = $derived(`toast-details-${toast.id}`);
328
376
  .toast-item > div {
329
377
  transition: none !important;
330
378
  }
379
+
380
+ .toast-item.toast-veiled {
381
+ filter: none;
382
+ }
331
383
  }
332
384
  </style>
@@ -3,6 +3,8 @@ type Props = {
3
3
  toast: Toast;
4
4
  index: number;
5
5
  total: number;
6
+ /** Which viewport edge the host is pinned to — sets the entrance direction. */
7
+ edge?: 'top' | 'bottom';
6
8
  };
7
9
  declare const ToastItem: import("svelte").Component<Props, {}, "">;
8
10
  type ToastItem = ReturnType<typeof ToastItem>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pure math behind the upload-progress strokes on FileDropzone (perimeter
3
+ * border) and ThumbnailGrid (per-tile ring). DOM-free so it is unit-testable
4
+ * under node:test (tests/unit/uploadProgress.test.ts).
5
+ */
6
+ /** Clamp a 0–100 progress value; `null`/`undefined`/NaN mean "no progress shown". */
7
+ export declare function clampProgress(value: number | null | undefined): number | null;
8
+ /** True while an upload is in flight (a progress value below 100). */
9
+ export declare function isInFlight(value: number | null | undefined): boolean;
10
+ /** Whole-number percent for the badge / aria-valuenow. Floors so 99.6 never reads "100". */
11
+ export declare function progressPercent(value: number | null | undefined): number;
12
+ /**
13
+ * stroke-dashoffset for a path drawn with `pathLength={length}` and
14
+ * `stroke-dasharray={length}`: 0 = fully drawn, `length` = empty.
15
+ */
16
+ export declare function dashOffset(value: number | null | undefined, length?: number): number;
17
+ export interface PerimeterRect {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ rx: number;
23
+ }
24
+ /**
25
+ * Geometry for an SVG `<rect>` whose stroke sits exactly on a CSS border of
26
+ * width `stroke` around a `width`×`height` border-box with `radius` corners.
27
+ * The stroke is centred on the path, so the rect is inset by half a stroke and
28
+ * the corner radius shrinks by the same amount (never below 0).
29
+ */
30
+ export declare function perimeterRect(width: number, height: number, stroke?: number, radius?: number): PerimeterRect;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pure math behind the upload-progress strokes on FileDropzone (perimeter
3
+ * border) and ThumbnailGrid (per-tile ring). DOM-free so it is unit-testable
4
+ * under node:test (tests/unit/uploadProgress.test.ts).
5
+ */
6
+ /** Clamp a 0–100 progress value; `null`/`undefined`/NaN mean "no progress shown". */
7
+ export function clampProgress(value) {
8
+ if (value == null || Number.isNaN(value))
9
+ return null;
10
+ return Math.min(100, Math.max(0, value));
11
+ }
12
+ /** True while an upload is in flight (a progress value below 100). */
13
+ export function isInFlight(value) {
14
+ const p = clampProgress(value);
15
+ return p !== null && p < 100;
16
+ }
17
+ /** Whole-number percent for the badge / aria-valuenow. Floors so 99.6 never reads "100". */
18
+ export function progressPercent(value) {
19
+ const p = clampProgress(value);
20
+ if (p === null)
21
+ return 0;
22
+ return p >= 100 ? 100 : Math.floor(p);
23
+ }
24
+ /**
25
+ * stroke-dashoffset for a path drawn with `pathLength={length}` and
26
+ * `stroke-dasharray={length}`: 0 = fully drawn, `length` = empty.
27
+ */
28
+ export function dashOffset(value, length = 100) {
29
+ const p = clampProgress(value) ?? 0;
30
+ return length - (p / 100) * length;
31
+ }
32
+ /**
33
+ * Geometry for an SVG `<rect>` whose stroke sits exactly on a CSS border of
34
+ * width `stroke` around a `width`×`height` border-box with `radius` corners.
35
+ * The stroke is centred on the path, so the rect is inset by half a stroke and
36
+ * the corner radius shrinks by the same amount (never below 0).
37
+ */
38
+ export function perimeterRect(width, height, stroke = 2, radius = 8) {
39
+ const inset = stroke / 2;
40
+ return {
41
+ x: inset,
42
+ y: inset,
43
+ width: Math.max(0, width - stroke),
44
+ height: Math.max(0, height - stroke),
45
+ rx: Math.max(0, radius - inset)
46
+ };
47
+ }
package/dist/index.d.ts CHANGED
@@ -138,6 +138,23 @@ export { default as TimeSlotPicker } from './components/TimeSlotPicker.svelte';
138
138
  export { default as CalendarPicker } from './components/CalendarPicker.svelte';
139
139
  export { default as AvailabilityModal } from './components/AvailabilityModal.svelte';
140
140
  export { default as BookingForm } from './components/BookingForm.svelte';
141
+ export { default as DatePicker } from './components/DatePicker.svelte';
142
+ export { default as BookingScheduler } from './components/BookingScheduler.svelte';
143
+ export { default as EventCalendar } from './components/EventCalendar.svelte';
144
+ export { default as CalendarMonthGrid } from './components/calendar/MonthGrid.svelte';
145
+ export type { MonthGridDay, MonthGridProps } from './components/calendar/MonthGrid.svelte';
146
+ export { createCalendarGrid } from './components/calendar/createCalendarGrid';
147
+ export type { CalendarGrid, CalendarGridOptions } from './components/calendar/createCalendarGrid';
148
+ export { buildMonthMatrix, nextFocusDate, weekStartForLocale, resolveWeekStart, weekdayLabels, toIsoDate, parseIsoDate, makeIsDisabled, defaultRangePresets, defaultRangePresetsWithAll, resolvePreset, matchPreset, rangeDayCount, formatRangeLabel, datePlaceholder, formatTypedDate, parseTypedDate } from './components/calendar/calendarGrid';
149
+ export type { Weekday, DateRange, IsoDateRange, RangePreset, MonthCell } from './components/calendar/calendarGrid';
150
+ export { formatSlotTime, groupSlotsByDayPart, nextSlotIndex, localeHourCycle, localTimeZone, commonTimeZones, timeZoneOffsetLabel } from './components/calendar/timeSlots';
151
+ export type { HourCycle, DayPart, SlotGroup } from './components/calendar/timeSlots';
152
+ export { packWeek, computeVisibility, eventDayRange } from './components/calendar/monthPacking';
153
+ export type { PackableEvent, MonthDay, WeekSegment, PackedWeek, WeekVisibility } from './components/calendar/monthPacking';
154
+ export { layoutDay, eventInterval } from './components/calendar/dayOverlap';
155
+ export type { DayPlacement, DayOverlapOptions } from './components/calendar/dayOverlap';
156
+ export { eventColorStyle, visibleRange, viewTitle, stepDate, agendaDays } from './components/calendar/eventCalendar';
157
+ export type { CalendarEventItem, EventColor, EventCalendarView } from './components/calendar/eventCalendar';
141
158
  export type { ShortenedLink } from './components/LinkShortener.svelte';
142
159
  export type { SearchDropdownOption, SearchDropdownGroup } from './components/SearchDropdown.svelte';
143
160
  export type { ChipSuggestion, SuggestionGroup, ChipInfo } from './components/ChipInput.svelte';
@@ -235,8 +252,9 @@ export { copyText, readClipboardText } from './utils/clipboard';
235
252
  export { splitText } from './utils/splitText';
236
253
  export type { SplitBy, SplitTextOptions, SplitTextResult } from './utils/splitText';
237
254
  export { default as ToastHost } from './components/toast/ToastHost.svelte';
238
- export { showToast, dismissToast, getToasts } from './stores/toast';
239
- export type { Toast, ToastType, ToastOptions, DismissHandle } from './stores/toast';
255
+ export { showToast, dismissToast, getToasts, normalizeToastActions } from './stores/toast';
256
+ export type { Toast, ToastType, ToastOptions, DismissHandle, ToastAction, ToastAvatar } from './stores/toast';
257
+ export type { ToastPosition } from './components/toast/ToastHost.svelte';
240
258
  export { default as Skeleton } from './components/Skeleton.svelte';
241
259
  export { default as ProgressBar } from './components/progress/ProgressBar.svelte';
242
260
  export { default as ProgressRing } from './components/progress/ProgressRing.svelte';
@@ -285,6 +303,15 @@ export { default as SectionNav } from './components/basic/SectionNav.svelte';
285
303
  export { computeActiveSection, DEFAULT_ACTIVATION_RATIO } from './components/basic/sectionNavSpy';
286
304
  export type { SectionRect, SpyViewport, SpyScrollState } from './components/basic/sectionNavSpy';
287
305
  export { default as Slider } from './components/basic/Slider.svelte';
306
+ export { snapToStep, moveThumb, normalizePair, percentOf, bubbleLeft } from './components/basic/sliderMath';
307
+ export { default as CheckGlyph } from './components/form-cards/CheckGlyph.svelte';
308
+ export { default as ChoiceCard } from './components/form-cards/ChoiceCard.svelte';
309
+ export { default as CheckboxCard } from './components/form-cards/CheckboxCard.svelte';
310
+ export { default as SwitchCard } from './components/form-cards/SwitchCard.svelte';
311
+ export { default as FieldLabel } from './components/field/FieldLabel.svelte';
312
+ export { default as FieldHint } from './components/field/FieldHint.svelte';
313
+ export { dismissOutside, createDismissOutside, isInsidePath } from './actions/dismissOutside';
314
+ export type { DismissOutsideOptions, DismissOutsideController, DismissReason } from './actions/dismissOutside';
288
315
  export { default as Pagination } from './components/basic/Pagination.svelte';
289
316
  export { MAX_UNDO_DEPTH, createHistory, pushState, beginChange, endChange, untracked, undo, redo, clearHistory } from './history';
290
317
  export type { History } from './history';
package/dist/index.js CHANGED
@@ -176,6 +176,17 @@ export { default as TimeSlotPicker } from './components/TimeSlotPicker.svelte';
176
176
  export { default as CalendarPicker } from './components/CalendarPicker.svelte';
177
177
  export { default as AvailabilityModal } from './components/AvailabilityModal.svelte';
178
178
  export { default as BookingForm } from './components/BookingForm.svelte';
179
+ // Dates + calendar (BoardUI wave 4, jatui-lfxyy). Every helper is also on 'jatui/calendar'.
180
+ export { default as DatePicker } from './components/DatePicker.svelte';
181
+ export { default as BookingScheduler } from './components/BookingScheduler.svelte';
182
+ export { default as EventCalendar } from './components/EventCalendar.svelte';
183
+ export { default as CalendarMonthGrid } from './components/calendar/MonthGrid.svelte';
184
+ export { createCalendarGrid } from './components/calendar/createCalendarGrid';
185
+ export { buildMonthMatrix, nextFocusDate, weekStartForLocale, resolveWeekStart, weekdayLabels, toIsoDate, parseIsoDate, makeIsDisabled, defaultRangePresets, defaultRangePresetsWithAll, resolvePreset, matchPreset, rangeDayCount, formatRangeLabel, datePlaceholder, formatTypedDate, parseTypedDate } from './components/calendar/calendarGrid';
186
+ export { formatSlotTime, groupSlotsByDayPart, nextSlotIndex, localeHourCycle, localTimeZone, commonTimeZones, timeZoneOffsetLabel } from './components/calendar/timeSlots';
187
+ export { packWeek, computeVisibility, eventDayRange } from './components/calendar/monthPacking';
188
+ export { layoutDay, eventInterval } from './components/calendar/dayOverlap';
189
+ export { eventColorStyle, visibleRange, viewTitle, stepDate, agendaDays } from './components/calendar/eventCalendar';
179
190
  export { DAYS_OF_WEEK, DAYS_SHORT, LOCATION_LABELS, LOCATION_ICONS } from './types/booking';
180
191
  // Utilities — date formatting
181
192
  export { normalizeTimestamp, parseTimestamp, formatRelativeTime, formatFullDate, formatShortDate, formatLastActivity, formatDate, getTimeSinceMs, getTimeSinceMinutes, isWithinMinutes, formatDateTime, taskAge, pacingColor, formatPacingDuration, } from './utils/dateFormatters';
@@ -283,7 +294,7 @@ export { copyText, readClipboardText } from './utils/clipboard';
283
294
  export { splitText } from './utils/splitText';
284
295
  // Toast system
285
296
  export { default as ToastHost } from './components/toast/ToastHost.svelte';
286
- export { showToast, dismissToast, getToasts } from './stores/toast';
297
+ export { showToast, dismissToast, getToasts, normalizeToastActions } from './stores/toast';
287
298
  // Loading & progress family
288
299
  export { default as Skeleton } from './components/Skeleton.svelte';
289
300
  export { default as ProgressBar } from './components/progress/ProgressBar.svelte';
@@ -340,6 +351,16 @@ export { default as Separator } from './components/basic/Separator.svelte';
340
351
  export { default as SectionNav } from './components/basic/SectionNav.svelte';
341
352
  export { computeActiveSection, DEFAULT_ACTIVATION_RATIO } from './components/basic/sectionNavSpy';
342
353
  export { default as Slider } from './components/basic/Slider.svelte';
354
+ export { snapToStep, moveThumb, normalizePair, percentOf, bubbleLeft } from './components/basic/sliderMath';
355
+ // BoardUI wave 5 — form polish (jatui-bns3w): choice cards + animated tick,
356
+ // shared field label/hint parts, dismissOutside.
357
+ export { default as CheckGlyph } from './components/form-cards/CheckGlyph.svelte';
358
+ export { default as ChoiceCard } from './components/form-cards/ChoiceCard.svelte';
359
+ export { default as CheckboxCard } from './components/form-cards/CheckboxCard.svelte';
360
+ export { default as SwitchCard } from './components/form-cards/SwitchCard.svelte';
361
+ export { default as FieldLabel } from './components/field/FieldLabel.svelte';
362
+ export { default as FieldHint } from './components/field/FieldHint.svelte';
363
+ export { dismissOutside, createDismissOutside, isInsidePath } from './actions/dismissOutside';
343
364
  export { default as Pagination } from './components/basic/Pagination.svelte';
344
365
  // Snapshot undo/redo history (wayflow-core) — also importable as 'jatui/history' (jatui-00jfh).
345
366
  export { MAX_UNDO_DEPTH, createHistory, pushState, beginChange, endChange, untracked, undo, redo, clearHistory } from './history';
@@ -1,36 +1,71 @@
1
+ import type { Snippet } from 'svelte';
1
2
  export type ToastType = 'info' | 'success' | 'warning' | 'error';
3
+ /** One button on a toast. `variant` 'primary' renders a filled button; the
4
+ * default 'ghost' matches the legacy single-action button. */
5
+ export type ToastAction = {
6
+ label: string;
7
+ onClick: () => void;
8
+ variant?: 'primary' | 'ghost';
9
+ };
10
+ /** Image shown in place of the status icon (e.g. the sender of a message). */
11
+ export type ToastAvatar = {
12
+ src: string;
13
+ alt: string;
14
+ };
2
15
  export type ToastOptions = {
3
16
  type?: ToastType;
4
17
  message: string;
18
+ /** Bold heading above `message`. Optional — without it the toast renders
19
+ * exactly as before. */
20
+ title?: string;
21
+ /** Leading icon. A string renders as a glyph/emoji; a Snippet renders as-is.
22
+ * Ignored when `avatar` is set. */
23
+ icon?: Snippet | string;
24
+ /** Leading avatar image (takes precedence over `icon`). */
25
+ avatar?: ToastAvatar;
5
26
  /** Extended detail text (e.g. a stack trace or error chain) — rendered by
6
27
  * ToastHost behind a collapsible "Details" toggle. Multiline-safe. */
7
28
  details?: string;
8
29
  /** Arbitrary structured context carried on the Toast for programmatic
9
30
  * access (getToasts()); not rendered by ToastHost. */
10
31
  meta?: Record<string, unknown>;
32
+ /** Legacy single action. Still supported; folded into `actions`. */
11
33
  action?: {
12
34
  label: string;
13
35
  onClick: () => void;
14
36
  };
37
+ /** Several action buttons, rendered in order after the message. */
38
+ actions?: ToastAction[];
15
39
  durationMs?: number;
16
40
  onUndo?: () => void;
41
+ /** Show a draining countdown bar on a timed (non-undo) toast. It pauses on
42
+ * hover/focus together with the timer. Default false. */
43
+ showProgress?: boolean;
17
44
  };
18
45
  export type Toast = {
19
46
  id: number;
20
47
  type: ToastType;
21
48
  message: string;
49
+ title?: string;
50
+ icon?: Snippet | string;
51
+ avatar?: ToastAvatar;
22
52
  details?: string;
23
53
  meta?: Record<string, unknown>;
24
54
  action?: {
25
55
  label: string;
26
56
  onClick: () => void;
27
57
  };
58
+ /** Normalised list: `actions` followed by the legacy `action` (if any). */
59
+ actions: ToastAction[];
28
60
  durationMs: number;
29
61
  onUndo?: () => void;
62
+ showProgress: boolean;
30
63
  };
31
64
  export type DismissHandle = {
32
65
  dismiss: () => void;
33
66
  };
34
67
  export declare function getToasts(): Toast[];
68
+ /** Merge the legacy single `action` with `actions[]` (pure; exported for tests). */
69
+ export declare function normalizeToastActions(options: Pick<ToastOptions, 'action' | 'actions'>): ToastAction[];
35
70
  export declare function showToast(options: ToastOptions): DismissHandle;
36
71
  export declare function dismissToast(id: number): void;
@@ -8,6 +8,15 @@ export function getToasts() {
8
8
  return $.get(toasts);
9
9
  }
10
10
 
11
+ /** Merge the legacy single `action` with `actions[]` (pure; exported for tests). */
12
+ export function normalizeToastActions(options) {
13
+ const list = [...options.actions ?? []];
14
+
15
+ if (options.action) list.push({ ...options.action, variant: 'ghost' });
16
+
17
+ return list;
18
+ }
19
+
11
20
  export function showToast(options) {
12
21
  const id = ++nextId;
13
22
 
@@ -15,11 +24,16 @@ export function showToast(options) {
15
24
  id,
16
25
  type: options.type ?? 'info',
17
26
  message: options.message,
27
+ title: options.title,
28
+ icon: options.icon,
29
+ avatar: options.avatar,
18
30
  details: options.details,
19
31
  meta: options.meta,
20
32
  action: options.action,
33
+ actions: normalizeToastActions(options),
21
34
  durationMs: options.durationMs ?? 5000,
22
- onUndo: options.onUndo
35
+ onUndo: options.onUndo,
36
+ showProgress: options.showProgress ?? false
23
37
  };
24
38
 
25
39
  $.set(toasts, [...$.get(toasts), toast], true);
@@ -223,3 +223,75 @@
223
223
  opacity: 1;
224
224
  }
225
225
  }
226
+
227
+ /*
228
+ * One-shot utility animations (BoardUI wave 5, jatui-bns3w). All on the
229
+ * tokens above, so they scale with data-motion and collapse under reduced
230
+ * motion. Ideas after BoardUI's globals.css (MIT © 2026 Mertcan Dundar
231
+ * Esmergul).
232
+ *
233
+ * .jatui-check-draw — an SVG stroke with pathLength="1" draws itself on
234
+ * (the CheckGlyph tick carries its own copy).
235
+ * .jatui-row-exit — a list row slides aside and collapses its height;
236
+ * remove it on `animationend` (bulk-delete flows).
237
+ * .jatui-label-out / .jatui-label-in — a button label swap: old text
238
+ * lifts out, new text rises in.
239
+ */
240
+ .jatui-check-draw {
241
+ stroke-dasharray: 1;
242
+ stroke-dashoffset: 1;
243
+ animation: jatui-check-draw var(--motion-fast) var(--motion-glide) forwards;
244
+ }
245
+
246
+ @keyframes jatui-check-draw {
247
+ to {
248
+ stroke-dashoffset: 0;
249
+ }
250
+ }
251
+
252
+ .jatui-row-exit {
253
+ overflow: hidden;
254
+ animation: jatui-row-exit var(--motion-move) var(--motion-standard) forwards;
255
+ }
256
+
257
+ @keyframes jatui-row-exit {
258
+ 0% {
259
+ opacity: 1;
260
+ transform: translateX(0);
261
+ max-height: var(--jatui-row-h, 4rem);
262
+ }
263
+ 50% {
264
+ opacity: 0;
265
+ transform: translateX(calc(var(--motion-travel) * 1.5));
266
+ max-height: var(--jatui-row-h, 4rem);
267
+ }
268
+ 100% {
269
+ opacity: 0;
270
+ transform: translateX(calc(var(--motion-travel) * 1.5));
271
+ max-height: 0;
272
+ padding-block: 0;
273
+ margin-block: 0;
274
+ }
275
+ }
276
+
277
+ .jatui-label-out {
278
+ animation: jatui-label-out var(--motion-fast) var(--motion-glide) forwards;
279
+ }
280
+
281
+ .jatui-label-in {
282
+ animation: jatui-label-in var(--motion-enter) var(--motion-glide) both;
283
+ }
284
+
285
+ @keyframes jatui-label-out {
286
+ to {
287
+ opacity: 0;
288
+ transform: translateY(calc(var(--motion-travel) * -0.5));
289
+ }
290
+ }
291
+
292
+ @keyframes jatui-label-in {
293
+ from {
294
+ opacity: 0;
295
+ transform: translateY(calc(var(--motion-travel) * 0.5));
296
+ }
297
+ }