@joewinke/jatui 0.1.27 → 0.1.29

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 (37) hide show
  1. package/CLAUDE.md +3 -3
  2. package/LICENSE +21 -0
  3. package/dist/actions/collapse.d.ts +1 -1
  4. package/dist/actions/collapse.js +16 -30
  5. package/dist/actions/listNav.d.ts +134 -0
  6. package/dist/actions/listNav.js +643 -0
  7. package/dist/actions/railNav.js +78 -89
  8. package/dist/components/InlineEdit.svelte +26 -1
  9. package/dist/components/InlineEdit.svelte.d.ts +13 -0
  10. package/dist/components/PhoneInput.svelte +70 -20
  11. package/dist/components/PhoneInput.svelte.d.ts +5 -2
  12. package/dist/components/ResizableDivider.svelte +148 -18
  13. package/dist/components/ResizableDivider.svelte.d.ts +14 -3
  14. package/dist/components/SignaturePad.svelte +13 -6
  15. package/dist/components/SignaturePad.svelte.d.ts +1 -0
  16. package/dist/components/ThemeSelector.svelte +61 -2
  17. package/dist/components/ThemeSelector.svelte.d.ts +4 -0
  18. package/dist/components/markup.js +59 -62
  19. package/dist/components/session-nav/transcriptModel.js +49 -66
  20. package/dist/data/emojis.js +4 -4
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +1 -0
  23. package/dist/stores/confirmDialog.svelte.js +5 -5
  24. package/dist/stores/inputDialog.svelte.js +5 -5
  25. package/dist/types/booking.js +4 -4
  26. package/dist/types/comments.js +2 -2
  27. package/dist/types/messaging.js +3 -3
  28. package/dist/types/pipeline.js +2 -2
  29. package/dist/utils/currency.js +2 -4
  30. package/dist/utils/daisyuiColors.js +25 -27
  31. package/dist/utils/dateFormatters.js +44 -44
  32. package/dist/utils/googleMapsLoader.js +46 -100
  33. package/dist/utils/mentionParser.js +30 -30
  34. package/dist/utils/phone.js +21 -27
  35. package/dist/utils/phoneFormat.js +9 -9
  36. package/dist/utils/taskUtils.js +1 -1
  37. package/package.json +11 -5
@@ -14,12 +14,23 @@
14
14
  */
15
15
 
16
16
  interface Props {
17
- onResize: (deltaY: number) => void;
17
+ /**
18
+ * Drag delta callback. For `horizontal` orientation this is the vertical
19
+ * pointer delta (deltaY); for `vertical` orientation it is the horizontal
20
+ * pointer delta (deltaX). Positive = down / right.
21
+ */
22
+ onResize: (delta: number) => void;
18
23
  class?: string;
24
+ /**
25
+ * Divider axis. `horizontal` = a horizontal bar that resizes panels stacked
26
+ * vertically (drag up/down). `vertical` = a vertical bar that resizes panels
27
+ * laid out side-by-side (drag left/right). Default: horizontal.
28
+ */
29
+ orientation?: 'horizontal' | 'vertical';
19
30
  /** Whether a panel is currently collapsed */
20
31
  isCollapsed?: boolean;
21
- /** Which direction the panel collapsed ('top' = top panel hidden, 'bottom' = bottom panel hidden) */
22
- collapsedDirection?: 'top' | 'bottom' | null;
32
+ /** Which direction the panel collapsed (hidden panel side) */
33
+ collapsedDirection?: 'top' | 'bottom' | 'left' | 'right' | null;
23
34
  /** Callback when clicking the divider while collapsed (to restore) */
24
35
  onCollapsedClick?: () => void;
25
36
  }
@@ -27,21 +38,26 @@
27
38
  let {
28
39
  onResize,
29
40
  class: className = '',
41
+ orientation = 'horizontal',
30
42
  isCollapsed = false,
31
43
  collapsedDirection = null,
32
44
  onCollapsedClick
33
45
  }: Props = $props();
