@rokkit/ui 1.5.0 → 1.7.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.
@@ -28,8 +28,8 @@
28
28
  onclose,
29
29
  class: className = '',
30
30
  item: itemSnippet,
31
- ...snippets
32
- }: FloatingActionProps & { [key: string]: FloatingActionItemSnippet | unknown } = $props()
31
+ snippets = {}
32
+ }: FloatingActionProps = $props()
33
33
 
34
34
  const icons = $derived({
35
35
  add: DEFAULT_STATE_ICONS.action.add,
@@ -1,7 +1,4 @@
1
- import type { FloatingActionProps, FloatingActionItemSnippet } from '../types/floating-action.js';
2
- type $$ComponentProps = FloatingActionProps & {
3
- [key: string]: FloatingActionItemSnippet | unknown;
4
- };
5
- declare const FloatingAction: import("svelte").Component<$$ComponentProps, {}, "open">;
1
+ import type { FloatingActionProps } from '../types/floating-action.js';
2
+ declare const FloatingAction: import("svelte").Component<FloatingActionProps, {}, "open">;
6
3
  type FloatingAction = ReturnType<typeof FloatingAction>;
7
4
  export default FloatingAction;
@@ -45,9 +45,16 @@
45
45
  label = messages.grid.label,
46
46
  onselect,
47
47
  class: className = '',
48
- ...snippets
48
+ itemContent,
49
+ groupContent,
50
+ snippets = {}
49
51
  }: GridProps = $props()
50
52
 
53
+ // What resolveSnippet searches: the per-item named snippets plus the two
54
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
55
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
56
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
57
+
51
58
  // ─── Wrapper ──────────────────────────────────────────────────────────────
52
59
 
53
60
  const proxyTree = $derived(new ProxyTree(items, fields))
@@ -95,7 +102,7 @@
95
102
  {#each wrapper.flatView as node (node.key)}
96
103
  {@const proxy = node.proxy}
97
104
  {@const sel = proxy.value === value}
98
- {@const content = resolveSnippet(snippets as Record<string, unknown>, proxy, ITEM_SNIPPET)}
105
+ {@const content = resolveSnippet(snippetBag, proxy, ITEM_SNIPPET)}
99
106
  <button
100
107
  type="button"
101
108
  data-grid-item
@@ -28,9 +28,16 @@
28
28
  onlazyload,
29
29
  hasMore = false,
30
30
  class: className = '',
31
- ...snippets
31
+ itemContent,
32
+ groupContent,
33
+ snippets = {}
32
34
  }: LazyTreeProps = $props()
33
35
 
36
+ // What resolveSnippet searches: the per-item named snippets plus the two
37
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
38
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
39
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
40
+
34
41
  const labels = $derived({ ...messages.tree, ...userLabels })
35
42
 
36
43
  const icons = $derived({ ...DEFAULT_STATE_ICONS.folder, ...userIcons })
@@ -77,7 +84,7 @@
77
84
  {@const proxy = node.proxy}
78
85
  {@const isActive = proxy.value === value}
79
86
  {@const isLoading = proxy.loading}
80
- {@const content = resolveSnippet(snippets, proxy, ITEM_SNIPPET)}
87
+ {@const content = resolveSnippet(snippetBag, proxy, ITEM_SNIPPET)}
81
88
 
82
89
  <div
83
90
  data-tree-node
@@ -45,9 +45,16 @@
45
45
  icons: userIcons = {} as ListStateIcons,
46
46
  onselect,
47
47
  class: className = '',
48
- ...snippets
48
+ itemContent,
49
+ groupContent,
50
+ snippets = {}
49
51
  }: ListProps = $props()
50
52
 
53
+ // What resolveSnippet searches: the per-item named snippets plus the two
54
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
55
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
56
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
57
+
51
58
  const icons = $derived({ ...DEFAULT_STATE_ICONS.accordion, ...userIcons })
52
59
 
