@dorsk/tsumikit 0.56.0 → 0.58.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 (51) hide show
  1. package/README.md +125 -10
  2. package/dist/avatar.d.ts +6 -0
  3. package/dist/avatar.js +17 -0
  4. package/dist/components/atoms/Avatar.svelte +140 -0
  5. package/dist/components/atoms/Avatar.svelte.d.ts +25 -0
  6. package/dist/components/atoms/Badge.svelte +30 -8
  7. package/dist/components/atoms/Badge.svelte.d.ts +7 -0
  8. package/dist/components/atoms/Button.svelte +72 -1
  9. package/dist/components/atoms/Button.svelte.d.ts +2 -0
  10. package/dist/components/atoms/Input.svelte +30 -5
  11. package/dist/components/atoms/Select.svelte +11 -1
  12. package/dist/components/atoms/Swatch.svelte +114 -0
  13. package/dist/components/atoms/Swatch.svelte.d.ts +23 -0
  14. package/dist/components/atoms/Textarea.svelte +41 -7
  15. package/dist/components/molecules/Accordion.svelte +80 -62
  16. package/dist/components/molecules/Accordion.svelte.d.ts +15 -2
  17. package/dist/components/molecules/Combobox.svelte +299 -0
  18. package/dist/components/molecules/Combobox.svelte.d.ts +108 -0
  19. package/dist/components/molecules/Composer.svelte +32 -29
  20. package/dist/components/molecules/Composer.svelte.d.ts +2 -0
  21. package/dist/components/molecules/Disclosure.svelte +155 -0
  22. package/dist/components/molecules/Disclosure.svelte.d.ts +26 -0
  23. package/dist/components/molecules/IconButton.svelte +8 -0
  24. package/dist/components/molecules/IconButton.svelte.d.ts +4 -0
  25. package/dist/components/molecules/InputGroup.svelte +159 -0
  26. package/dist/components/molecules/InputGroup.svelte.d.ts +23 -0
  27. package/dist/components/molecules/Menu.svelte +26 -4
  28. package/dist/components/molecules/Menu.svelte.d.ts +9 -1
  29. package/dist/components/molecules/OptionButton.svelte +42 -1
  30. package/dist/components/molecules/OptionButton.svelte.d.ts +8 -1
  31. package/dist/components/molecules/Popover.svelte +49 -1
  32. package/dist/components/molecules/Popover.svelte.d.ts +4 -0
  33. package/dist/components/molecules/RadioGroup.svelte +59 -7
  34. package/dist/components/molecules/RadioGroup.svelte.d.ts +4 -0
  35. package/dist/components/molecules/SplitButton.svelte +181 -0
  36. package/dist/components/molecules/SplitButton.svelte.d.ts +41 -0
  37. package/dist/components/molecules/Tabs.svelte +155 -27
  38. package/dist/components/molecules/Tabs.svelte.d.ts +16 -1
  39. package/dist/components/molecules/combobox-keyboard.d.ts +100 -0
  40. package/dist/components/molecules/combobox-keyboard.js +170 -0
  41. package/dist/components/molecules/menu-item.d.ts +11 -0
  42. package/dist/components/molecules/menu-item.js +9 -0
  43. package/dist/count.d.ts +2 -0
  44. package/dist/count.js +7 -0
  45. package/dist/floating.d.ts +4 -0
  46. package/dist/floating.js +4 -1
  47. package/dist/index.d.ts +9 -0
  48. package/dist/index.js +9 -0
  49. package/dist/input-group-context.d.ts +10 -0
  50. package/dist/input-group-context.js +8 -0
  51. package/package.json +1 -1
@@ -8,6 +8,7 @@
8
8
  import type { HTMLInputAttributes } from 'svelte/elements';
9
9
  import Icon, { type IconName } from './Icon.svelte';
10
10
  import { getFieldContext, warnUnlabelled } from '../../field-context';
11
+ import { getInputGroupContext } from '../../input-group-context';
11
12
 
12
13
  // `size` shadows the native char-width attribute (unused in token-sized
13
14
  // layouts) to expose a height preset instead.
@@ -43,9 +44,10 @@
43
44
 
44
45
  let {
45
46
  mono = false,
46
- size = 'md',
47
+ size,
47
48
  grow = false,
48
49
  invalid = false,
50
+ disabled = false,
49
51
  icon,
50
52
  clearable = false,
51
53
  onclear,
@@ -65,7 +67,10 @@
65
67
  }: Props = $props();