34
46
 
47
+ const isVertical = $derived(orientation === 'vertical');
48
+
35
49
  let isDragging = $state(false);
36
50
  let isNearby = $state(false);
37
51
  let isHovering = $state(false);
38
- let startY = $state(0);
52
+ // Drag origin along the active axis (clientY for horizontal, clientX for vertical).
53
+ let startPos = $state(0);
39
54
  let dividerElement: HTMLDivElement | undefined = $state();
40
55
 
41
56
  // Proximity detection threshold in pixels
42
57
  const PROXIMITY_THRESHOLD = 24;
43
58
 
44
59
  function handleMouseDown(e: MouseEvent) {
60
+ // If collapsed and we have a restore handler, treat click as restore
45
61
  if (isCollapsed && onCollapsedClick) {
46
62
  onCollapsedClick();
47
63
  return;
@@ -49,7 +65,7 @@
49
65
 
50
66
  e.preventDefault();
51
67
  isDragging = true;
52
- startY = e.clientY;
68
+ startPos = isVertical ? e.clientX : e.clientY;
53
69
 
54
70
  document.addEventListener('mousemove', handleMouseMove);
55
71
  document.addEventListener('mouseup', handleMouseUp);
@@ -57,9 +73,10 @@
57
73
 
58
74
  function handleMouseMove(e: MouseEvent) {
59
75
  if (!isDragging) return;
60
- const deltaY = e.clientY - startY;
61
- startY = e.clientY;
62
- onResize(deltaY);
76
+ const pos = isVertical ? e.clientX : e.clientY;
77
+ const delta = pos - startPos;
78
+ startPos = pos;
79
+ onResize(delta);
63
80
  }
64
81
 
65
82
  function handleMouseUp() {
@@ -69,6 +86,7 @@
69
86
  }
70
87
 
71
88
  function handleTouchStart(e: TouchEvent) {
89
+ // If collapsed and we have a restore handler, treat tap as restore
72
90
  if (isCollapsed && onCollapsedClick) {
73
91
  onCollapsedClick();
74
92
  return;
@@ -76,14 +94,15 @@
76
94
 
77
95
  if (e.touches.length !== 1) return;
78
96
  isDragging = true;
79
- startY = e.touches[0].clientY;
97
+ startPos = isVertical ? e.touches[0].clientX : e.touches[0].clientY;
80
98
  }
81
99
 
82
100
  function handleTouchMove(e: TouchEvent) {
83
101
  if (!isDragging || e.touches.length !== 1) return;
84
- const deltaY = e.touches[0].clientY - startY;
85
- startY = e.touches[0].clientY;
86
- onResize(deltaY);
102
+ const pos = isVertical ? e.touches[0].clientX : e.touches[0].clientY;
103
+ const delta = pos - startPos;
104
+ startPos = pos;
105
+ onResize(delta);
87
106
  }
88
107
 
89
108
  function handleTouchEnd() {
@@ -98,16 +117,19 @@
98
117
  isHovering = false;
99
118
  }
100
119
 
120
+ // Proximity detection: track mouse movement near the divider
101
121
  function handleProximityMove(e: MouseEvent) {
102
122
  if (!dividerElement || isDragging) return;
103
123
 
104
124
  const rect = dividerElement.getBoundingClientRect();
105
- const dividerCenterY = rect.top + rect.height / 2;
106
- const distance = Math.abs(e.clientY - dividerCenterY);
125
+ const distance = isVertical
126
+ ? Math.abs(e.clientX - (rect.left + rect.width / 2))
127
+ : Math.abs(e.clientY - (rect.top + rect.height / 2));
107
128
 
108
129
  isNearby = distance <= PROXIMITY_THRESHOLD;
109
130
  }
110
131
 
132
+ // Set up proximity tracking on mount
111
133
  $effect(() => {
112
134
  if (typeof window === 'undefined') return;
113
135
 
@@ -118,19 +140,21 @@
118
140
  };
119
141
  });
120
142
 
143
+ // Derived state for expanded appearance
121
144
  const isExpanded = $derived(isDragging || isHovering || isNearby);
122
145
  </script>
123
146
 
124
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
125
147
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
148
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
126
149
  <div
127
150
  bind:this={dividerElement}
128
- class="divider-container flex items-center justify-center select-none {className} {isCollapsed ? 'cursor-pointer divider-collapsed' : 'cursor-row-resize'} {isDragging ? 'bg-primary/25' : isExpanded && isCollapsed ? 'bg-primary/40' : isExpanded ? 'bg-primary/15' : ''}"
151
+ class="divider-container flex items-center justify-center select-none {className} {isCollapsed ? 'cursor-pointer divider-collapsed' : isVertical ? 'cursor-col-resize' : 'cursor-row-resize'} {isDragging ? 'bg-primary/25' : isExpanded && isCollapsed ? 'bg-primary/40' : isExpanded ? 'bg-primary/15' : ''}"
129
152
  class:expanded={isExpanded}
130
153
  class:dragging={isDragging}
154
+ class:vertical={isVertical}
131
155
  role="separator"
132
- aria-orientation="horizontal"
133
- aria-label={isCollapsed ? 'Expand panel' : 'Resize panel'}
156
+ aria-orientation={isVertical ? 'vertical' : 'horizontal'}
157
+ aria-valuenow={isCollapsed ? 0 : 1}
134
158
  tabindex="0"
135
159
  onmousedown={handleMouseDown}
136
160
  onmouseenter={handleMouseEnter}
@@ -140,6 +164,7 @@
140
164
  ontouchend={handleTouchEnd}
141
165
  >
142
166
  {#if isCollapsed}
167
+ <!-- Collapsed state: minimal industrial indicator -->
143
168
  <div class="collapsed-indicator">
144
169
  <div class="indicator-line"></div>
145
170
  <div class="indicator-dots">
@@ -150,6 +175,7 @@
150
175
  <div class="indicator-line"></div>
151
176
  </div>
152
177
  {:else}
178
+ <!-- Normal state: grippy handle with proximity-aware sizing -->
153
179
  <div class="grippy-handle">
154
180
  <div class="grip-line"></div>
155
181
  <div class="grip-line"></div>
@@ -158,6 +184,7 @@
158
184
  </div>
159
185
 
160
186
  <style>
187
+ /* Main divider container with smooth transitions */
161
188
  .divider-container {
162
189
  height: 8px;
163
190
  min-height: 8px;
@@ -167,6 +194,8 @@
167
194
  box-shadow 200ms ease;
168
195
  }
169
196
 
197
+ /* Expanded state: larger hit target + glow */
198
+ /* NOTE: background uses Tailwind class (bg-primary/15) on element instead of CSS */
170
199
  .divider-container.expanded {
171
200
  height: 16px;
172
201
  min-height: 16px;
@@ -175,6 +204,8 @@
175
204
  inset 0 0 8px color-mix(in oklch, var(--color-primary) 20%, transparent);
176
205
  }
177
206
 
207
+ /* Dragging state: stronger glow */
208
+ /* NOTE: background uses Tailwind class (bg-primary/25) on element instead of CSS */
178
209
  .divider-container.dragging {
179
210
  height: 16px;
180
211
  min-height: 16px;
@@ -183,6 +214,7 @@
183
214
  inset 0 0 12px color-mix(in oklch, var(--color-primary) 30%, transparent);
184
215
  }
185
216
 
217
+ /* Grippy handle styling */
186
218
  .grippy-handle {
187
219
  display: flex;
188
220
  flex-direction: column;
@@ -226,6 +258,7 @@
226
258
  box-shadow: 0 0 8px color-mix(in oklch, var(--color-primary) 60%, transparent);
227
259
  }
228
260
 
261
+ /* Collapsed state styling */
229
262
  .divider-collapsed {
230
263
  height: 6px;
231
264
  min-height: 6px;
@@ -233,6 +266,8 @@
233
266
  border-color: var(--color-base-300) !important;
234
267
  }
235
268
 
269
+ /* Collapsed + expanded (proximity/hover): grow larger for easy targeting */
270
+ /* NOTE: background uses Tailwind class (bg-primary/40) on element instead of CSS */
236
271
  .divider-collapsed.expanded {
237
272
  height: 20px;
238
273
  min-height: 20px;
@@ -250,6 +285,7 @@
250
285
  transition: transform 200ms ease;
251
286
  }
252
287
 
288
+ /* Scale up the indicator when expanded */
253
289
  .divider-collapsed.expanded .collapsed-indicator {
254
290
  transform: scaleY(1.5);
255
291
  }
@@ -267,6 +303,7 @@
267
303
  transition: background 200ms ease, height 200ms ease;
268
304
  }
269
305
 
306
+ /* Brighter lines when expanded */
270
307
  .divider-collapsed.expanded .indicator-line {
271
308
  height: 2px;
272
309
  background: linear-gradient(
@@ -293,6 +330,7 @@
293
330
  transition: width 200ms ease, height 200ms ease, background 200ms ease, box-shadow 200ms ease;
294
331
  }
295
332
 
333
+ /* Expanded state: larger, brighter dots with glow */
296
334
  .divider-collapsed.expanded .indicator-dots {
297
335
  opacity: 1;
298
336
  gap: 5px;
@@ -304,4 +342,96 @@
304
342
  background: var(--color-primary);
305
343
  box-shadow: 0 0 8px color-mix(in oklch, var(--color-primary) 70%, transparent);
306
344
  }
345
+
346
+ /* ── Vertical orientation ─────────────────────────────────────────────────
347
+ Mirrors the horizontal styles across the X axis: the bar runs top-to-bottom
348
+ and resizes side-by-side panels (drag left/right). Width replaces height
349
+ everywhere, and the grippy handle / collapsed indicator rotate 90°. */
350
+ .divider-container.vertical {
351
+ height: auto;
352
+ min-height: 0;
353
+ width: 8px;
354
+ min-width: 8px;
355
+ }
356
+
357
+ .divider-container.vertical.expanded {
358
+ height: auto;
359
+ min-height: 0;
360
+ width: 16px;
361
+ min-width: 16px;
362
+ }
363
+
364
+ .divider-container.vertical.dragging {
365
+ height: auto;
366
+ min-height: 0;
367
+ width: 16px;
368
+ min-width: 16px;
369
+ }
370
+
371
+ .divider-container.vertical .grippy-handle {
372
+ flex-direction: row;
373
+ padding: 32px 4px;
374
+ }
375
+
376
+ .divider-container.vertical.expanded .grippy-handle {
377
+ transform: scaleX(1.2);
378
+ }
379
+
380
+ .divider-container.vertical.dragging .grippy-handle {
381
+ transform: scaleX(1.3);
382
+ }
383
+
384
+ .divider-container.vertical .grip-line {
385
+ width: 2px;
386
+ height: 32px;
387
+ }
388
+
389
+ .divider-collapsed.vertical {
390
+ height: auto;
391
+ min-height: 0;
392
+ width: 6px;
393
+ min-width: 6px;
394
+ }
395
+
396
+ .divider-collapsed.vertical.expanded {
397
+ height: auto;
398
+ min-height: 0;
399
+ width: 20px;
400
+ min-width: 20px;
401
+ }
402
+
403
+ .divider-container.vertical .collapsed-indicator {
404
+ flex-direction: column;
405
+ width: auto;
406
+ height: 100%;
407
+ padding: 16px 0;
408
+ }
409
+
410
+ .divider-collapsed.vertical.expanded .collapsed-indicator {
411
+ transform: scaleX(1.5);
412
+ }
413
+
414
+ .divider-container.vertical .indicator-line {
415
+ width: 1px;
416
+ height: auto;
417
+ background: linear-gradient(
418
+ 180deg,
419
+ transparent 0%,
420
+ color-mix(in oklch, var(--color-base-content) 30%, transparent) 20%,
421
+ color-mix(in oklch, var(--color-base-content) 30%, transparent) 80%,
422
+ transparent 100%
423
+ );
424
+ }
425
+
426
+ .divider-collapsed.vertical.expanded .indicator-line {
427
+ width: 2px;
428
+ height: auto;
429
+ background: linear-gradient(
430
+ 180deg,
431
+ transparent 0%,
432
+ color-mix(in oklch, var(--color-primary) 70%, transparent) 15%,
433
+ color-mix(in oklch, var(--color-primary) 70%, transparent) 85%,
434
+ transparent 100%
435
+ );
436
+ }
307
437
  </style>
@@ -12,12 +12,23 @@
12
12
  * - Glow effect on hover for better visibility
13
13
  */
14
14
  interface Props {
15
- onResize: (deltaY: number) => void;
15
+ /**
16
+ * Drag delta callback. For `horizontal` orientation this is the vertical
17
+ * pointer delta (deltaY); for `vertical` orientation it is the horizontal
18
+ * pointer delta (deltaX). Positive = down / right.
19
+ */
20
+ onResize: (delta: number) => void;
16
21
  class?: string;
22
+ /**
23
+ * Divider axis. `horizontal` = a horizontal bar that resizes panels stacked
24
+ * vertically (drag up/down). `vertical` = a vertical bar that resizes panels
25
+ * laid out side-by-side (drag left/right). Default: horizontal.
26
+ */
27
+ orientation?: 'horizontal' | 'vertical';
17
28
  /** Whether a panel is currently collapsed */
18
29
  isCollapsed?: boolean;
19
- /** Which direction the panel collapsed ('top' = top panel hidden, 'bottom' = bottom panel hidden) */
20
- collapsedDirection?: 'top' | 'bottom' | null;
30
+ /** Which direction the panel collapsed (hidden panel side) */
31
+ collapsedDirection?: 'top' | 'bottom' | 'left' | 'right' | null;
21
32
  /** Callback when clicking the divider while collapsed (to restore) */
22
33
  onCollapsedClick?: () => void;
23
34
  }
@@ -4,6 +4,8 @@
4
4
  *
5
5
  * Exports `clear()` and `isEmpty()` methods. Outputs signature as base64 PNG data URL
6
6
  * via the bindable `signatureData` prop or `onSignatureChange` callback.
7
+ *
8
+ * Pass `disabled` to render a non-interactive, dimmed pad (e.g. form-builder previews).
7
9
  */
8
10
  import { onMount } from 'svelte';
9
11
 
@@ -17,6 +19,7 @@
17
19
  label = 'Signature',
18
20
  required = false,
19
21
  placeholder = 'Sign here',
22
+ disabled = false,
20
23
  }: {
21
24
  signatureData?: string;
22
25
  onSignatureChange?: (data: string) => void;
@@ -27,6 +30,7 @@
27
30
  label?: string;
28
31
  required?: boolean;
29
32
  placeholder?: string;
33
+ disabled?: boolean;
30
34
  } = $props();
31
35
 
32
36
  let canvas: HTMLCanvasElement;
@@ -68,6 +72,7 @@
68
72
  }
69
73
 
70
74
  function startDrawing(e: MouseEvent | TouchEvent) {
75
+ if (disabled) return;
71
76
  e.preventDefault();
72
77
  isDrawing = true;
73
78
  const coords = getCoordinates(e);
@@ -76,7 +81,7 @@
76
81
  }
77
82
 
78
83
  function draw(e: MouseEvent | TouchEvent) {
79
- if (!isDrawing || !ctx) return;
84
+ if (disabled || !isDrawing || !ctx) return;
80
85
  e.preventDefault();
81
86
 
82
87
  const coords = getCoordinates(e);
@@ -145,19 +150,21 @@
145
150
  <span class="text-error">*</span>
146
151
  {/if}
147
152
  </span>
148
- <button type="button" onclick={() => clear()} class="btn btn-ghost btn-xs">
149
- Clear
150
- </button>
153
+ {#if !disabled}
154
+ <button type="button" onclick={() => clear()} class="btn btn-ghost btn-xs">
155
+ Clear
156
+ </button>
157
+ {/if}
151
158
  </div>
152
159
  {/if}
153
160
 
154
161
  <div class="rounded-lg bg-base-100 overflow-hidden relative transition-all duration-300
155
- {hasDrawn ? 'border-2 border-success/60' : 'border-2 border-base-300'}">
162
+ {disabled ? 'opacity-50 border-2 border-base-300' : hasDrawn ? 'border-2 border-success/60' : 'border-2 border-base-300'}">
156
163
  <canvas
157
164
  bind:this={canvas}
158
165
  {width}
159
166
  {height}
160
- class="w-full touch-none cursor-crosshair"
167
+ class="w-full touch-none {disabled ? 'cursor-not-allowed' : 'cursor-crosshair'}"
161
168
  ></canvas>
162
169
 
163
170
  {#if !hasDrawn}
@@ -8,6 +8,7 @@ type $$ComponentProps = {
8
8
  label?: string;
9
9
  required?: boolean;
10
10
  placeholder?: string;
11
+ disabled?: boolean;
11
12
  };
12
13
  declare const SignaturePad: import("svelte").Component<$$ComponentProps, {
13
14
  clear: () => void;
@@ -53,12 +53,18 @@
53
53
  defaultTheme = 'light',
54
54
  storageKey = 'theme',
55
55
  onThemeChange,
56
+ compact = false,
57
+ inline = false,
56
58
  }: {
57
59
  dropUp?: boolean;
58
60
  themes?: ThemeOption[];
59
61
  defaultTheme?: string;
60
62
  storageKey?: string;
61
63
  onThemeChange?: (theme: string) => void;
64
+ /** Icon-only trigger (label hidden, shown as tooltip). Dropdown mode only. */
65
+ compact?: boolean;
66
+ /** Render a collapsible `<details>` theme list instead of a dropdown — for embedding inside another menu. */
67
+ inline?: boolean;
62
68
  } = $props();
63
69
 
64
70
  // svelte-ignore state_referenced_locally
@@ -104,6 +110,51 @@
104
110
  }
105
111
  </script>
106
112
 
113
+ {#if inline}
114
+ <!-- Inline mode: collapsible theme list with color previews, for embedding inside another menu -->
115
+ <details class="w-full theme-details">
116
+ <summary class="flex items-center gap-2 cursor-pointer hover:bg-base-300 transition-colors px-2 py-1.5 rounded list-none">
117
+ <div
118
+ data-theme={currentTheme}
119
+ class="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded p-0.5 w-4 h-4 transition-transform duration-300 ease-out"
120
+ class:theme-spin={isAnimating}
121
+ >
122
+ <div class="bg-base-content size-1 rounded-full transition-all duration-300 theme-dot" class:theme-dot-animate={isAnimating} style="--dot-delay: 0ms"></div>
123
+ <div class="bg-primary size-1 rounded-full transition-all duration-300 theme-dot" class:theme-dot-animate={isAnimating} style="--dot-delay: 50ms"></div>
124
+ <div class="bg-secondary size-1 rounded-full transition-all duration-300 theme-dot" class:theme-dot-animate={isAnimating} style="--dot-delay: 100ms"></div>
125
+ <div class="bg-accent size-1 rounded-full transition-all duration-300 theme-dot" class:theme-dot-animate={isAnimating} style="--dot-delay: 150ms"></div>
126
+ </div>
127
+ <span class="text-[0.75rem] flex-1 text-left text-base-content/70">{currentThemeLabel}</span>
128
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-3 h-3 text-base-content/45 transition-transform duration-200 details-chevron">
129
+ <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
130
+ </svg>
131
+ </summary>
132
+ <div class="max-h-48 overflow-y-auto mt-1 ml-2 border-l border-base-content/10 pl-2">
133
+ {#each themes as theme}
134
+ <button
135
+ class="flex items-center gap-2 w-full px-2 py-1 rounded transition-colors hover:bg-base-300 {currentTheme === theme.name ? 'bg-primary/20' : ''}"
136
+ onclick={() => handleThemeChange(theme.name)}
137
+ >
138
+ <div
139
+ data-theme={theme.name}
140
+ class="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded p-0.5 w-4 h-4"
141
+ >
142
+ <div class="bg-base-content size-1 rounded-full"></div>
143
+ <div class="bg-primary size-1 rounded-full"></div>
144
+ <div class="bg-secondary size-1 rounded-full"></div>
145
+ <div class="bg-accent size-1 rounded-full"></div>
146
+ </div>
147
+ <span class="text-[0.75rem] text-base-content/70 flex-1 text-left">{theme.label}</span>
148
+ {#if currentTheme === theme.name}
149
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-3 h-3 text-primary">
150
+ <path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"></path>
151
+ </svg>
152
+ {/if}
153
+ </button>
154
+ {/each}
155
+ </div>
156
+ </details>
157
+ {:else}
107
158
  <div class="dropdown dropdown-end" class:dropdown-top={dropUp}>
108
159
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
109
160
  <div
@@ -111,7 +162,7 @@
111
162
  role="button"
112
163
  class="flex items-center gap-2 cursor-pointer hover:bg-base-300 transition-colors px-2 py-1 rounded"
113
164
  aria-label="Change Theme"
114
- title="Change Theme"
165
+ title={compact ? currentThemeLabel : 'Change Theme'}
115
166
  >
116
167
  <div
117
168
  data-theme={currentTheme}
@@ -123,7 +174,9 @@
123
174
  <div class="bg-secondary size-1 rounded-full transition-all duration-300 theme-dot" class:theme-dot-animate={isAnimating} style="--dot-delay: 100ms"></div>
124
175
  <div class="bg-accent size-1 rounded-full transition-all duration-300 theme-dot" class:theme-dot-animate={isAnimating} style="--dot-delay: 150ms"></div>
125
176
  </div>
126
- <span class="text-sm font-medium transition-opacity duration-200" class:opacity-50={isAnimating}>{currentThemeLabel}</span>
177
+ {#if !compact}
178
+ <span class="text-sm font-medium transition-opacity duration-200" class:opacity-50={isAnimating}>{currentThemeLabel}</span>
179
+ {/if}
127
180
  </div>
128
181
 
129
182
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
@@ -171,8 +224,14 @@
171
224
  </ul>
172
225
  </div>
173
226
  </div>
227
+ {/if}
174
228
 
175
229
  <style>
230
+ /* Rotate chevron when the inline details list is open */
231
+ .theme-details[open] .details-chevron {
232
+ transform: rotate(180deg);
233
+ }
234
+
176
235
  /* NOTE: DaisyUI v5.0.0 has a stuck-animation bug on `.dropdown-content`
177
236
  (`@keyframes dropdown { 0% { opacity: 0 } }` pins opacity to 0). If the
178
237
  theme dropdown opens but stays invisible, the consuming project needs
@@ -8,6 +8,10 @@ type $$ComponentProps = {
8
8
  defaultTheme?: string;
9
9
  storageKey?: string;
10
10
  onThemeChange?: (theme: string) => void;
11
+ /** Icon-only trigger (label hidden, shown as tooltip). Dropdown mode only. */
12
+ compact?: boolean;
13
+ /** Render a collapsible `<details>` theme list instead of a dropdown — for embedding inside another menu. */
14
+ inline?: boolean;
11
15
  };
12
16
  declare const ThemeSelector: import("svelte").Component<$$ComponentProps, {}, "">;
13
17
  type ThemeSelector = ReturnType<typeof ThemeSelector>;