@dorsk/tsumikit 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -222,6 +222,23 @@ renders, and the trigger belongs to `Popover`, so `.toolbar .my-trigger` compile
222
222
  to `.toolbar.svelte-x .my-trigger.svelte-x` and matches nothing. Use `style` and
223
223
  the properties above instead.
224
224
 
225
+ ### Anchoring to a component
226
+
227
+ Components that render a single addressable element forward rest props onto it,
228
+ so `data-journey`, `data-testid` and analytics attributes land where you expect.
229
+ `Popover` puts them on its **trigger** (so `Menu`, `ThemePicker` and
230
+ `FontScalePicker` forward through to it), `Tabs` on its root — with a per-tab
231
+ `attrs` bag on each `TabItem` — `FilterInput` on its root, and `FilterSearchBar`
232
+ forwards onto that. `Menu` items take an `attrs` bag. Components that forward
233
+ into another component take `AnchorAttributes` (`data-*` only), which cannot
234
+ collide with the child's own props; the rest take full `HTMLAttributes`. The
235
+ component's own `role`, `aria-*`, `class` and keyboard wiring are applied after
236
+ the spread and always win, so an anchor can never break behaviour; `class` in a
237
+ rest/`attrs` bag is therefore dropped — use `class` / `triggerClass`.
238
+
239
+ Never target the private `data-tsu="…"` markers: they are internal, unguarded by
240
+ any test, and change without a major.
241
+
225
242
  ## Components
226
243
 
227
244
  **Atoms:** Text, Heading, Button, Input (`icon` inset leading glyph,
@@ -241,7 +258,8 @@ always/hover/none, `align`), Icon (open registry — pass a `children` snippet f
241
258
  any custom SVG).
242
259
 
243
260
  **Molecules:** Field (`grow`), IconButton, SelectButton, Toggle, OptionButton, Modal,
244
- Popover, Menu (items take a free-form trailing `tag` + `tagTone`, or a `tag` snippet),
261
+ Popover, Menu (items take a free-form trailing `tag` + `tagTone`, or a `tag` snippet,
262
+ and an `attrs` object for `data-*`/test ids on the row),
245
263
  Tabs, RadioGroup (`variant="rows"`: bordered rows, per-option `note`/`description`,
246
264
  `action(option)` trailing control that never toggles, `below(option)` inline panel),
247
265
  Tooltip, Accordion, CopyButton, FileButton,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `data-*` attributes for addressing a component later — tour anchors, test ids.
3
+ *
4
+ * Components rendering their own element take a full `HTMLAttributes` rest
5
+ * instead; this narrower bag is for those that forward into another component,
6
+ * where a full attribute type collides with the child's narrower props.
7
+ */
8
+ export type AnchorAttributes = {
9
+ [key: `data-${string}`]: string | number | boolean | null | undefined;
10
+ };
package/dist/anchor.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -2,9 +2,10 @@
2
2
  import type { FilterNode, Query } from '../../query/ast';
3
3
 
4
4
  /**
5
- * The reactive parse context handed to the `inline` and `below` snippets so a
6
- * host can render arbitrary Svelte nodes (badges, custom components, chips)
7
- * either inline inside the bar or below it — all derived from the same value.
5
+ * The reactive parse context handed to the `inline`, `display` and `below`
6
+ * snippets so a host can render arbitrary Svelte nodes (badges, custom
7
+ * components, chips) inside the bar, over the input or below it — all
8
+ * derived from the same value.
8
9
  */