66
68
 
67
69
  const field = getFieldContext();
68
- const isInvalid = $derived(invalid || !!field?.invalid);
70
+ const group = getInputGroupContext();
71
+ const sizeEff = $derived(size ?? group?.size ?? 'md');
72
+ const isInvalid = $derived(invalid || !!field?.invalid || !!group?.invalid);
73
+ const isDisabled = $derived(disabled || !!group?.disabled);
69
74
 
70
75
  $effect(() => warnUnlabelled(el, 'Input'));
71
76
 
@@ -90,8 +95,9 @@
90
95
  data-tsu="Input"
91
96
  class="input {klass}"
92
97
  class:mono
93
- class:input-sm={size === 'sm'}
94
- class:input-lg={size === 'lg'}
98
+ class:input-sm={sizeEff === 'sm'}
99
+ class:input-lg={sizeEff === 'lg'}
100
+ class:grouped={!!group}
95
101
  class:input-grow={grow}
96
102
  class:input-fixed={!!width && !wrapped}
97
103
  class:input-pill={shape === 'pill'}
@@ -100,6 +106,7 @@
100
106
  style:width={wrapped ? undefined : width}
101
107
  bind:value
102
108
  {...rest}
109
+ disabled={isDisabled}
103
110
  onkeydown={onenter || onsubmit || onkeydown ? handleKeydown : undefined}
104
111
  id={id ?? field?.id}
105
112
  aria-describedby={ariaDescribedby ?? field?.describedBy}
@@ -155,7 +162,14 @@
155
162
  .input-lg {
156
163
  min-height: var(--input-size, var(--control-height-large));
157
164
  padding: var(--sp-3) var(--sp-4);
158
- font-size: var(--fs-base);
165
+ font-size: max(16px, var(--fs-md));
166
+ }
167
+ /* iOS auto-zooms any field under 16px on focus; on touch the size modifiers
168
+ carry their difference through padding and height alone. */
169
+ @media (pointer: coarse) {
170
+ .input-sm {
171
+ font-size: max(16px, var(--fs-sm));
172
+ }
159
173
  }
