@rokkit/app 1.15.1 → 1.15.2

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.
@@ -7,16 +7,24 @@
7
7
  * the role.
8
8
  */
9
9
  import type { Snippet } from 'svelte'
10
+ import type { HTMLAttributes } from 'svelte/elements'
10
11
  import type { GlyphMode, InfoColumn, InfoRole, InfoSlot } from '../types/screen-shell.js'
11
12
 
12
- interface Props {
13
+ // `role` is the band's wash, not the ARIA role, so it stays out of the passthrough.
14
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'role'> {
13
15
  columns: InfoSlot[]
14
16
  role?: InfoRole
15
17
  glyphs?: GlyphMode
16
18
  class?: string
17
19
  }
18
20
 
19
- const { columns, role = 'accent', glyphs = 'text', class: className = '' }: Props = $props()
21
+ const {
22
+ columns,
23
+ role = 'accent',
24
+ glyphs = 'text',
25
+ class: className = '',
26
+ ...rest
27
+ }: Props = $props()
20
28
 
21
29
  const isSnippet = (slot: InfoSlot): slot is Snippet => typeof slot === 'function'
22
30
  const percent = (progress: number) => `${Math.round(Math.max(0, Math.min(1, progress)) * 100)}%`
@@ -72,7 +80,7 @@
72
80
  {#if info.text}<p data-screen-info-text>{info.text}</p>{/if}
73
81
  {/snippet}
74
82
 
75
- <div data-screen-info data-role={role} class={className || undefined}>
83
+ <div {...rest} data-screen-info data-role={role} class={className || undefined}>
76
84
  {#each columns.slice(0, 3) as slot, index (index)}
77
85
  <div data-screen-info-column>
78
86
  {#if isSnippet(slot)}
@@ -1,5 +1,6 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
1
2
  import type { GlyphMode, InfoRole, InfoSlot } from '../types/screen-shell.js';
2
- interface Props {
3
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'role'> {
3
4
  columns: InfoSlot[];
4
5
  role?: InfoRole;
5
6
  glyphs?: GlyphMode;
@@ -70,7 +70,10 @@
70
70
  glyphs = 'text',
71
71
  icons,
72
72
  labels,
73
- class: className = ''
73
+ class: className = '',
74
+ toggleAttrs,
75
+ bandAttrs,
76
+ ...rest
74
77
  }: ScreenShellProps = $props()
75
78
 
76
79
  const text = $derived({ ...messages.screen, ...labels })
@@ -104,7 +107,8 @@
104
107
  panels,
105
108
  labels: text,
106
109
  icons: { ...DEFAULT_ICONS, ...icons },
107
- busy: text.busy
110
+ busy: text.busy,
111
+ toggleAttrs
108
112
  })
109
113
  )
110
114
 
@@ -212,13 +216,13 @@
212
216
 
213
217
  {#snippet side()}
214
218
  {#if sidebar && shown('side')}
215
- <aside data-screen-sidebar data-side={sidebarSide} aria-label={text.side}>
219
+ <aside {...bandAttrs?.side} data-screen-sidebar data-side={sidebarSide} aria-label={text.side}>
216
220
  {@render sidebar()}
217
221
  </aside>
218
222
  {/if}
219
223
  {/snippet}
220
224
 
221
- <div data-screen-shell data-view={viewId} class={className || undefined}>
225
+ <div {...rest} data-screen-shell data-view={viewId} class={className || undefined}>
222
226
  <header data-screen-header>
223
227
  <div data-screen-titlebar>
224
228
  {@render glyph(mark.kanji, mark.icon)}
@@ -233,16 +237,17 @@
233
237
  {#if description}<p data-screen-description>{description}</p>{/if}
234
238
 
235
239
  {#if info?.length && shown('info')}
236
- <ScreenInfo columns={info} role={infoRole} {glyphs} />
240
+ <ScreenInfo columns={info} role={infoRole} {glyphs} {...bandAttrs?.info} />
237
241
  {/if}
238
242
 
239
243
  {#if stats !== undefined && shown('stats')}
240
- <div data-screen-stats aria-label={text.stats} role="group">
244
+ <div {...bandAttrs?.stats} data-screen-stats aria-label={text.stats} role="group">
241
245
  {#if stats === null}
242
246
  <p data-screen-stats-reading>{text.reading}</p>
243
247
  {:else}
244
248
  {#each stats as stat (stat.id)}
245
249
  <StatCard
250
+ {...stat.attrs}
246
251
  label={stat.title}
247
252
  value={stat.value}
248
253
  tone={stat.tone}
@@ -262,7 +267,7 @@
262
267
  {/if}
263
268
 
264
269
  {#if shown('filters')}
265
- <div data-screen-filters role="group" aria-label={text.filters}>
270
+ <div {...bandAttrs?.filters} data-screen-filters role="group" aria-label={text.filters}>
266
271
  {#if searchOn}
267
272
  <label data-screen-search>
268
273
  <span
@@ -313,9 +318,9 @@
313
318
  </div>
314
319
 
315
320
  {#if footerSnippet}
316
- <footer data-screen-footer>{@render footerSnippet()}</footer>
321
+ <footer {...bandAttrs?.footer} data-screen-footer>{@render footerSnippet()}</footer>
317
322
  {:else if footerParts}
318
- <footer data-screen-footer>
323
+ <footer {...bandAttrs?.footer} data-screen-footer>
319
324
  {#each FOOTER_PARTS as part (part)}
320
325
  {#if footerParts[part]}<span data-part={part}>{footerParts[part]}</span>{/if}
321
326
  {/each}
@@ -7,7 +7,14 @@
7
7
  * `filter` are bindable and the host stores them.
8
8
  */
9
9
  import type { Snippet } from 'svelte';
10
+ import type { HTMLAttributes } from 'svelte/elements';
10
11
  import type { ToolbarItem } from '@rokkit/ui';
12
+ /**
13
+ * Attributes a host hangs on a shell part for its own hooks (`data-testid`) or extra
14
+ * semantics (`aria-*`): components take them as rest props, data (actions, stats, toolbar
15
+ * items) carries them in an `attrs` field, rendered on the element.
16
+ */
17
+ export type ShellAttrs = Record<string, string>;
11
18
  /** The bands a screen can open and close from its toolbar, in toolbar order. */
12
19
  export type PanelId = 'info' | 'stats' | 'filters' | 'side';
13
20
  /** Which bands are open. A missing key is open, so a first visit opens everything. */
@@ -33,6 +40,8 @@ export interface ShellAction {
33
40
  href?: string;
34
41
  busy?: boolean;
35
42
  disabled?: string | boolean;
43
+ /** Attributes rendered on the action's button or link (e.g. `data-testid`). */
44
+ attrs?: ShellAttrs;
36
45
  [key: string]: unknown;
37
46
  }
38
47
  /**
@@ -89,6 +98,8 @@ export interface ShellStat {
89
98
  value: unknown;
90
99
  };
91
100
  href?: string;
101
+ /** Attributes rendered on the stat's card (e.g. `data-testid`). */
102
+ attrs?: ShellAttrs;
92
103
  }
93
104
  /** The filter values, the one source of truth, and the active stat's id. */
94
105
  export interface FilterState {
@@ -127,7 +138,7 @@ export interface ScreenShellLabels extends PanelLabels {
127
138
  reading: string;
128
139
  busy: string;
129
140
  }
130
- export interface ScreenShellProps {
141
+ export interface ScreenShellProps extends HTMLAttributes<HTMLDivElement> {
131
142
  /** `<surface>.<area>[.<view>]` — the host keys its stored panels by it. */
132
143
  viewId?: string;
133
144
  mark: ShellMark;
@@ -162,4 +173,8 @@ export interface ScreenShellProps {
162
173
  /** String overrides; defaults come from `messages.screen`. */
163
174
  labels?: Partial<ScreenShellLabels>;
164
175
  class?: string;
176
+ /** Attributes per band toggle, rendered on its toolbar button (e.g. `data-testid`). */
177
+ toggleAttrs?: Partial<Record<PanelId, ShellAttrs>>;
178
+ /** Attributes per band, rendered on the band's element (e.g. `data-testid`). */
179
+ bandAttrs?: Partial<Record<PanelId | 'footer', ShellAttrs>>;
165
180
  }
@@ -3,7 +3,7 @@
3
3
  * how wide a stat card is, and how a stat's filter merges into the filter values.
4
4
  */
5
5
  import type { ToolbarItem } from '@rokkit/ui';
6
- import type { PanelId, PanelState, PanelLabels, ToolbarSpec, ShellStat, FilterState, StatMemory } from '../types/screen-shell.js';
6
+ import type { PanelId, PanelState, PanelLabels, ToolbarSpec, ShellAttrs, ShellStat, FilterState, StatMemory } from '../types/screen-shell.js';
7
7
  /** The bands a screen can toggle, in toolbar order. */
8
8
  export declare const PANEL_IDS: readonly PanelId[];
9
9
  /** Whether a band is open: a missing key is open (first visit: everything open). */
@@ -22,6 +22,8 @@ interface ToolbarContext {
22
22
  icons: PanelLabels;
23
23
  /** Appended to a busy action's label. */
24
24
  busy?: string;
25
+ /** Attributes per band toggle, rendered on its toolbar button. */
26
+ toggleAttrs?: Partial<Record<PanelId, ShellAttrs>>;
25
27
  }
26
28
  /**
27
29
  * The toolbar's items: the screen's own buttons, a separator, then one toggle per band the
@@ -38,23 +38,32 @@ function toToolbarItem(item, busy = '') {
38
38
  disabled: Boolean(action.disabled || action.busy)
39
39
  };
40
40
  }
41
+ /** A panel toggle with the band's attrs under its own (its own keys win). */
42
+ function withToggleAttrs(item, band) {
43
+ if (!band && item.attrs === undefined)
44
+ return item;
45
+ return { ...item, attrs: { ...band, ...(item.attrs ?? {}) } };
46
+ }
41
47
  /** One toggle per band, pressed while the band is open. */
42
48
  function panelToggle(id, ctx) {
43
- return {
49
+ return withToggleAttrs({
44
50
  id,
45
51
  value: id,
46
52
  type: 'toggle',
47
53
  icon: ctx.icons[id],
48
54
  label: ctx.labels[id],
49
55
  active: isOpen(ctx.panels, id)
50
- };
56
+ }, ctx.toggleAttrs?.[id]);
51
57
  }
52
58
  /** A full list: kept as is, except a band toggle follows the band (dropped when it is absent). */
53
59
  function resolveFullList(spec, ctx) {
54
60
  return spec
55
61
  .filter((item) => item.type !== 'toggle' || !isPanelId(item.value) || ctx.present.includes(item.value))
56
62
  .map((item) => item.type === 'toggle' && isPanelId(item.value)
57
- ? { ...item, active: isOpen(ctx.panels, item.value) }
63
+ ? {
64
+ ...withToggleAttrs(item, ctx.toggleAttrs?.[item.value]),
65
+ active: isOpen(ctx.panels, item.value)
66
+ }
58
67
  : item.type === 'separator' || item.type === 'spacer'
59
68
  ? item
60
69
  : toToolbarItem(item, ctx.busy));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/app",
3
- "version": "1.15.1",
3
+ "version": "1.15.2",
4
4
  "description": "App-level controls for Rokkit applications - theme management and UI chrome",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,11 +38,11 @@
38
38
  "build": "svelte-package -i src -o dist"
39
39
  },
40
40
  "dependencies": {
41
- "@rokkit/actions": "1.15.1",
42
- "@rokkit/core": "1.15.1",
43
- "@rokkit/forms": "1.15.1",
44
- "@rokkit/states": "1.15.1",
45
- "@rokkit/ui": "1.15.1"
41
+ "@rokkit/actions": "1.15.2",
42
+ "@rokkit/core": "1.15.2",
43
+ "@rokkit/forms": "1.15.2",
44
+ "@rokkit/states": "1.15.2",
45
+ "@rokkit/ui": "1.15.2"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "svelte": "^5.0.0"