9
10
  export interface FilterInputContext {
10
11
  /** The parsed AST for the current value. */
@@ -38,6 +39,7 @@
38
39
  // `ValueProvider`s). No `fetch` lives in here; the bar owns the QUERY string.
39
40
  // ───────────────────────────────────────────────────────────────────────
40
41
  import type { Snippet } from 'svelte';
42
+ import type { HTMLAttributes } from 'svelte/elements';
41
43
  import Icon, { type IconName } from '../atoms/Icon.svelte';
42
44
  import { filters, freeText } from '../../query/ast';
43
45
  import { autoQuoteEdit, backspaceEmptyQuotes, closingQuoteExit } from '../../query/edit';
@@ -67,10 +69,14 @@
67
69
  onchange,
68
70
  onsubmit,
69
71
  inline,
72
+ display,
70
73
  below,
71
74
  class: klass = '',
72
75
  style: styleProp = '',
73
- }: {
76
+ ...rest
77
+ }: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
78
+
79
+ type Own = {
74
80
  schema: Schema;
75
81
  /**
76
82
  * Single-key mode: the name (or alias) of the ONE schema field being
@@ -120,6 +126,15 @@
120
126
  * inline (no below-bar chips). Receives the reactive parse context.
121
127
  */
122
128
  inline?: Snippet<[FilterInputContext]>;
129
+ /**
130
+ * Rendering of the value shown over the input while the field is blurred
131
+ * and non-empty — a compact path, a formatted timestamp, a resolved id.
132
+ * Focusing the input (click, Tab, `hotkey`, `context.focus()`) reveals the
133
+ * raw text again. It is decoration, never a control: the overlay is
134
+ * `aria-hidden`, takes no pointer events and adds no tab stop, so the
135
+ * input stays the single focus target.
136
+ */
137
+ display?: Snippet<[FilterInputContext]>;
123
138
  /**
124
139
  * Content rendered below the bar (e.g. removable chips). FilterSearchBar
125
140
  * passes its chip row here; single-field hosts simply omit it.
@@ -127,7 +142,7 @@
127
142
  below?: Snippet<[FilterInputContext]>;
128
143
  class?: string;
129
144
  style?: string;
130
- } = $props();
145
+ };
131
146
 
132
147
  const field = getFieldContext();
133
148
  let el = $state<HTMLInputElement | null>(null);
@@ -177,6 +192,8 @@
177
192
  focus: () => el?.focus()
178
193
  });
179
194
 
195
+ const showDisplay = $derived(!!display && !focused && !!value);
196
+
180
197
  // Emit the AST whenever the parse result changes.
181
198
  $effect(() => {
182
199
  onchange?.(ast, value);
@@ -335,6 +352,7 @@
335
352
  </script>
336
353
 
337
354
  <div
355
+ {...rest}
338
356
  class="fi {klass}"
339
357
  style={styleProp}
340
358
  class:fi--sm={size === 'sm'}
@@ -347,31 +365,37 @@
347
365
  <div class="fi__bar">
348
366
  {#if icon}<span class="fi__icon"><Icon name={icon} label="Search" /></span>{/if}
349
367
  {#if inline}{@render inline(ctx)}{/if}
350
- <input
351
- bind:this={el}
352
- bind:value
353
- class="fi__input"
354
- id={id ?? field?.id}
355
- aria-describedby={ariaDescribedby ?? field?.describedBy}
356
- aria-invalid={ariaInvalid ?? (field?.invalid ? 'true' : undefined)}
357
- spellcheck="false"
358
- autocomplete="off"
359
- placeholder={hint}
360
- {oninput}
361
- onclick={refresh}
362
- onkeyup={(e) => {
363
- if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') refresh();
364
- }}
365
- onfocus={() => {
366
- focused = true;
367
- refresh();
368
- }}
369
- onblur={() => {
370
- focused = false;
371
- setTimeout(() => (open = false), 120);
372
- }}
373
- {onkeydown}
374
- />
368
+ <span class="fi__field">
369
+ <input
370
+ bind:this={el}
371
+ bind:value
372
+ class="fi__input"
373
+ class:fi__input--masked={showDisplay}
374
+ id={id ?? field?.id}
375
+ aria-describedby={ariaDescribedby ?? field?.describedBy}
376
+ aria-invalid={ariaInvalid ?? (field?.invalid ? 'true' : undefined)}
377
+ spellcheck="false"
378
+ autocomplete="off"
379
+ placeholder={hint}
380
+ {oninput}
381
+ onclick={refresh}
382
+ onkeyup={(e) => {
383
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') refresh();
384
+ }}
385
+ onfocus={() => {
386
+ focused = true;
387
+ refresh();
388
+ }}
389
+ onblur={() => {
390
+ focused = false;
391
+ setTimeout(() => (open = false), 120);
392
+ }}
393
+ {onkeydown}
394
+ />
395
+ {#if display && showDisplay}
396
+ <span class="fi__display" aria-hidden="true">{@render display(ctx)}</span>
397
+ {/if}
398
+ </span>
375
399
  {#if showHotkey && hotkey && !focused && !value}
376
400
  <kbd class="fi__kbd" aria-hidden="true">{hotkey}</kbd>
377
401
  {/if}
@@ -474,6 +498,22 @@
474
498
  display: flex;
475
499
  color: var(--text-faint);
476
500
  }
501
+ .fi__field {
502
+ position: relative;
503
+ display: flex;
504
+ flex: 1;
505
+ min-width: 0;
506
+ }
507
+ .fi__display {
508
+ position: absolute;
509
+ inset: 0;
510
+ display: flex;
511
+ align-items: center;
512
+ gap: var(--sp-2);
513
+ min-width: 0;
514
+ overflow: hidden;
515
+ pointer-events: none;
516
+ }
477
517
  .fi__input {
478
518
  flex: 1;
479
519
  border: 0;
@@ -487,6 +527,9 @@
487
527
  .fi__input::placeholder {
488
528
  color: var(--text-faint);
489
529
  }
530
+ .fi__input--masked {
531
+ color: transparent;
532
+ }
490
533
  .fi__clear {
491
534
  display: flex;
492
535
  border: 0;
@@ -1,8 +1,9 @@
1
1
  import type { FilterNode, Query } from '../../query/ast';
2
2
  /**
3
- * The reactive parse context handed to the `inline` and `below` snippets so a
4
- * host can render arbitrary Svelte nodes (badges, custom components, chips)
5
- * either inline inside the bar or below it — all derived from the same value.
3
+ * The reactive parse context handed to the `inline`, `display` and `below`
4
+ * snippets so a host can render arbitrary Svelte nodes (badges, custom
5
+ * components, chips) inside the bar, over the input or below it — all
6
+ * derived from the same value.
6
7
  */
7
8
  export interface FilterInputContext {
8
9
  /** The parsed AST for the current value. */
@@ -20,9 +21,10 @@ export interface FilterInputContext {
20
21
  }
21
22
  import type { ControlSize } from '../../size';
22
23
  import type { Snippet } from 'svelte';
24
+ import type { HTMLAttributes } from 'svelte/elements';
23
25
  import { type IconName } from '../atoms/Icon.svelte';
24
26
  import { type Schema } from '../../query/schema';
25
- type $$ComponentProps = {
27
+ type Own = {
26
28
  schema: Schema;
27
29
  /**
28
30
  * Single-key mode: the name (or alias) of the ONE schema field being
@@ -72,6 +74,15 @@ type $$ComponentProps = {
72
74
  * inline (no below-bar chips). Receives the reactive parse context.
73
75
  */
74
76
  inline?: Snippet<[FilterInputContext]>;
77
+ /**
78
+ * Rendering of the value shown over the input while the field is blurred
79
+ * and non-empty — a compact path, a formatted timestamp, a resolved id.
80
+ * Focusing the input (click, Tab, `hotkey`, `context.focus()`) reveals the
81
+ * raw text again. It is decoration, never a control: the overlay is
82
+ * `aria-hidden`, takes no pointer events and adds no tab stop, so the
83
+ * input stays the single focus target.
84
+ */
85
+ display?: Snippet<[FilterInputContext]>;
75
86
  /**
76
87
  * Content rendered below the bar (e.g. removable chips). FilterSearchBar
77
88
  * passes its chip row here; single-field hosts simply omit it.
@@ -80,6 +91,7 @@ type $$ComponentProps = {
80
91
  class?: string;
81
92
  style?: string;
82
93
  };
94
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
83
95
  declare const FilterInput: import("svelte").Component<$$ComponentProps, {}, "value">;
84
96
  type FilterInput = ReturnType<typeof FilterInput>;
85
97
  export default FilterInput;
@@ -12,6 +12,12 @@
12
12
  pressed?: boolean;
13
13
  /** Custom row content (a rename Input, a slider…) replacing icon/label/tag. */
14
14
  content?: import('svelte').Snippet<[MenuItem]>;
15
+ /**
16
+ * Extra attributes for the rendered row — `data-*`, `title`, `aria-describedby`…
17
+ * The row's own semantics (`role`, `aria-checked`, `disabled`, `class`, `onclick`)
18
+ * are applied after and win.
19
+ */
20
+ attrs?: import('svelte/elements').HTMLButtonAttributes;
15
21
  }
16
22
  </script>
17
23
 
@@ -165,6 +171,7 @@
165
171
  {#each items as item (item.label)}
166
172
  <button
167
173
  type="button"
174
+ {...item.attrs}
168
175
  role={item.pressed === undefined ? 'menuitem' : 'menuitemcheckbox'}
169
176
  aria-checked={item.pressed === undefined ? undefined : item.pressed}
170
177
  class="menu-item"
@@ -11,6 +11,12 @@ export interface MenuItem {
11
11
  pressed?: boolean;
12
12
  /** Custom row content (a rename Input, a slider…) replacing icon/label/tag. */
13
13
  content?: import('svelte').Snippet<[MenuItem]>;
14
+ /**
15
+ * Extra attributes for the rendered row — `data-*`, `title`, `aria-describedby`…
16
+ * The row's own semantics (`role`, `aria-checked`, `disabled`, `class`, `onclick`)
17
+ * are applied after and win.
18
+ */
19
+ attrs?: import('svelte/elements').HTMLButtonAttributes;
14
20
  }
15
21
  import type { ComponentProps, Snippet } from 'svelte';
16
22
  import Popover from './Popover.svelte';
@@ -10,6 +10,7 @@
10
10
  // ships beyond Chromium; the popover semantics above are the hard part and
11
11
  // are broadly supported today.)
12
12
  import { tick, type Snippet } from 'svelte';
13
+ import type { HTMLAttributes } from 'svelte/elements';
13
14
  import { place } from '../../floating';
14
15
  import { HOVER_CLOSE_GRACE, HOVER_OPEN_DELAY, createHoverIntent, opensOnHover } from './popover-hover.js';
15
16
 
@@ -20,36 +21,7 @@
20
21
  type PanelRole = 'dialog' | 'menu' | 'listbox' | 'group';
21
22
  type HasPopup = 'menu' | 'dialog' | 'listbox' | true;
22
23
 
23
- let {
24
- placement = 'bottom-start',
25
- gap = 6,
26
- label,
27
- trigger,
28
- children,
29
- triggerClass = '',
30
- bare = false,
31
- variant,
32
- tone = 'none',
33
- size,
34
- box,
35
- pill = false,
36
- control = false,
37
- block = false,
38
- hitArea = 'auto',
39
- disabled = false,
40
- openOn = 'click',
41
- hoverDelay = HOVER_OPEN_DELAY,
42
- as = 'button',
43
- href,
44
- role = 'dialog',
45
- haspopup = 'dialog',
46
- onopen,
47
- onclose,
48
- class: klass = '',
49
- style: styleProp = '',
50
- panelClass = '',
51
- panelStyle = '',
52
- }: {
24
+ type Own = {
53
25
  placement?: Placement;
54
26
  gap?: number;
55
27
  /** Accessible name for the trigger. */
@@ -106,7 +78,39 @@
106
78
  /** Class / inline style on the floating panel. */
107
79
  panelClass?: string;
108
80
  panelStyle?: string;
109
- } = $props();
81
+ };
82
+
83
+ let {
84
+ placement = 'bottom-start',
85
+ gap = 6,
86
+ label,
87
+ trigger,
88
+ children,
89
+ triggerClass = '',
90
+ bare = false,
91
+ variant,
92
+ tone = 'none',
93
+ size,
94
+ box,
95
+ pill = false,
96
+ control = false,
97
+ block = false,
98
+ hitArea = 'auto',
99
+ disabled = false,
100
+ openOn = 'click',
101
+ hoverDelay = HOVER_OPEN_DELAY,
102
+ as = 'button',
103
+ href,
104
+ role = 'dialog',
105
+ haspopup = 'dialog',
106
+ onopen,
107
+ onclose,
108
+ class: klass = '',
109
+ style: styleProp = '',
110
+ panelClass = '',
111
+ panelStyle = '',
112
+ ...rest
113
+ }: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
110
114
 
111
115
  const canonicalChrome = $derived(
112
116
  variant !== undefined || tone !== 'none' || size !== undefined || control || block
@@ -219,6 +223,7 @@
219
223
  <svelte:element
220
224
  this={as}
221
225
  bind:this={triggerEl}
226
+ {...rest}
222
227
  data-tsu="Popover"
223
228
  class="pop-trigger {triggerClass} {klass}"
224
229
  style={styleProp}
@@ -1,11 +1,12 @@
1
1
  import { type Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
2
3
  type Placement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
3
4
  type TriggerVariant = 'default' | 'primary' | 'ghost' | 'danger';
4
5
  type TriggerTone = 'none' | 'accent' | 'success' | 'info' | 'warn' | 'danger';
5
6
  type TriggerSize = 'sm' | 'md' | 'lg';
6
7
  type PanelRole = 'dialog' | 'menu' | 'listbox' | 'group';
7
8
  type HasPopup = 'menu' | 'dialog' | 'listbox' | true;
8
- type $$ComponentProps = {
9
+ type Own = {
9
10
  placement?: Placement;
10
11
  gap?: number;
11
12
  /** Accessible name for the trigger. */
@@ -65,6 +66,7 @@ type $$ComponentProps = {
65
66
  panelClass?: string;
66
67
  panelStyle?: string;
67
68
  };
69
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
68
70
  declare const Popover: import("svelte").Component<$$ComponentProps, {}, "">;
69
71
  type Popover = ReturnType<typeof Popover>;
70
72
  export default Popover;
@@ -7,6 +7,12 @@
7
7
  disabled?: boolean;
8
8
  /** Trailing count badge. */
9
9
  count?: number | string;
10
+ /**
11
+ * Extra attributes for this tab's button — `data-*`, `title`…
12
+ * The tab's own semantics (`role`, `aria-*`, `id`, `class`, `disabled`,
13
+ * `tabindex`, `onclick`) are applied after and win.
14
+ */
15
+ attrs?: import('svelte/elements').HTMLButtonAttributes;
10
16
  }
11
17
  </script>
12
18
 
@@ -18,6 +24,7 @@
18
24
  // the Tab order. `value` is bindable; `panel` is a snippet that receives the
19
25
  // active id so the caller renders the matching content.
20
26
  import type { Snippet } from 'svelte';
27
+ import type { HTMLAttributes } from 'svelte/elements';
21
28
  import Icon from '../atoms/Icon.svelte';
22
29
 
23
30
  let {
@@ -29,7 +36,10 @@
29
36
  style: styleProp = '',
30
37
  panelClass = '',
31
38
  panelPadding = 'md',
32
- }: {
39
+ ...rest
40
+ }: Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own = $props();
41
+
42
+ type Own = {
33
43
  tabs: TabItem[];
34
44
  value?: string;
35
45
  label?: string;
@@ -38,7 +48,7 @@
38
48
  style?: string;
39
49
  panelClass?: string;
40
50
  panelPadding?: 'none' | 'sm' | 'md';
41
- } = $props();
51
+ };
42
52
 
43
53
  // Default to the first selectable tab when no value is supplied.
44
54
  $effect(() => {
@@ -84,11 +94,12 @@
84
94
  }
85
95
  </script>
86
96
 
87
- <div class="tabs {klass}" style={styleProp} data-tsu="Tabs">
97
+ <div {...rest} class="tabs {klass}" style={styleProp} data-tsu="Tabs">
88
98
  <div bind:this={listEl} role="tablist" aria-label={label} tabindex="-1" class="tablist" {onkeydown}>
89
99
  {#each tabs as t (t.id)}
90
100
  <button
91
101
  type="button"
102
+ {...t.attrs}
92
103
  role="tab"
93
104
  id="{baseId}-tab-{t.id}"
94
105
  aria-selected={value === t.id}
@@ -6,9 +6,16 @@ export interface TabItem {
6
6
  disabled?: boolean;
7
7
  /** Trailing count badge. */
8
8
  count?: number | string;
9
+ /**
10
+ * Extra attributes for this tab's button — `data-*`, `title`…
11
+ * The tab's own semantics (`role`, `aria-*`, `id`, `class`, `disabled`,
12
+ * `tabindex`, `onclick`) are applied after and win.
13
+ */
14
+ attrs?: import('svelte/elements').HTMLButtonAttributes;
9
15
  }
10
16
  import type { Snippet } from 'svelte';
11
- type $$ComponentProps = {
17
+ import type { HTMLAttributes } from 'svelte/elements';
18
+ type Own = {
12
19
  tabs: TabItem[];
13
20
  value?: string;
14
21
  label?: string;
@@ -18,6 +25,7 @@ type $$ComponentProps = {
18
25
  panelClass?: string;
19
26
  panelPadding?: 'none' | 'sm' | 'md';
20
27
  };
28
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, keyof Own> & Own;
21
29
  declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "value">;
22
30
  type Tabs = ReturnType<typeof Tabs>;
23
31
  export default Tabs;
@@ -6,6 +6,7 @@
6
6
  // resolved once at :root and would not re-scope). The root default theme
7
7
  // has no [data-theme] block, so its swatch carries the :root values.
8
8
  import type { ComponentProps } from 'svelte';
9
+ import type { AnchorAttributes } from '../../anchor';
9
10
  import Popover from './Popover.svelte';
10
11
  import { type ThemeDef, theme } from '../../stores/theme.svelte';
11
12
  import { AUTO_THEME } from '../../theme-mode';
@@ -34,7 +35,10 @@
34
35
  darkLabel = 'Dark',
35
36
  class: klass = '',
36
37
  style: styleProp = '',
37
- }: TriggerChrome & {
38
+ ...rest
39
+ }: AnchorAttributes & TriggerChrome & Own = $props();
40
+
41
+ type Own = {
38
42
  /** Offer an "auto" row that follows `prefers-color-scheme`, remembering
39
43
  * one light and one dark theme. */
40
44
  auto?: boolean;
@@ -44,7 +48,7 @@
44
48
  darkLabel?: string;
45
49
  class?: string;
46
50
  style?: string;
47
- } = $props();
51
+ };
48
52
 
49
53
  let hovered = $state<ThemeDef | null>(null);
50
54
  let hoveredAuto = $state(false);
@@ -74,6 +78,7 @@
74
78
  {/snippet}
75
79
 
76
80
  <Popover
81
+ {...rest}
77
82
  label={title}
78
83
  {placement}
79
84
  {box}
@@ -1,7 +1,8 @@
1
1
  import type { ComponentProps } from 'svelte';
2
+ import type { AnchorAttributes } from '../../anchor';
2
3
  import Popover from './Popover.svelte';
3
4
  type TriggerChrome = Pick<ComponentProps<typeof Popover>, 'variant' | 'tone' | 'size' | 'box' | 'pill' | 'control' | 'block' | 'bare' | 'hitArea' | 'placement' | 'disabled'>;
4
- type $$ComponentProps = TriggerChrome & {
5
+ type Own = {
5
6
  /** Offer an "auto" row that follows `prefers-color-scheme`, remembering
6
7
  * one light and one dark theme. */
7
8
  auto?: boolean;
@@ -12,6 +13,7 @@ type $$ComponentProps = TriggerChrome & {
12
13
  class?: string;
13
14
  style?: string;
14
15
  };
16
+ type $$ComponentProps = AnchorAttributes & TriggerChrome & Own;
15
17
  declare const ThemePicker: import("svelte").Component<$$ComponentProps, {}, "">;
16
18
  type ThemePicker = ReturnType<typeof ThemePicker>;
17
19
  export default ThemePicker;
@@ -14,6 +14,7 @@
14
14
  // and forwards onchange/onsubmit. Everything project-specific is INJECTED via
15
15
  // `schema` (with per-field async `ValueProvider`s).
16
16
  // ───────────────────────────────────────────────────────────────────────
17
+ import type { AnchorAttributes } from '../../anchor';
17
18
  import Badge from '../atoms/Badge.svelte';
18
19
  import FilterInput from '../molecules/FilterInput.svelte';
19
20
  import type { Query } from '../../query/ast';
@@ -35,7 +36,10 @@
35
36
  onsubmit,
36
37
  class: klass = '',
37
38
  style: styleProp = '',
38
- }: {
39
+ ...rest
40
+ }: AnchorAttributes & Own = $props();
41
+
42
+ type Own = {
39
43
  schema: Schema;
40
44
  /** The raw textual query (two-way bindable). */
41
45
  value?: string;
@@ -61,7 +65,7 @@
61
65
  onsubmit?: (value: string) => void;
62
66
  class?: string;
63
67
  style?: string;
64
- } = $props();
68
+ };
65
69
 
66
70
  function labelFor(fieldName: string): string {
67
71
  return findField(schema, fieldName)?.label ?? fieldName;
@@ -69,6 +73,7 @@
69
73
  </script>
70
74
 
71
75
  <FilterInput
76
+ {...rest}
72
77
  {schema}
73
78
  bind:value
74
79
  {placeholder}
@@ -1,7 +1,8 @@
1
1
  import type { ControlSize } from '../../size';
2
+ import type { AnchorAttributes } from '../../anchor';
2
3
  import type { Query } from '../../query/ast';
3
4
  import { type Schema } from '../../query/schema';
4
- type $$ComponentProps = {
5
+ type Own = {
5
6
  schema: Schema;
6
7
  /** The raw textual query (two-way bindable). */
7
8
  value?: string;
@@ -28,6 +29,7 @@ type $$ComponentProps = {
28
29
  class?: string;
29
30
  style?: string;
30
31
  };
32
+ type $$ComponentProps = AnchorAttributes & Own;
31
33
  declare const FilterSearchBar: import("svelte").Component<$$ComponentProps, {}, "value">;
32
34
  type FilterSearchBar = ReturnType<typeof FilterSearchBar>;
33
35
  export default FilterSearchBar;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export type { AnchorAttributes } from './anchor';
1
2
  export { artworkGradient, artworkHue, initials } from './artwork';
2
3
  export { autoresize } from './autoresize';
3
4
  export { copyToClipboard } from './clipboard';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
5
5
  "type": "module",
6
6
  "license": "MIT",