@rokkit/app 1.15.1 → 1.15.3
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/dist/components/ScreenInfo.svelte +11 -3
- package/dist/components/ScreenInfo.svelte.d.ts +2 -1
- package/dist/components/ScreenShell.svelte +14 -9
- package/dist/types/screen-shell.d.ts +16 -1
- package/dist/utils/screen-shell.d.ts +3 -1
- package/dist/utils/screen-shell.js +12 -3
- package/package.json +6 -6
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
? {
|
|
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.
|
|
3
|
+
"version": "1.15.3",
|
|
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.
|
|
42
|
-
"@rokkit/core": "1.15.
|
|
43
|
-
"@rokkit/forms": "1.15.
|
|
44
|
-
"@rokkit/states": "1.15.
|
|
45
|
-
"@rokkit/ui": "1.15.
|
|
41
|
+
"@rokkit/actions": "1.15.3",
|
|
42
|
+
"@rokkit/core": "1.15.3",
|
|
43
|
+
"@rokkit/forms": "1.15.3",
|
|
44
|
+
"@rokkit/states": "1.15.3",
|
|
45
|
+
"@rokkit/ui": "1.15.3"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"svelte": "^5.0.0"
|