160
174
  .input-grow {
161
175
  flex: 1 1 0;
@@ -175,6 +189,17 @@
175
189
  .input[aria-invalid='true']:focus {
176
190
  border-color: var(--danger);
177
191
  }
192
+ /* Inside an InputGroup the group draws the focus ring and the overlaid
193
+ adornments report their widths; the text stays clear of them. */
194
+ .input.grouped {
195
+ width: 100%;
196
+ border-radius: var(--ig-radius);
197
+ padding-inline: max(var(--ig-pad-x), var(--ig-leading-w, 0px) + var(--ig-gap))
198
+ max(var(--ig-pad-x), var(--ig-trailing-w, 0px) + var(--ig-gap));
199
+ }
200
+ .input.grouped:focus-visible {
201
+ outline: none;
202
+ }
178
203
  .mono {
179
204
  font-family: var(--font-mono);
180
205
  }
@@ -228,7 +228,7 @@
228
228
  .select.select-lg {
229
229
  min-height: var(--select-size, var(--control-height-large));
230
230
  padding: var(--sp-3) var(--sp-4);
231
- font-size: var(--fs-base);
231
+ font-size: max(16px, var(--fs-md));
232
232
  }
233
233
  .select.compact {
234
234
  padding: var(--sp-1) var(--sp-2);
@@ -296,6 +296,16 @@
296
296
  .select-wrap.no-chevron .select-face.compact {
297
297
  padding-right: var(--sp-2);
298
298
  }
299
+ /* iOS auto-zooms any field under 16px on focus; on touch the size modifiers
300
+ carry their difference through padding and height alone. */
301
+ @media (pointer: coarse) {
302
+ .select.compact {
303
+ font-size: max(16px, var(--fs-xs));
304
+ }
305
+ .select-face.compact {
306
+ font-size: max(16px, var(--fs-xs));
307
+ }
308
+ }
299
309
  .select:disabled ~ .select-face {
300
310
  color: var(--text-muted);
301
311
  }
@@ -0,0 +1,114 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import type { ControlSize } from '../../size';
5
+ // Colour-swatch primitive: a filled chip that shows a colour. With a `label`
6
+ // it is an image (role=img + aria-label); without one it is decorative
7
+ // (aria-hidden). `interactive` renders a <button> so a palette picker can
8
+ // build a row of swatches with `selected` mirrored to aria-pressed. The
9
+ // hairline token border keeps pale colours visible on light themes.
10
+
11
+ type Own = {
12
+ /** Any CSS colour (or var()). */
13
+ color: string;
14
+ size?: ControlSize;
15
+ shape?: 'circle' | 'square';
16
+ /** Accessible name; without it the swatch is hidden from assistive tech. */
17
+ label?: string;
18
+ /** Draws the selection ring; `aria-pressed` when interactive. */
19
+ selected?: boolean;
20
+ /** Renders a `<button>` and wires `onclick`. */
21
+ interactive?: boolean;
22
+ onclick?: (e: MouseEvent) => void;
23
+ class?: string;
24
+ /** Optional glyph drawn over the colour (e.g. an "A" for auto). */
25
+ children?: Snippet;
26
+ };
27
+ let {
28
+ color,
29
+ size = 'md',
30
+ shape = 'circle',
31
+ label,
32
+ selected = false,
33
+ interactive = false,
34
+ onclick,
35
+ class: klass = '',
36
+ children,
37
+ ...rest
38
+ }: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
39
+ </script>
40
+
41
+ <svelte:element
42
+ this={interactive ? 'button' : 'span'}
43
+ type={interactive ? 'button' : undefined}
44
+ data-tsu="Swatch"
45
+ class="swatch {klass}"
46
+ class:sm={size === 'sm'}
47
+ class:lg={size === 'lg'}
48
+ class:square={shape === 'square'}
49
+ class:selected
50
+ class:interactive
51
+ style:--swatch-color={color}
52
+ role={!interactive && label ? 'img' : undefined}
53
+ aria-label={interactive ? (label ?? color) : label}
54
+ aria-hidden={!interactive && !label ? 'true' : undefined}
55
+ aria-pressed={interactive ? selected : undefined}
56
+ onclick={interactive ? onclick : undefined}
57
+ {...rest}
58
+ >
59
+ {@render children?.()}
60
+ </svelte:element>
61
+
62
+ <style>
63
+ .swatch {
64
+ --swatch-size: 1rem;
65
+ display: inline-flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ flex: none;
69
+ width: var(--swatch-size);
70
+ height: var(--swatch-size);
71
+ padding: 0;
72
+ margin: 0;
73
+ border: 1px solid var(--border);
74
+ border-radius: var(--r-pill);
75
+ background: var(--swatch-color);
76
+ color: var(--text);
77
+ font: inherit;
78
+ font-size: calc(var(--swatch-size) * 0.6);
79
+ line-height: 1;
80
+ vertical-align: middle;
81
+ box-sizing: border-box;
82
+ }
83
+ .sm {
84
+ --swatch-size: 0.75rem;
85
+ }
86
+ .lg {
87
+ --swatch-size: 1.5rem;
88
+ }
89
+ .square {
90
+ border-radius: var(--r-sm);
91
+ }
92
+ .sm.square {
93
+ border-radius: 3px;
94
+ }
95
+ .selected {
96
+ border-color: var(--text);
97
+ box-shadow:
98
+ 0 0 0 2px var(--bg),
99
+ 0 0 0 3px var(--text);
100
+ }
101
+ .interactive {
102
+ cursor: pointer;
103
+ transition:
104
+ transform 0.1s var(--ease),
105
+ box-shadow 0.12s var(--ease);
106
+ }
107
+ .interactive:hover {
108
+ transform: scale(1.15);
109
+ }
110
+ .interactive:focus-visible {
111
+ outline: 2px solid var(--accent);
112
+ outline-offset: 3px;
113
+ }
114
+ </style>
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ControlSize } from '../../size';
4
+ type Own = {
5
+ /** Any CSS colour (or var()). */
6
+ color: string;
7
+ size?: ControlSize;
8
+ shape?: 'circle' | 'square';
9
+ /** Accessible name; without it the swatch is hidden from assistive tech. */
10
+ label?: string;
11
+ /** Draws the selection ring; `aria-pressed` when interactive. */
12
+ selected?: boolean;
13
+ /** Renders a `<button>` and wires `onclick`. */
14
+ interactive?: boolean;
15
+ onclick?: (e: MouseEvent) => void;
16
+ class?: string;
17
+ /** Optional glyph drawn over the colour (e.g. an "A" for auto). */
18
+ children?: Snippet;
19
+ };
20
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
21
+ declare const Swatch: import("svelte").Component<$$ComponentProps, {}, "">;
22
+ type Swatch = ReturnType<typeof Swatch>;
23
+ export default Swatch;
@@ -21,6 +21,7 @@
21
21
  import type { HTMLTextareaAttributes } from 'svelte/elements';
22
22
  import { autoresize as autoresizeAction } from '../../autoresize';
23
23
  import { getFieldContext, warnUnlabelled } from '../../field-context';
24
+ import { getInputGroupContext } from '../../input-group-context';
24
25
 
25
26
  type Props = HTMLTextareaAttributes & {
26
27
  mono?: boolean;
@@ -55,12 +56,13 @@
55
56
  let {
56
57
  mono = false,
57
58
  autoresize = false,
58
- size = 'md',
59
+ size,
59
60
  grow = false,
60
61
  shrink = true,
61
62
  block = false,
62
63
  resize = 'bottom',
63
64
  invalid = false,
65
+ disabled = false,
64
66
  maxHeight,
65
67
  onsubmit,
66
68
  submitOn = 'none',
@@ -75,7 +77,10 @@
75
77
  }: Props = $props();
76
78
 
77
79
  const field = getFieldContext();
78
- const isInvalid = $derived(invalid || !!field?.invalid);
80
+ const group = getInputGroupContext();
81
+ const sizeEff = $derived(size ?? group?.size ?? 'md');
82
+ const isInvalid = $derived(invalid || !!field?.invalid || !!group?.invalid);
83
+ const isDisabled = $derived(disabled || !!group?.disabled);
79
84
 
80
85
  $effect(() => warnUnlabelled(el, 'Textarea'));
81
86
 
@@ -150,6 +155,7 @@
150
155
  class:grow={grow}
151
156
  class:no-shrink={!shrink}
152
157
  class:block={block}
158
+ class:grouped={!!group}
153
159
  data-tsu="Textarea"
154
160
  >
155
161
  {#if autoresize}
@@ -157,9 +163,11 @@
157
163
  bind:this={el}
158
164
  class="textarea {klass}"
159
165
  class:mono
160
- class:textarea-sm={size === 'sm'}
161
- class:textarea-lg={size === 'lg'}
166
+ class:textarea-sm={sizeEff === 'sm'}
167
+ class:textarea-lg={sizeEff === 'lg'}
168
+ class:grouped={!!group}
162
169
  class:capped={!!maxHeight}
170
+ disabled={isDisabled}
163
171
  style:max-height={maxHeight}
164
172
  bind:value
165
173
  use:autoresizeAction={typeof value === 'string' ? value : ''}
@@ -174,9 +182,11 @@
174
182
  bind:this={el}
175
183
  class="textarea {klass}"
176
184
  class:mono
177
- class:textarea-sm={size === 'sm'}
178
- class:textarea-lg={size === 'lg'}
185
+ class:textarea-sm={sizeEff === 'sm'}
186
+ class:textarea-lg={sizeEff === 'lg'}
187
+ class:grouped={!!group}
179
188
  class:capped={!!maxHeight}
189
+ disabled={isDisabled}
180
190
  style:max-height={maxHeight}
181
191
  bind:value
182
192
  {...rest}
@@ -254,13 +264,37 @@
254
264
  }
255
265
  .textarea-lg {
256
266
  padding: var(--sp-3) var(--sp-4);
257
- font-size: var(--fs-base);
267
+ font-size: max(16px, var(--fs-md));
258
268
  min-height: var(--textarea-size, var(--control-height-large));
259
269
  }
270
+ /* iOS auto-zooms any field under 16px on focus; on touch the size modifiers
271
+ carry their difference through padding and height alone. */
272
+ @media (pointer: coarse) {
273
+ .textarea-sm {
274
+ font-size: max(16px, var(--fs-sm));
275
+ }
276
+ }
260
277
  .textarea[aria-invalid='true'],
261
278
  .textarea[aria-invalid='true']:focus {
262
279
  border-color: var(--danger);
263
280
  }
281
+ /* Inside an InputGroup the group draws the focus ring and the overlaid
282
+ adornments report their widths; the text stays clear of them. */
283
+ .textarea.grouped {
284
+ border-radius: var(--ig-radius);
285
+ padding-inline: max(var(--ig-pad-x), var(--ig-leading-w, 0px) + var(--ig-gap))
286
+ max(var(--ig-pad-x), var(--ig-trailing-w, 0px) + var(--ig-gap));
287
+ }
288
+ .textarea.grouped:focus-visible {
289
+ outline: none;
290
+ }
291
+ .textarea-wrap.grouped {
292
+ width: 100%;
293
+ }
294
+ .textarea-wrap.grouped .resize-handle {
295
+ left: var(--ig-leading-w, 0px);
296
+ right: var(--ig-trailing-w, 0px);
297
+ }
264
298
  .mono {
265
299
  font-family: var(--font-mono);
266
300
  }
@@ -1,46 +1,99 @@
1
1
  <script lang="ts" module>
2
2
  import type { Snippet } from 'svelte';
3
+ import type { DisclosureHeaderContext } from './Disclosure.svelte';
3
4
  export interface AccordionItem {
4
5
  id: string;
5
- title: string;
6
+ /** Plain-text header; `summary` wins when both are given. */
7
+ title?: string;
8
+ /** Rich header content; receives the live open state. */
9
+ summary?: Snippet<[DisclosureHeaderContext]>;
6
10
  /** Panel content for this item. */
7
11
  content: Snippet;
12
+ /** Open state. Re-passing a new value takes over from any local toggle. */
8
13
  open?: boolean;
14
+ onchange?: (open: boolean) => void;
15
+ disabled?: boolean;
9
16
  }
10
17
  </script>
11
18
 
12
19
  <script lang="ts">
13
- // Disclosure accordion built on native <details>/<summary> — zero JS for the
14
- // open/close, full keyboard + a11y for free. When `multiple` is false the
15
- // items share a `name`, using the platform's exclusive-accordion behavior
16
- // (only one open at a time). The summary marker is replaced with a rotating
17
- // chevron.
18
- import Icon from '../atoms/Icon.svelte';
20
+ // A stack of Disclosures. Each item's `open` is the parent's word: local
21
+ // toggles override it until the parent passes a new value. `multiple=false`
22
+ // closes the other items when one opens.
23
+ import type { HTMLAttributes } from 'svelte/elements';
24
+ import Disclosure from './Disclosure.svelte';
25
+ import type { DisclosureChevron } from './Disclosure.svelte';
19
26
 
20
27
  let {
21
28
  items,
22
29
  multiple = true,
23
- class: klass = ''
24
- }: {
30
+ chevron = 'end',
31
+ onchange,
32
+ class: klass = '',
33
+ style: styleProp = '',
34
+ ...rest
35
+ }: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
36
+
37
+ type Own = {
25
38
  items: AccordionItem[];
26
39
  multiple?: boolean;
40
+ chevron?: DisclosureChevron;
41
+ onchange?: (id: string, open: boolean) => void;
27
42
  class?: string;
28
- } = $props();
43
+ style?: string;
44
+ };
45
+
46
+ let local = $state<Record<string, boolean>>({});
47
+ let seen: Record<string, boolean | undefined> = {};
48
+
49
+ $effect.pre(() => {
50
+ for (const item of items) {
51
+ if (seen[item.id] !== item.open) {
52
+ seen[item.id] = item.open;
53
+ delete local[item.id];
54
+ }
55
+ }
56
+ });
57
+
58
+ const isOpen = (item: AccordionItem) => local[item.id] ?? item.open ?? false;
29
59
 
30
- // One shared name → native single-open accordion (stable id, derived toggle).
31
- const gid = `acc-${Math.random().toString(36).slice(2, 8)}`;
32
- const groupName = $derived(multiple ? undefined : gid);
60
+ function set(item: AccordionItem, open: boolean) {
61
+ local[item.id] = open;
62
+ item.onchange?.(open);
63
+ onchange?.(item.id, open);
64
+ if (!open || multiple) return;
65
+ for (const other of items) {
66
+ if (other.id === item.id || !isOpen(other)) continue;
67
+ local[other.id] = false;
68
+ other.onchange?.(false);
69
+ onchange?.(other.id, false);
70
+ }
71
+ }
33
72
  </script>
34
73
 
35
- <div class="accordion {klass}" data-tsu="Accordion">
74
+ {#snippet plain(title: string | undefined)}
75
+ <span class="acc-title">{title}</span>
76
+ {/snippet}
77
+
78
+ <div {...rest} class="accordion {klass}" style={styleProp} data-tsu="Accordion">
36
79
  {#each items as item (item.id)}
37
- <details name={groupName} open={item.open}>
38
- <summary>
39
- <span class="acc-title">{item.title}</span>
40
- <span class="acc-chevron"><Icon name="chevron-down" /></span>
41
- </summary>
42
- <div class="acc-panel">{@render item.content()}</div>
43
- </details>
80
+ <Disclosure
81
+ class="acc-item"
82
+ id={`acc-${item.id}`}
83
+ open={isOpen(item)}
84
+ onchange={(o) => set(item, o)}
85
+ disabled={item.disabled}
86
+ {chevron}
87
+ >
88
+ {#snippet header(ctx)}
89
+ {#if item.summary}
90
+ {@render item.summary(ctx)}
91
+ {:else}
92
+ {@render plain(item.title)}
93
+ {/if}
94
+ {/snippet}
95
+ {@render item.content()}
96
+ </Disclosure>
44
97
  {/each}
45
98
  </div>
46
99
 
@@ -50,48 +103,13 @@
50
103
  border-radius: var(--r-lg);
51
104
  overflow: hidden;
52
105
  }
53
- details + details {
106
+ .accordion > :global(.acc-item + .acc-item) {
54
107
  border-top: 1px solid var(--border);
55
108
  }
56
- summary {
57
- display: flex;
58
- align-items: center;
59
- justify-content: space-between;
60
- gap: var(--sp-2);
61
- padding: var(--sp-3) var(--sp-4);
62
- cursor: pointer;
63
- font-weight: var(--fw-medium);
64
- font-size: var(--fs-sm);
65
- list-style: none;
66
- user-select: none;
67
- }
68
- /* Hide the default disclosure triangle across engines. */
69
- summary::-webkit-details-marker {
70
- display: none;
71
- }
72
- summary::marker {
73
- content: '';
74
- }
75
- summary:hover {
76
- background: var(--bg-elevated-2);
77
- }
78
- summary:focus-visible {
79
- outline: 2px solid var(--accent);
80
- outline-offset: -2px;
81
- }
82
- /* Wrap the chevron in our own element so rotation is a scoped rule (no
83
- :global into the Icon child). The Icon inherits the color via currentColor. */
84
- .acc-chevron {
85
- display: inline-flex;
86
- color: var(--text-muted);
87
- transition: transform 0.15s var(--ease);
88
- }
89
- details[open] summary .acc-chevron {
90
- transform: rotate(180deg);
91
- }
92
- .acc-panel {
93
- padding: 0 var(--sp-4) var(--sp-4);
94
- font-size: var(--fs-sm);
95
- color: var(--text-muted);
109
+ .acc-title {
110
+ min-width: 0;
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ white-space: nowrap;
96
114
  }
97
115
  </style>
@@ -1,16 +1,29 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import type { DisclosureHeaderContext } from './Disclosure.svelte';
2
3
  export interface AccordionItem {
3
4
  id: string;
4
- title: string;
5
+ /** Plain-text header; `summary` wins when both are given. */
6
+ title?: string;
7
+ /** Rich header content; receives the live open state. */
8
+ summary?: Snippet<[DisclosureHeaderContext]>;
5
9
  /** Panel content for this item. */
6
10
  content: Snippet;
11
+ /** Open state. Re-passing a new value takes over from any local toggle. */
7
12
  open?: boolean;
13
+ onchange?: (open: boolean) => void;
14
+ disabled?: boolean;
8
15
  }
9
- type $$ComponentProps = {
16
+ import type { HTMLAttributes } from 'svelte/elements';
17
+ import type { DisclosureChevron } from './Disclosure.svelte';
18
+ type Own = {
10
19
  items: AccordionItem[];
11
20
  multiple?: boolean;
21
+ chevron?: DisclosureChevron;
22
+ onchange?: (id: string, open: boolean) => void;
12
23
  class?: string;
24
+ style?: string;
13
25
  };
26
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
14
27
  declare const Accordion: import("svelte").Component<$$ComponentProps, {}, "">;
15
28
  type Accordion = ReturnType<typeof Accordion>;
16
29
  export default Accordion;