53
60
  /**
@@ -168,7 +175,7 @@
168
175
  {@const proxy = node.proxy}
169
176
  {@const isActive = proxy.value === value}
170
177
  {@const content = resolveSnippet(
171
- snippets as Record<string, unknown>,
178
+ snippetBag,
172
179
  proxy,
173
180
  node.hasChildren ? GROUP_SNIPPET : ITEM_SNIPPET
174
181
  )}
@@ -64,9 +64,16 @@
64
64
  icons: userIcons = {} as MenuStateIcons,
65
65
  onselect,
66
66
  class: className = '',
67
- ...snippets
67
+ itemContent,
68
+ groupContent,
69
+ snippets = {}
68
70
  }: MenuProps = $props()
69
71
 
72
+ // What resolveSnippet searches: the per-item named snippets plus the two
73
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
74
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
75
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
76
+
70
77
  const icons = $derived({ ...DEFAULT_STATE_ICONS.selector, ...userIcons })
71
78
 
72
79
  // ─── Dropdown state ───────────────────────────────────────────────────────
@@ -198,7 +205,7 @@
198
205
  {@const proxy = node.proxy}
199
206
  {@const isActive = proxy.value === value}
200
207
  {@const content = resolveSnippet(
201
- snippets as Record<string, unknown>,
208
+ snippetBag,
202
209
  proxy,
203
210
  node.hasChildren ? GROUP_SNIPPET : ITEM_SNIPPET
204
211
  )}
@@ -46,9 +46,16 @@
46
46
  icons: userIcons = {} as SelectStateIcons,
47
47
  onchange,
48
48
  class: className = '',
49
- ...snippets
49
+ itemContent,
50
+ groupContent,
51
+ snippets = {}
50
52
  }: MultiSelectProps = $props()
51
53
 
54
+ // What resolveSnippet searches: the per-item named snippets plus the two
55
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
56
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
57
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
58
+
52
59
  const icons = $derived({
53
60
  ...DEFAULT_STATE_ICONS.selector,
54
61
  ...DEFAULT_STATE_ICONS.checkbox,
@@ -296,7 +303,7 @@
296
303
  {@const proxy = node.proxy}
297
304
  {@const sel = !node.hasChildren && isItemSelected(proxy.value)}
298
305
  {@const content = resolveSnippet(
299
- snippets as Record<string, unknown>,
306
+ snippetBag,
300
307
  proxy,
301
308
  node.hasChildren ? GROUP_SNIPPET : ITEM_SNIPPET
302
309
  )}
@@ -61,9 +61,16 @@
61
61
  icons: userIcons = {} as SelectStateIcons,
62
62
  onchange,
63
63
  class: className = '',
64
- ...snippets
64
+ itemContent,
65
+ groupContent,
66
+ snippets = {}
65
67
  }: SelectProps = $props()
66
68
 
69
+ // What resolveSnippet searches: the per-item named snippets plus the two
70
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
71
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
72
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
73
+
67
74
  const icons = $derived({
68
75
  ...DEFAULT_STATE_ICONS.selector,
69
76
  ...DEFAULT_STATE_ICONS.checkbox,
@@ -442,7 +449,7 @@
442
449
  {@const proxy = node.proxy}
443
450
  {@const sel = !node.hasChildren && proxy.value === value}
444
451
  {@const content = resolveSnippet(
445
- snippets as Record<string, unknown>,
452
+ snippetBag,
446
453
  proxy,
447
454
  node.hasChildren ? GROUP_SNIPPET : ITEM_SNIPPET
448
455
  )}
@@ -17,7 +17,6 @@
17
17
  class: className = '',
18
18
  onchange,
19
19
  item: itemSnippet,
20
- ..._rest
21
20
  }: SwatchProps = $props()
22
21
 
23
22
  const proxyTree = $derived(new ProxyTree(options, userFields))
@@ -23,7 +23,8 @@
23
23
  header: headerSnippet,
24
24
  row: rowSnippet,
25
25
  cell: cellSnippet,
26
- empty: emptySnippet
26
+ empty: emptySnippet,
27
+ snippets = {}
27
28
  }: TableProps = $props()
28
29
 
29
30
  const icons = $derived<TableSortIcons>({ ...defaultTableSortIcons, ...userIcons })
@@ -159,6 +160,12 @@
159
160
  {@const row = entry.proxy.value as Record<string, unknown>}
160
161
  {@const isSelected = wrapper.selectedKeys.has(entry.key)}
161
162
  {@const isFocused = wrapper.focusedKey === entry.key}
163
+ <!-- A roving tabindex needs a resting stop. focusedKey starts null, and this
164
+ root carries no tabindex of its own (unlike Tree, which puts 0 on its
165
+ container), so keying purely off isFocused left the body with NO tab stop
166
+ and no way for a keyboard user to enter the grid. The first row holds it
167
+ until focus moves. -->
168
+ {@const isTabStop = isFocused || (wrapper.focusedKey === null && rowIndex === 0)}
162
169
  {#if rowSnippet}
163
170
  {@render rowSnippet(row, proxyTable.columns, rowIndex, isSelected)}
164
171
  {:else}
@@ -169,12 +176,18 @@
169
176
  data-focused={isFocused || undefined}
170
177
  aria-selected={isSelected}
171
178
  aria-rowindex={rowIndex + 1}
172
- tabindex={isFocused ? 0 : -1}
179
+ tabindex={isTabStop ? 0 : -1}
173
180
  >
174
181
  {#each proxyTable.columns as column (column.name)}
175
- {#if cellSnippet}
182
+ <!-- A column that names a snippet is more specific than the blanket
183
+ `cell`, so it wins — the same precedence per-item named snippets
184
+ have over `itemContent` elsewhere. Naming one nobody passed falls
185
+ through to the default renderer rather than blanking the cell. -->
186
+ {@const namedCell = column.snippet ? snippets[column.snippet] : undefined}
187
+ {@const renderCell = namedCell ?? cellSnippet}
188
+ {#if renderCell}
176
189
  <td data-table-cell data-column={column.name} data-label={column.label ?? column.name} style:text-align={column.align}>
177
- {@render cellSnippet(getCellValue(row, column), column, row)}
190
+ {@render renderCell(getCellValue(row, column), column, row)}
178
191
  </td>
179
192
  {:else}
180
193
  {@const cellIcon = getCellIcon(row, column)}
@@ -22,7 +22,8 @@
22
22
  import { Wrapper, ProxyTree, messages } from '@rokkit/states'
23
23
  import { Navigator } from '@rokkit/actions'
24
24
  import { resolveSnippet, ITEM_SNIPPET, DEFAULT_STATE_ICONS } from '@rokkit/core'
25
- import type { SelectableItemSnippet } from '../types/snippets.js'
25
+ import type { Snippet } from 'svelte'
26
+ import type { SelectableItemSnippet, ItemSnippet } from '../types/snippets.js'
26
27
 
27
28
  let {
28
29
  options = [],
@@ -41,14 +42,25 @@
41
42
  onselect,
42
43
  onadd,
43
44
  onremove,
44
- ...snippets
45
+ itemContent,
46
+ empty,
47
+ tabPanel,
48
+ snippets = {}
45
49
  }: TabsProps & {
46
50
  labels?: Record<string, string>
47
51
  // Tabs renders its content as content(proxy, selected).
48
52
  itemContent?: SelectableItemSnippet
49
- [key: string]: unknown
53
+ /** Shown when there are no tabs. */
54
+ empty?: Snippet<[]>
55
+ /** Panel body for the active tab. */
56
+ tabPanel?: ItemSnippet
57
+ /** Per-item named snippets, keyed by the item's `snippet` field. */
58
+ snippets?: Record<string, SelectableItemSnippet>
50
59
  } = $props()
