@classic-homes/theme-svelte 1.37.0 → 1.39.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.
@@ -0,0 +1,80 @@
1
+ <!--
2
+ @component
3
+ A keyboard key or shortcut. Pass `keys` for a combination ("Mod" shows as
4
+ ⌘ on Apple devices and Ctrl elsewhere), or `children` for a single key.
5
+ -->
6
+ <script lang="ts" module>
7
+ const SYMBOLS: Record<string, { apple: string; other: string; name: string }> = {
8
+ mod: { apple: '⌘', other: 'Ctrl', name: 'Command' },
9
+ ctrl: { apple: '⌃', other: 'Ctrl', name: 'Control' },
10
+ alt: { apple: '⌥', other: 'Alt', name: 'Option' },
11
+ shift: { apple: '⇧', other: 'Shift', name: 'Shift' },
12
+ enter: { apple: '↵', other: 'Enter', name: 'Enter' },
13
+ escape: { apple: 'Esc', other: 'Esc', name: 'Escape' },
14
+ esc: { apple: 'Esc', other: 'Esc', name: 'Escape' },
15
+ };
16
+
17
+ /** Whether shortcuts should use Apple modifier symbols (⌘, ⌥, ⇧). */
18
+ export function isApplePlatform(): boolean {
19
+ if (typeof navigator === 'undefined') return false;
20
+ const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
21
+ const platform = nav.userAgentData?.platform ?? navigator.platform ?? '';
22
+ return /mac|iphone|ipad|ipod/i.test(platform);
23
+ }
24
+
25
+ /** The text shown for one key, and the name read out for it. */
26
+ export function formatKey(
27
+ key: string,
28
+ apple = isApplePlatform()
29
+ ): { label: string; name: string } {
30
+ const known = SYMBOLS[key.toLowerCase()];
31
+ if (!known) return { label: key.length === 1 ? key.toUpperCase() : key, name: key };
32
+ const name = key.toLowerCase() === 'mod' && !apple ? 'Control' : known.name;
33
+ return { label: apple ? known.apple : known.other, name };
34
+ }
35
+ </script>
36
+
37
+ <script lang="ts">
38
+ import type { Snippet } from 'svelte';
39
+ import { cn } from '../utils.js';
40
+
41
+ interface Props {
42
+ /** Keys of a combination, e.g. ['Mod', 'K'] */
43
+ keys?: string[];
44
+ size?: 'sm' | 'md';
45
+ class?: string;
46
+ children?: Snippet;
47
+ }
48
+
49
+ let { keys, size = 'sm', class: className, children }: Props = $props();
50
+
51
+ // Resolved after mount so server and client render the same markup
52
+ let apple = $state(false);
53
+ $effect(() => {
54
+ apple = isApplePlatform();
55
+ });
56
+
57
+ const keyClass = $derived(
58
+ cn(
59
+ 'inline-flex items-center justify-center rounded border border-border bg-muted font-sans font-medium text-muted-foreground shadow-[inset_0_-1px_0_hsl(var(--border))]',
60
+ size === 'sm' ? 'h-5 min-w-5 px-1 text-[11px]' : 'h-6 min-w-6 px-1.5 text-xs'
61
+ )
62
+ );
63
+ const formatted = $derived((keys ?? []).map((k) => formatKey(k, apple)));
64
+ </script>
65
+
66
+ {#if keys?.length}
67
+ <span
68
+ class={cn('inline-flex items-center gap-0.5', className)}
69
+ aria-label={formatted.map((k) => k.name).join(' + ')}
70
+ role="img"
71
+ >
72
+ {#each formatted as key, i (i)}
73
+ <kbd class={keyClass} aria-hidden="true">{key.label}</kbd>
74
+ {/each}
75
+ </span>
76
+ {:else}
77
+ <kbd class={cn(keyClass, className)}
78
+ >{#if children}{@render children()}{/if}</kbd
79
+ >
80
+ {/if}
@@ -0,0 +1,22 @@
1
+ /** Whether shortcuts should use Apple modifier symbols (⌘, ⌥, ⇧). */
2
+ export declare function isApplePlatform(): boolean;
3
+ /** The text shown for one key, and the name read out for it. */
4
+ export declare function formatKey(key: string, apple?: boolean): {
5
+ label: string;
6
+ name: string;
7
+ };
8
+ import type { Snippet } from 'svelte';
9
+ interface Props {
10
+ /** Keys of a combination, e.g. ['Mod', 'K'] */
11
+ keys?: string[];
12
+ size?: 'sm' | 'md';
13
+ class?: string;
14
+ children?: Snippet;
15
+ }
16
+ /**
17
+ * A keyboard key or shortcut. Pass `keys` for a combination ("Mod" shows as
18
+ * ⌘ on Apple devices and Ctrl elsewhere), or `children` for a single key.
19
+ */
20
+ declare const Kbd: import("svelte").Component<Props, {}, "">;
21
+ type Kbd = ReturnType<typeof Kbd>;
22
+ export default Kbd;
@@ -0,0 +1,90 @@
1
+ <!--
2
+ @component
3
+ Floating content anchored to a trigger (Bits UI Popover). The `trigger`
4
+ snippet receives `{ props }`: spread them on your own focusable element
5
+ (`<Button {...props}>`). Without a trigger, anchor it with `anchor` (an
6
+ element or selector) and control it through `open`, e.g. a suggestion list
7
+ under a text field that must keep focus (`autoFocus={false}`).
8
+ -->
9
+ <script lang="ts">
10
+ import { Popover as PopoverPrimitive } from 'bits-ui';
11
+ import type { Snippet } from 'svelte';
12
+ import { cn } from '../utils.js';
13
+ import TriggerChild, { type TriggerSnippetProps } from './internal/TriggerChild.svelte';
14
+
15
+ interface Props {
16
+ /** Whether the popover is shown (bindable) */
17
+ open?: boolean;
18
+ onOpenChange?: (open: boolean) => void;
19
+ side?: 'top' | 'right' | 'bottom' | 'left';
20
+ align?: 'start' | 'center' | 'end';
21
+ sideOffset?: number;
22
+ /** Element (or selector) to position against instead of the trigger */
23
+ anchor?: HTMLElement | string | null;
24
+ /**
25
+ * Move focus into the content when it opens (default true). Set false to
26
+ * keep focus where it is, e.g. in a text field driving the popover.
27
+ */
28
+ autoFocus?: boolean;
29
+ /** Keep Tab inside the content while open (default false) */
30
+ trapFocus?: boolean;
31
+ /** Accessible name for the content when it has no visible heading */
32
+ 'aria-label'?: string;
33
+ class?: string;
34
+ trigger?: Snippet<[TriggerSnippetProps]>;
35
+ children: Snippet;
36
+ }
37
+
38
+ let {
39
+ open = $bindable(false),
40
+ onOpenChange,
41
+ side = 'bottom',
42
+ align = 'center',
43
+ sideOffset = 6,
44
+ anchor = null,
45
+ autoFocus = true,
46
+ trapFocus = false,
47
+ 'aria-label': ariaLabel,
48
+ class: className,
49
+ trigger,
50
+ children,
51
+ }: Props = $props();
52
+
53
+ const uid = $props.id();
54
+ let triggerId = $state(`${uid}-trigger`);
55
+ </script>
56
+
57
+ <PopoverPrimitive.Root bind:open {onOpenChange}>
58
+ {#if trigger}
59
+ <PopoverPrimitive.Trigger id={triggerId}>
60
+ {#snippet child({ props })}
61
+ <TriggerChild
62
+ {props}
63
+ {trigger}
64
+ fallbackClass="inline-flex"
65
+ onfallback={() => (triggerId = `${uid}-trigger-wrap`)}
66
+ />
67
+ {/snippet}
68
+ </PopoverPrimitive.Trigger>
69
+ {/if}
70
+ <PopoverPrimitive.Portal>
71
+ <PopoverPrimitive.Content
72
+ {side}
73
+ {align}
74
+ {sideOffset}
75
+ {trapFocus}
76
+ customAnchor={anchor}
77
+ collisionPadding={8}
78
+ aria-label={ariaLabel}
79
+ onOpenAutoFocus={(e) => {
80
+ if (!autoFocus) e.preventDefault();
81
+ }}
82
+ class={cn(
83
+ 'z-50 w-72 rounded-lg border border-border bg-popover p-3 text-sm text-popover-foreground shadow-md outline-none animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 motion-reduce:animate-none',
84
+ className
85
+ )}
86
+ >
87
+ {@render children()}
88
+ </PopoverPrimitive.Content>
89
+ </PopoverPrimitive.Portal>
90
+ </PopoverPrimitive.Root>
@@ -0,0 +1,34 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type TriggerSnippetProps } from './internal/TriggerChild.svelte';
3
+ interface Props {
4
+ /** Whether the popover is shown (bindable) */
5
+ open?: boolean;
6
+ onOpenChange?: (open: boolean) => void;
7
+ side?: 'top' | 'right' | 'bottom' | 'left';
8
+ align?: 'start' | 'center' | 'end';
9
+ sideOffset?: number;
10
+ /** Element (or selector) to position against instead of the trigger */
11
+ anchor?: HTMLElement | string | null;
12
+ /**
13
+ * Move focus into the content when it opens (default true). Set false to
14
+ * keep focus where it is, e.g. in a text field driving the popover.
15
+ */
16
+ autoFocus?: boolean;
17
+ /** Keep Tab inside the content while open (default false) */
18
+ trapFocus?: boolean;
19
+ /** Accessible name for the content when it has no visible heading */
20
+ 'aria-label'?: string;
21
+ class?: string;
22
+ trigger?: Snippet<[TriggerSnippetProps]>;
23
+ children: Snippet;
24
+ }
25
+ /**
26
+ * Floating content anchored to a trigger (Bits UI Popover). The `trigger`
27
+ * snippet receives `{ props }`: spread them on your own focusable element
28
+ * (`<Button {...props}>`). Without a trigger, anchor it with `anchor` (an
29
+ * element or selector) and control it through `open`, e.g. a suggestion list
30
+ * under a text field that must keep focus (`autoFocus={false}`).
31
+ */
32
+ declare const Popover: import("svelte").Component<Props, {}, "open">;
33
+ type Popover = ReturnType<typeof Popover>;
34
+ export default Popover;
@@ -0,0 +1,185 @@
1
+ <!--
2
+ @component
3
+ Two panes split by a draggable handle. `size` is the start pane's share in
4
+ percent; the end pane takes the rest. The handle is a focusable
5
+ `separator`: arrow keys move it by `step`, Home/End jump to `min`/`max`.
6
+ With `persistKey` the size is remembered in localStorage.
7
+ -->
8
+ <script lang="ts" module>
9
+ const STORAGE_PREFIX = 'classic-theme:resizable:';
10
+
11
+ export function clampSize(value: number, min: number, max: number): number {
12
+ return Math.min(Math.max(value, min), max);
13
+ }
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import type { Snippet } from 'svelte';
18
+ import { cn } from '../utils.js';
19
+
20
+ interface Props {
21
+ /** Start pane share in percent (bindable) */
22
+ size?: number;
23
+ /** Smallest start share in percent (default 20) */
24
+ min?: number;
25
+ /** Largest start share in percent (default 80) */
26
+ max?: number;
27
+ /** Keyboard step in percent (default 5) */
28
+ step?: number;
29
+ /** `horizontal`: side by side (default); `vertical`: stacked */
30
+ orientation?: 'horizontal' | 'vertical';
31
+ /** Remember the size under this key */
32
+ persistKey?: string;
33
+ /** Accessible name for the handle (default "Resize panes") */
34
+ handleLabel?: string;
35
+ onResize?: (size: number) => void;
36
+ class?: string;
37
+ startClass?: string;
38
+ endClass?: string;
39
+ start: Snippet;
40
+ end: Snippet;
41
+ }
42
+
43
+ let {
44
+ size = $bindable(50),
45
+ min = 20,
46
+ max = 80,
47
+ step = 5,
48
+ orientation = 'horizontal',
49
+ persistKey,
50
+ handleLabel = 'Resize panes',
51
+ onResize,
52
+ class: className,
53
+ startClass,
54
+ endClass,
55
+ start,
56
+ end,
57
+ }: Props = $props();
58
+
59
+ const uid = $props.id();
60
+ let container = $state<HTMLDivElement | null>(null);
61
+ let dragging = $state(false);
62
+ const horizontal = $derived(orientation === 'horizontal');
63
+
64
+ // Read the remembered size once, after mount (no storage during SSR)
65
+ $effect(() => {
66
+ if (!persistKey) return;
67
+ try {
68
+ const stored = Number(localStorage.getItem(STORAGE_PREFIX + persistKey));
69
+ if (Number.isFinite(stored) && stored > 0) size = clampSize(stored, min, max);
70
+ } catch {
71
+ // Storage blocked: keep the default size
72
+ }
73
+ });
74
+
75
+ function set(next: number, save = true) {
76
+ const value = Math.round(clampSize(next, min, max) * 10) / 10;
77
+ if (value === size) return;
78
+ size = value;
79
+ onResize?.(value);
80
+ if (save) persist();
81
+ }
82
+
83
+ function persist() {
84
+ if (!persistKey) return;
85
+ try {
86
+ localStorage.setItem(STORAGE_PREFIX + persistKey, String(size));
87
+ } catch {
88
+ // Storage blocked: the size just isn't remembered
89
+ }
90
+ }
91
+
92
+ function onkeydown(e: KeyboardEvent) {
93
+ const back = horizontal ? 'ArrowLeft' : 'ArrowUp';
94
+ const forward = horizontal ? 'ArrowRight' : 'ArrowDown';
95
+ if (e.key === back) set(size - step);
96
+ else if (e.key === forward) set(size + step);
97
+ else if (e.key === 'Home') set(min);
98
+ else if (e.key === 'End') set(max);
99
+ else return;
100
+ e.preventDefault();
101
+ }
102
+
103
+ function fromPointer(e: PointerEvent) {
104
+ if (!container) return;
105
+ const rect = container.getBoundingClientRect();
106
+ const total = horizontal ? rect.width : rect.height;
107
+ if (total <= 0) return;
108
+ const offset = horizontal ? e.clientX - rect.left : e.clientY - rect.top;
109
+ set((offset / total) * 100, false);
110
+ }
111
+
112
+ function onpointerdown(e: PointerEvent) {
113
+ if (e.button !== 0) return;
114
+ e.preventDefault();
115
+ dragging = true;
116
+ (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
117
+ }
118
+
119
+ function onpointermove(e: PointerEvent) {
120
+ if (dragging) fromPointer(e);
121
+ }
122
+
123
+ function onpointerup(e: PointerEvent) {
124
+ if (!dragging) return;
125
+ dragging = false;
126
+ (e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId);
127
+ persist();
128
+ }
129
+ </script>
130
+
131
+ <div
132
+ bind:this={container}
133
+ class={cn('flex h-full min-h-0 w-full min-w-0', horizontal ? 'flex-row' : 'flex-col', className)}
134
+ data-dragging={dragging || undefined}
135
+ >
136
+ <div
137
+ id={`${uid}-start`}
138
+ class={cn('min-h-0 min-w-0 shrink-0 overflow-hidden', startClass)}
139
+ style={`flex-basis:${size}%`}
140
+ >
141
+ {@render start()}
142
+ </div>
143
+ <!-- A focusable separator is an interactive widget (WAI-ARIA window splitter) -->
144
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
145
+ <div
146
+ role="separator"
147
+ tabindex="0"
148
+ aria-label={handleLabel}
149
+ aria-orientation={horizontal ? 'vertical' : 'horizontal'}
150
+ aria-controls={`${uid}-start`}
151
+ aria-valuenow={size}
152
+ aria-valuemin={min}
153
+ aria-valuemax={max}
154
+ class={cn(
155
+ 'group relative z-10 flex shrink-0 touch-none select-none items-center justify-center bg-border outline-none transition-colors hover:bg-primary/40 focus-visible:bg-primary data-[dragging]:bg-primary',
156
+ horizontal ? 'w-px cursor-col-resize' : 'h-px cursor-row-resize'
157
+ )}
158
+ data-dragging={dragging || undefined}
159
+ {onkeydown}
160
+ {onpointerdown}
161
+ {onpointermove}
162
+ {onpointerup}
163
+ onpointercancel={onpointerup}
164
+ >
165
+ <!-- Wider invisible hit area around the 1px line -->
166
+ <span
167
+ class={cn(
168
+ 'absolute',
169
+ horizontal ? 'inset-y-0 -left-1.5 -right-1.5' : 'inset-x-0 -top-1.5 -bottom-1.5'
170
+ )}
171
+ aria-hidden="true"
172
+ ></span>
173
+ </div>
174
+ <div class={cn('min-h-0 min-w-0 flex-1 overflow-hidden', endClass)}>
175
+ {@render end()}
176
+ </div>
177
+ </div>
178
+
179
+ {#if dragging}
180
+ <!-- Keeps the resize cursor and stops iframes in the panes from swallowing pointer moves -->
181
+ <div
182
+ class={cn('fixed inset-0 z-50', horizontal ? 'cursor-col-resize' : 'cursor-row-resize')}
183
+ aria-hidden="true"
184
+ ></div>
185
+ {/if}
@@ -0,0 +1,33 @@
1
+ export declare function clampSize(value: number, min: number, max: number): number;
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ /** Start pane share in percent (bindable) */
5
+ size?: number;
6
+ /** Smallest start share in percent (default 20) */
7
+ min?: number;
8
+ /** Largest start share in percent (default 80) */
9
+ max?: number;
10
+ /** Keyboard step in percent (default 5) */
11
+ step?: number;
12
+ /** `horizontal`: side by side (default); `vertical`: stacked */
13
+ orientation?: 'horizontal' | 'vertical';
14
+ /** Remember the size under this key */
15
+ persistKey?: string;
16
+ /** Accessible name for the handle (default "Resize panes") */
17
+ handleLabel?: string;
18
+ onResize?: (size: number) => void;
19
+ class?: string;
20
+ startClass?: string;
21
+ endClass?: string;
22
+ start: Snippet;
23
+ end: Snippet;
24
+ }
25
+ /**
26
+ * Two panes split by a draggable handle. `size` is the start pane's share in
27
+ * percent; the end pane takes the rest. The handle is a focusable
28
+ * `separator`: arrow keys move it by `step`, Home/End jump to `min`/`max`.
29
+ * With `persistKey` the size is remembered in localStorage.
30
+ */
31
+ declare const ResizablePanes: import("svelte").Component<Props, {}, "size">;
32
+ type ResizablePanes = ReturnType<typeof ResizablePanes>;
33
+ export default ResizablePanes;
@@ -51,6 +51,8 @@
51
51
  * renders one once there are at least that many options.
52
52
  */
53
53
  searchable?: boolean | number;
54
+ /** With `searchable`: render at most this many matches (see Combobox) */
55
+ maxResults?: number;
54
56
  }
55
57
 
56
58
  let {
@@ -69,6 +71,7 @@
69
71
  'aria-label': ariaLabelProp,
70
72
  'aria-labelledby': ariaLabelledby,
71
73
  searchable = false,
74
+ maxResults,
72
75
  }: Props = $props();
73
76
 
74
77
  // Generate unique IDs for accessibility using sequential counter
@@ -291,6 +294,7 @@
291
294
  {id}
292
295
  {error}
293
296
  {hint}
297
+ {maxResults}
294
298
  aria-label={ariaLabelProp}
295
299
  aria-labelledby={ariaLabelledby}
296
300
  class={className}
@@ -41,6 +41,8 @@ interface Props {
41
41
  * renders one once there are at least that many options.
42
42
  */
43
43
  searchable?: boolean | number;
44
+ /** With `searchable`: render at most this many matches (see Combobox) */
45
+ maxResults?: number;
44
46
  }
45
47
  declare const Select: import("svelte").Component<Props, {}, "value">;
46
48
  type Select = ReturnType<typeof Select>;
@@ -20,6 +20,17 @@
20
20
  /** Whether the textarea has an error */
21
21
  'aria-invalid'?: boolean | 'true' | 'false';
22
22
  onValueChange?: (value: string) => void;
23
+ /** Native input listener; runs after the value is updated */
24
+ oninput?: (e: Event & { currentTarget: HTMLTextAreaElement }) => void;
25
+ /**
26
+ * Grow with the content instead of showing a resize handle. `rows` is the
27
+ * minimum height; past `maxRows` the textarea scrolls.
28
+ */
29
+ autosize?: boolean;
30
+ /** Upper bound for `autosize`, in rows (default 8) */
31
+ maxRows?: number;
32
+ /** The textarea element (bindable), e.g. to focus it */
33
+ ref?: HTMLTextAreaElement | null;
23
34
  [key: string]: unknown;
24
35
  }
25
36
 
@@ -38,17 +49,50 @@
38
49
  'aria-describedby': ariaDescribedby,
39
50
  'aria-invalid': ariaInvalid,
40
51
  onValueChange,
52
+ oninput,
53
+ autosize = false,
54
+ maxRows = 8,
55
+ ref = $bindable(null),
41
56
  ...restProps
42
57
  }: Props = $props();
43
58
 
59
+ /**
60
+ * Fit the height to the content: measure at `auto`, then clamp between
61
+ * `rows` and `maxRows` lines (plus padding and border, read from the
62
+ * computed style so consumer classes are respected).
63
+ */
64
+ function fit(el: HTMLTextAreaElement) {
65
+ const style = getComputedStyle(el);
66
+ const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.5 || 20;
67
+ const chrome =
68
+ parseFloat(style.paddingTop) +
69
+ parseFloat(style.paddingBottom) +
70
+ parseFloat(style.borderTopWidth) +
71
+ parseFloat(style.borderBottomWidth);
72
+ const min = rows * lineHeight + chrome;
73
+ const max = Math.max(rows, maxRows) * lineHeight + chrome;
74
+ el.style.height = 'auto';
75
+ const next = Math.min(Math.max(el.scrollHeight, min), max);
76
+ el.style.height = `${next}px`;
77
+ el.style.overflowY = el.scrollHeight > max ? 'auto' : 'hidden';
78
+ }
79
+
80
+ $effect(() => {
81
+ // Re-fit when the value changes from outside too (cleared after send, prefilled)
82
+ void value;
83
+ if (autosize && ref) fit(ref);
84
+ });
85
+
44
86
  function handleInput(e: Event) {
45
87
  const target = e.target as HTMLTextAreaElement;
46
88
  value = target.value;
47
89
  onValueChange?.(target.value);
90
+ oninput?.(e as Event & { currentTarget: HTMLTextAreaElement });
48
91
  }
49
92
  </script>
50
93
 
51
94
  <textarea
95
+ bind:this={ref}
52
96
  {value}
53
97
  {placeholder}
54
98
  {disabled}
@@ -63,6 +107,7 @@
63
107
  aria-invalid={ariaInvalid}
64
108
  class={cn(
65
109
  'flex min-h-[80px] w-full rounded-md border border-input bg-background text-foreground px-3 py-2 text-base sm:text-sm ring-offset-background placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-y',
110
+ autosize && 'min-h-0 resize-none',
66
111
  ariaInvalid === true || ariaInvalid === 'true'
67
112
  ? 'border-destructive focus-visible:ring-destructive'
68
113
  : '',
@@ -17,8 +17,21 @@ interface Props {
17
17
  /** Whether the textarea has an error */
18
18
  'aria-invalid'?: boolean | 'true' | 'false';
19
19
  onValueChange?: (value: string) => void;
20
+ /** Native input listener; runs after the value is updated */
21
+ oninput?: (e: Event & {
22
+ currentTarget: HTMLTextAreaElement;
23
+ }) => void;
24
+ /**
25
+ * Grow with the content instead of showing a resize handle. `rows` is the
26
+ * minimum height; past `maxRows` the textarea scrolls.
27
+ */
28
+ autosize?: boolean;
29
+ /** Upper bound for `autosize`, in rows (default 8) */
30
+ maxRows?: number;
31
+ /** The textarea element (bindable), e.g. to focus it */
32
+ ref?: HTMLTextAreaElement | null;
20
33
  [key: string]: unknown;
21
34
  }
22
- declare const Textarea: import("svelte").Component<Props, {}, "value">;
35
+ declare const Textarea: import("svelte").Component<Props, {}, "value" | "ref">;
23
36
  type Textarea = ReturnType<typeof Textarea>;
24
37
  export default Textarea;
@@ -17,6 +17,8 @@ export { default as Checkbox } from './components/Checkbox.svelte';
17
17
  export { default as Switch } from './components/Switch.svelte';
18
18
  export { default as Separator } from './components/Separator.svelte';
19
19
  export { default as Icon, type IconName } from './components/Icon.svelte';
20
+ export { default as CopyButton } from './components/CopyButton.svelte';
21
+ export { default as Kbd, formatKey, isApplePlatform } from './components/Kbd.svelte';
20
22
  export { default as Card } from './components/Card.svelte';
21
23
  export { default as CardHeader } from './components/CardHeader.svelte';
22
24
  export { default as CardTitle } from './components/CardTitle.svelte';
@@ -35,14 +37,19 @@ export { default as ProgressBar } from './components/ProgressBar.svelte';
35
37
  export { default as LoadingOverlay } from './components/LoadingOverlay.svelte';
36
38
  export { default as LoadingPage } from './components/LoadingPage.svelte';
37
39
  export { default as LoadingSection } from './components/LoadingSection.svelte';
40
+ export { default as EmptyState } from './components/EmptyState.svelte';
38
41
  export { default as LiveRegion } from './components/LiveRegion.svelte';
39
42
  export { default as SkipLink } from './components/SkipLink.svelte';
40
43
  export { default as Dialog } from './components/Dialog.svelte';
41
44
  export { default as AlertDialog } from './components/AlertDialog.svelte';
42
45
  export { default as Tooltip } from './components/Tooltip.svelte';
46
+ export { default as Popover } from './components/Popover.svelte';
47
+ export { default as Collapsible } from './components/Collapsible.svelte';
43
48
  export { default as DropdownMenu } from './components/DropdownMenu.svelte';
49
+ export { default as ContextMenu } from './components/ContextMenu.svelte';
44
50
  export { default as AppLauncher, type AppLauncherItem } from './components/AppLauncher.svelte';
45
51
  export { default as AppLauncherGrid } from './components/AppLauncherGrid.svelte';
52
+ export { default as ResizablePanes, clampSize } from './components/ResizablePanes.svelte';
46
53
  export { default as DataPanel } from './components/DataPanel.svelte';
47
54
  export { default as DataPanelHeader } from './components/DataPanelHeader.svelte';
48
55
  export { default as DataPanelContent } from './components/DataPanelContent.svelte';
package/dist/lib/index.js CHANGED
@@ -18,6 +18,8 @@ export { default as Checkbox } from './components/Checkbox.svelte';
18
18
  export { default as Switch } from './components/Switch.svelte';
19
19
  export { default as Separator } from './components/Separator.svelte';
20
20
  export { default as Icon } from './components/Icon.svelte';
21
+ export { default as CopyButton } from './components/CopyButton.svelte';
22
+ export { default as Kbd, formatKey, isApplePlatform } from './components/Kbd.svelte';
21
23
  // Card Components
22
24
  export { default as Card } from './components/Card.svelte';
23
25
  export { default as CardHeader } from './components/CardHeader.svelte';
@@ -41,6 +43,7 @@ export { default as ProgressBar } from './components/ProgressBar.svelte';
41
43
  export { default as LoadingOverlay } from './components/LoadingOverlay.svelte';
42
44
  export { default as LoadingPage } from './components/LoadingPage.svelte';
43
45
  export { default as LoadingSection } from './components/LoadingSection.svelte';
46
+ export { default as EmptyState } from './components/EmptyState.svelte';
44
47
  // Accessibility Components
45
48
  export { default as LiveRegion } from './components/LiveRegion.svelte';
46
49
  export { default as SkipLink } from './components/SkipLink.svelte';
@@ -48,9 +51,14 @@ export { default as SkipLink } from './components/SkipLink.svelte';
48
51
  export { default as Dialog } from './components/Dialog.svelte';
49
52
  export { default as AlertDialog } from './components/AlertDialog.svelte';
50
53
  export { default as Tooltip } from './components/Tooltip.svelte';
54
+ export { default as Popover } from './components/Popover.svelte';
55
+ export { default as Collapsible } from './components/Collapsible.svelte';
51
56
  export { default as DropdownMenu } from './components/DropdownMenu.svelte';
57
+ export { default as ContextMenu } from './components/ContextMenu.svelte';
52
58
  export { default as AppLauncher } from './components/AppLauncher.svelte';
53
59
  export { default as AppLauncherGrid } from './components/AppLauncherGrid.svelte';
60
+ // Layout primitives
61
+ export { default as ResizablePanes, clampSize } from './components/ResizablePanes.svelte';
54
62
  // DataPanel Components
55
63
  export { default as DataPanel } from './components/DataPanel.svelte';
56
64
  export { default as DataPanelHeader } from './components/DataPanelHeader.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-svelte",
3
- "version": "1.37.0",
3
+ "version": "1.39.0",
4
4
  "description": "Svelte components for the Classic theme system",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",