51
60
 
61
+ // See the note in List.svelte — named snippets are a declared prop now.
62
+ const snippetBag = $derived({ ...snippets, itemContent })
63
+
52
64
  const labels = $derived({ ...messages.tabs, ...userLabels })
53
65
 
54
66
  // ─── Wrapper ──────────────────────────────────────────────────────────────
@@ -141,8 +153,8 @@
141
153
  >
142
154
  {#if options.length === 0}
143
155
  <div data-tabs-empty>
144
- {#if snippets.empty}
145
- {@render snippets.empty()}
156
+ {#if empty}
157
+ {@render empty()}
146
158
  {:else}
147
159
  {@render defaultEmpty()}
148
160
  {/if}
@@ -152,7 +164,7 @@
152
164
  {#each wrapper.flatView as node (node.key)}
153
165
  {@const proxy = node.proxy}
154
166
  {@const sel = proxy.value === value}
155
- {@const content = resolveSnippet(snippets, proxy, ITEM_SNIPPET)}
167
+ {@const content = resolveSnippet(snippetBag, proxy, ITEM_SNIPPET)}
156
168
 
157
169
  <button
158
170
  type="button"
@@ -190,8 +202,8 @@
190
202
  id="tab-panel-{node.key}"
191
203
  aria-labelledby="tab-{node.key}"
192
204
  >
193
- {#if snippets.tabPanel}
194
- {@render snippets.tabPanel(proxy)}
205
+ {#if tabPanel}
206
+ {@render tabPanel(proxy)}
195
207
  {:else}
196
208
  {@render defaultPanel(proxy)}
197
209
  {/if}
@@ -17,11 +17,17 @@
17
17
  * receives data-panel-active. Navigator ignores panels (no data-path on them).
18
18
  */
19
19
  import type { TabsProps } from '../types/tabs.js';
20
- import type { SelectableItemSnippet } from '../types/snippets.js';
20
+ import type { Snippet } from 'svelte';
21
+ import type { SelectableItemSnippet, ItemSnippet } from '../types/snippets.js';
21
22
  type $$ComponentProps = TabsProps & {
22
23
  labels?: Record<string, string>;
23
24
  itemContent?: SelectableItemSnippet;
24
- [key: string]: unknown;
25
+ /** Shown when there are no tabs. */
26
+ empty?: Snippet<[]>;
27
+ /** Panel body for the active tab. */
28
+ tabPanel?: ItemSnippet;
29
+ /** Per-item named snippets, keyed by the item's `snippet` field. */
30
+ snippets?: Record<string, SelectableItemSnippet>;
25
31
  };
26
32
  declare const Tabs: import("svelte").Component<$$ComponentProps, {}, "value">;
27
33
  type Tabs = ReturnType<typeof Tabs>;
@@ -17,13 +17,18 @@
17
17
  disabled = false,
18
18
  label = messages.toggle.label,
19
19
  class: className = '',
20
- ...snippets
20
+ itemContent,
21
+ snippets = {}
21
22
  }: ToggleProps & {
22
23
  // Toggle renders its content as content(proxy, selected).
23
24
  itemContent?: SelectableItemSnippet
24
- [key: string]: unknown
25
+ /** Per-item named snippets, keyed by the item's `snippet` field. */
26
+ snippets?: Record<string, SelectableItemSnippet>
25
27
  } = $props()
26
28
 
29
+ // See the note in List.svelte — named snippets are a declared prop now.
30
+ const snippetBag = $derived({ ...snippets, itemContent })
31
+
27
32
  // ─── Wrapper ──────────────────────────────────────────────────────────────
28
33
 
29
34
  const proxyTree = $derived(new ProxyTree(options, userFields))
@@ -76,7 +81,7 @@
76
81
 
77
82
  {#if variant === 'button'}
78
83
  {@const proxy = nextNode?.proxy}
79
- {@const content = proxy ? resolveSnippet(snippets, proxy) : null}
84
+ {@const content = proxy ? resolveSnippet(snippetBag, proxy) : null}
80
85
  <button
81
86
  type="button"
82
87
  data-toggle
@@ -119,7 +124,7 @@
119
124
  {#each wrapper.flatView as node (node.key)}
120
125
  {@const proxy = node.proxy}
121
126
  {@const sel = proxy.value === value}
122
- {@const content = resolveSnippet(snippets, proxy)}
127
+ {@const content = resolveSnippet(snippetBag, proxy)}
123
128
  <button
124
129
  type="button"
125
130
  data-toggle-option
@@ -2,7 +2,8 @@ import type { ToggleProps } from '../types/toggle.js';
2
2
  import type { SelectableItemSnippet } from '../types/snippets.js';
3
3
  type $$ComponentProps = ToggleProps & {
4
4
  itemContent?: SelectableItemSnippet;
5
- [key: string]: unknown;
5
+ /** Per-item named snippets, keyed by the item's `snippet` field. */
6
+ snippets?: Record<string, SelectableItemSnippet>;
6
7
  };
7
8
  declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "value">;
8
9
  type Toggle = ReturnType<typeof Toggle>;
@@ -27,8 +27,8 @@
27
27
  center,
28
28
  end,
29
29
  children,
30
- ...snippets
31
- }: ToolbarProps & { label?: string; [key: string]: ToolbarItemSnippet | unknown } = $props()
30
+ snippets = {}
31
+ }: ToolbarProps & { label?: string } = $props()
32
32
 
33
33
  /**
34
34
  * Create a ProxyItem for the given item
@@ -1,7 +1,6 @@
1
- import type { ToolbarProps, ToolbarItemSnippet } from '../types/toolbar.js';
1
+ import type { ToolbarProps } from '../types/toolbar.js';
2
2
  type $$ComponentProps = ToolbarProps & {
3
3
  label?: string;
4
- [key: string]: ToolbarItemSnippet | unknown;
5
4
  };
6
5
  declare const Toolbar: import("svelte").Component<$$ComponentProps, {}, "">;
7
6
  type Toolbar = ReturnType<typeof Toolbar>;
@@ -29,9 +29,16 @@
29
29
  icons: userIcons = {},
30
30
  onselect,
31
31
  class: className = '',
32
- ...snippets
32
+ itemContent,
33
+ groupContent,
34
+ snippets = {}
33
35
  }: TreeProps = $props()
34
36
 
37
+ // What resolveSnippet searches: the per-item named snippets plus the two
38
+ // defaults it falls back to. Named snippets arrive as a declared `snippets`
39
+ // prop now, so a misspelled prop name is a type error rather than a no-op.
40
+ const snippetBag = $derived({ ...snippets, itemContent, groupContent })
41
+
35
42
  const labels = $derived({ ...messages.tree, ...userLabels })
36
43
 
37
44
  const icons = $derived({ ...DEFAULT_STATE_ICONS.folder, ...userIcons })
@@ -81,7 +88,7 @@
81
88
  {#each wrapper.flatView as node (node.key)}
82
89
  {@const proxy = node.proxy}
83
90
  {@const isActive = proxy.value === value}
84
- {@const content = resolveSnippet(snippets, proxy, ITEM_SNIPPET)}
91
+ {@const content = resolveSnippet(snippetBag, proxy, ITEM_SNIPPET)}
85
92
 
86
93
  <div
87
94
  data-tree-node
@@ -15,7 +15,6 @@
15
15
  import type { ProxyItem } from '@rokkit/states'
16
16
  import type { UploadProgressProps, UploadItem } from '../types/upload-progress.js'
17
17
  import { messages } from '@rokkit/states'
18
- import { ITEM_SNIPPET } from '@rokkit/core'
19
18
  import List from './List.svelte'
20
19
  import Grid from './Grid.svelte'
21
20
  import UploadFileStatus from './UploadFileStatus.svelte'
@@ -33,8 +32,8 @@
33
32
  onclear,
34
33
  labels: userLabels = {} as Record<string, string>,
35
34
  class: className = '',
36
- ...snippets
37
- }: UploadProgressProps & { [key: string]: unknown } = $props()
35
+ itemContent
36
+ }: UploadProgressProps = $props()
38
37
 
39
38
  // ─── Labels ──────────────────────────────────────────────────────────────
40
39
 
@@ -71,7 +70,7 @@
71
70
 
72
71
  // ─── Snippet resolution ──────────────────────────────────────────────────
73
72
 
74
- let itemSnippet = $derived(snippets[ITEM_SNIPPET] as typeof defaultItemContent | undefined)
73
+ let itemSnippet = $derived(itemContent as typeof defaultItemContent | undefined)
75
74
 
76
75
  // ─── Action callbacks (unwrap proxy → raw item) ──────────────────────────
77
76
 
@@ -1,7 +1,4 @@
1
1
  import type { UploadProgressProps } from '../types/upload-progress.js';
2
- type $$ComponentProps = UploadProgressProps & {
3
- [key: string]: unknown;
4
- };
5
- declare const UploadProgress: import("svelte").Component<$$ComponentProps, {}, "">;
2
+ declare const UploadProgress: import("svelte").Component<UploadProgressProps, {}, "">;
6
3
  type UploadProgress = ReturnType<typeof UploadProgress>;
7
4
  export default UploadProgress;
@@ -12,7 +12,6 @@
12
12
  * data-dragging — present when files dragged over
13
13
  * data-disabled — when disabled
14
14
  */
15
- import type { Snippet } from 'svelte'
16
15
  import type { UploadTargetProps } from '../types/upload-target.js'
17
16
  import { messages } from '@rokkit/states'
18
17
  import { matchesAccept } from '../utils/upload.js'
@@ -27,10 +26,9 @@
27
26
  onfiles,
28
27
  onerror,
29
28
  class: className = '',
30
- ...snippets
31
- }: UploadTargetProps & { [key: string]: unknown } = $props()
29
+ content
30
+ }: UploadTargetProps = $props()
32
31
 
33
- const content = $derived(snippets.content as Snippet<[dragging: boolean]> | undefined)
34
32
 
35
33
  const resolvedLabels = $derived({
36
34
  ...messages.uploadTarget,
@@ -1,7 +1,17 @@
1
+ /**
2
+ * UploadTarget — Drop zone with file validation.
3
+ *
4
+ * Accepts drag-and-drop or click-to-browse. Validates files against
5
+ * accept (MIME/extension) and maxSize constraints via upload-utils.
6
+ *
7
+ * Data attributes:
8
+ * data-upload-target — root container / drop zone
9
+ * data-upload-icon — upload icon
10
+ * data-upload-button — browse button
11
+ * data-dragging — present when files dragged over
12
+ * data-disabled — when disabled
13
+ */
1
14
  import type { UploadTargetProps } from '../types/upload-target.js';
2
- type $$ComponentProps = UploadTargetProps & {
3
- [key: string]: unknown;
4
- };
5
- declare const UploadTarget: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ declare const UploadTarget: import("svelte").Component<UploadTargetProps, {}, "">;
6
16
  type UploadTarget = ReturnType<typeof UploadTarget>;
7
17
  export default UploadTarget;
@@ -85,5 +85,10 @@ export interface FloatingActionProps {
85
85
  class?: string;
86
86
  /** Custom snippet for rendering action items */
87
87
  item?: FloatingActionItemSnippet;
88
+ /**
89
+ * Per-item named snippets, selected via the item's `snippet` field. A declared
90
+ * prop rather than an index signature — see types/snippets.ts for why.
91
+ */
92
+ snippets?: Record<string, FloatingActionItemSnippet>;
88
93
  }
89
94
  export { getSnippet } from './menu.js';
@@ -41,15 +41,24 @@ export type SelectableItemSnippet = Snippet<[ProxyItem, boolean]>;
41
41
  * let { items = [], ...snippets }: { items?: unknown[] } & ItemSnippets = $props()
42
42
  * ```
43
43
  */
44
- export interface ItemSnippets {
44
+ export interface ItemSnippets<S = ItemSnippet> {
45
45
  /** Inner content for a leaf item. */
46
- itemContent?: ItemSnippet;
46
+ itemContent?: S;
47
47
  /** Inner content for a group/parent item. Falls back to `itemContent`. */
48
- groupContent?: ItemSnippet;
48
+ groupContent?: S;
49
49
  /**
50
- * Per-item named snippets, selected via the item's `snippet` field. Kept as
51
- * `unknown` rather than `ItemSnippet` because this signature also has to
52
- * admit every other prop the component spreads through.
50
+ * Per-item named snippets, selected via the item's `snippet` field:
51
+ *
52
+ * ```svelte
53
+ * {#snippet pinned(proxy)}…{/snippet}
54
+ * <List items={[{ label: 'A', snippet: 'pinned' }]} snippets={{ pinned }} />
55
+ * ```
56
+ *
57
+ * A declared prop rather than an index signature. An index signature would
58
+ * have to admit every other prop too — TypeScript requires each declared
59
+ * member to conform to it — which forced it to `unknown` and silently
60
+ * accepted any misspelled prop name (`itemcontnt`, `onSelect`) on every
61
+ * component that used it.
53
62
  */
54
- [key: string]: unknown;
63
+ snippets?: Record<string, S>;
55
64
  }
@@ -36,6 +36,4 @@ export interface SwatchProps {
36
36
  onchange?: (value: unknown, item: unknown) => void;
37
37
  /** Custom snippet for rendering a swatch (receives proxy + selected) */
38
38
  item?: Snippet<[ProxyItem, boolean]>;
39
- /** Remaining props are spread through */
40
- [key: string]: unknown;
41
39
  }
@@ -143,6 +143,11 @@ export interface TableProps {
143
143
  row?: TableRowSnippet;
144
144
  /** Custom cell snippet */
145
145
  cell?: TableCellSnippet;
146
+ /**
147
+ * Per-column named snippets, selected via a column's `snippet` field. Takes
148
+ * precedence over `cell` for the columns that name one.
149
+ */
150
+ snippets?: Record<string, TableCellSnippet>;
146
151
  /** Empty state snippet */
147
152
  empty?: TableEmptySnippet;
148
153
  }
@@ -85,6 +85,11 @@ export interface ToolbarProps {
85
85
  class?: string;
86
86
  /** Custom snippet for rendering toolbar items */
87
87
  item?: ToolbarItemSnippet;
88
+ /**
89
+ * Per-item named snippets, selected via the item's `snippet` field. A declared
90
+ * prop rather than an index signature — see types/snippets.ts for why.
91
+ */
92
+ snippets?: Record<string, ToolbarItemSnippet>;
88
93
  /** Slot for start section content */
89
94
  start?: import('svelte').Snippet;
90
95
  /** Slot for center section content */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/ui",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Data driven UI components for Rokkit applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,10 +45,10 @@
45
45
  "dropdown"
46
46
  ],
47
47
  "dependencies": {
48
- "@rokkit/core": "1.5.0",
49
- "@rokkit/data": "1.5.0",
50
- "@rokkit/states": "1.5.0",
51
- "@rokkit/actions": "1.5.0",
48
+ "@rokkit/core": "1.7.0",
49
+ "@rokkit/data": "1.7.0",
50
+ "@rokkit/states": "1.7.0",
51
+ "@rokkit/actions": "1.7.0",
52
52
  "marked": "^15.0.0",
53
53
  "dompurify": "^3.4.13"
54
54
  },