@happyvertical/smrt-svelte 0.55.0 → 0.55.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.
- package/README.md +38 -6
- package/agents/components.md +44 -1
- package/agents/workspace.md +77 -2
- package/dist/components/app/AppShell.svelte +122 -24
- package/dist/components/app/AppShell.svelte.d.ts +25 -2
- package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
- package/dist/components/app/DockToggles.svelte +103 -0
- package/dist/components/app/DockToggles.svelte.d.ts +8 -0
- package/dist/components/app/DockToggles.svelte.d.ts.map +1 -0
- package/dist/components/app/dock-toggle.d.ts +14 -0
- package/dist/components/app/dock-toggle.d.ts.map +1 -0
- package/dist/components/app/dock-toggle.js +1 -0
- package/dist/components/app/index.d.ts +2 -0
- package/dist/components/app/index.d.ts.map +1 -1
- package/dist/components/app/index.js +1 -0
- package/dist/components/board/Board.svelte +92 -394
- package/dist/components/board/Board.svelte.d.ts.map +1 -1
- package/dist/components/forms/RelationInput.svelte +379 -0
- package/dist/components/forms/RelationInput.svelte.d.ts +75 -0
- package/dist/components/forms/RelationInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +2 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/forms/types.d.ts +9 -0
- package/dist/components/forms/types.d.ts.map +1 -1
- package/dist/components/sortable/Sortable.svelte +359 -0
- package/dist/components/sortable/Sortable.svelte.d.ts +26 -0
- package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -0
- package/dist/components/sortable/controller.svelte.d.ts +152 -0
- package/dist/components/sortable/controller.svelte.d.ts.map +1 -0
- package/dist/components/sortable/controller.svelte.js +453 -0
- package/dist/components/sortable/index.d.ts +5 -0
- package/dist/components/sortable/index.d.ts.map +1 -0
- package/dist/components/sortable/index.js +2 -0
- package/dist/components/sortable/types.d.ts +80 -0
- package/dist/components/sortable/types.d.ts.map +1 -0
- package/dist/components/sortable/types.js +1 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +62 -3
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +5 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +2 -1
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +196 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts +17 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +69 -3
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/dock.d.ts +37 -0
- package/dist/components/workspace/admin-shell/dock.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/dock.js +25 -0
- package/dist/components/workspace/admin-shell/layout-context.d.ts +7 -0
- package/dist/components/workspace/admin-shell/layout-context.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout-context.js +17 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +69 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.js +91 -0
- package/dist/components/workspace/admin-shell/layout.d.ts +120 -0
- package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout.js +402 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +6 -4
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +14 -4
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +59 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +146 -2
- package/dist/components/workspace/admin-shell/types.d.ts +52 -0
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/index.d.ts +6 -1
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +5 -0
- package/dist/i18n/server.d.ts +1 -0
- package/dist/i18n/server.d.ts.map +1 -1
- package/dist/i18n/server.js +1 -0
- package/dist/i18n/strings.sortable.d.ts +13 -0
- package/dist/i18n/strings.sortable.d.ts.map +1 -0
- package/dist/i18n/strings.sortable.js +13 -0
- package/dist/i18n/strings.workspace.d.ts +13 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +13 -0
- package/package.json +26 -15
package/README.md
CHANGED
|
@@ -32,6 +32,24 @@ pipeline (`opportunity.stage`) without importing `@happyvertical/smrt-projects`.
|
|
|
32
32
|
|
|
33
33
|
Svelte 5 component library for the s-m-r-t framework. Provides UI components, browser AI integration (STT/TTS/LLM with warm cache), a theme system, permission-aware rendering, and module UI registry for agent admin panels.
|
|
34
34
|
|
|
35
|
+
## Sortable and shell layout
|
|
36
|
+
|
|
37
|
+
`@happyvertical/smrt-svelte/sortable` provides `Sortable`, an accessible
|
|
38
|
+
(pointer, touch, keyboard, screen-reader announcements) list of containers and
|
|
39
|
+
items that share Board's drag engine. `AppShell` lets users customize their
|
|
40
|
+
shell: pass `layout` and `onlayoutchange` to own persistence (for example in an
|
|
41
|
+
exported app blueprint), or neither to store it in the user's settings. Mount
|
|
42
|
+
`ShellLayoutEditor` (from `/app` or `/workspace`) on a settings page, and call
|
|
43
|
+
`useShellLayout()` to make the same changes from code or an assistant. See
|
|
44
|
+
`agents/workspace.md`; the pure `ShellLayout` model is published without Svelte
|
|
45
|
+
as `@happyvertical/smrt-svelte/workspace/layout`.
|
|
46
|
+
|
|
47
|
+
```svelte
|
|
48
|
+
<AppShell {nav} {navGroups} layout={stored} onlayoutchange={(next) => (stored = next)}>
|
|
49
|
+
<ShellLayoutEditor />
|
|
50
|
+
</AppShell>
|
|
51
|
+
```
|
|
52
|
+
|
|
35
53
|
## Installation
|
|
36
54
|
|
|
37
55
|
```bash
|
|
@@ -450,10 +468,15 @@ keys the shell renders exactly as before.
|
|
|
450
468
|
sheet, `phone.swipeToClose` closes drawers by swiping. A `path` change on a
|
|
451
469
|
phone closes open drawers. `overlays` is a layer above the edges (and above
|
|
452
470
|
the bottom bar's row) for `PhoneSheet`s and status strips.
|
|
453
|
-
- **Page contracts** on phones: `data-shell-breadcrumbs` is hidden
|
|
454
|
-
`data-shell-page-
|
|
455
|
-
|
|
456
|
-
|
|
471
|
+
- **Page contracts** on phones: `data-shell-breadcrumbs` is hidden only when
|
|
472
|
+
the phone bar contains `data-shell-page-navigation-replacement` (the back
|
|
473
|
+
link in `PhoneTopBar` detail mode declares it). A title alone leaves
|
|
474
|
+
breadcrumb navigation available.
|
|
475
|
+
`data-shell-page-title` is visually hidden only when the phone top bar
|
|
476
|
+
contains `data-shell-page-title-replacement`. `PhoneTopBar` marks its detail
|
|
477
|
+
title automatically; a workspace-name or action-only bar leaves the page
|
|
478
|
+
heading visible. Custom bars should mark only an actual replacement title.
|
|
479
|
+
`data-shell-tabs` sticks under the top bar and slides away with it. smrt-ui's `PageHeader` renders the first two.
|
|
457
480
|
- **Page trail**: `shellPageTrailFor({ path, homeHref, homeTitle, navItems,
|
|
458
481
|
parents })` is the one source for a page's ancestors: section homes (home
|
|
459
482
|
and top-level nav pages) have none; other pages get the home, the nav items
|
|
@@ -481,6 +504,15 @@ keys the shell renders exactly as before.
|
|
|
481
504
|
- **Nav attention**: a `ShellNavItem` with `attention: true` (or a string
|
|
482
505
|
label) shows a dot in `TenantNav`, over the icon when collapsed, and
|
|
483
506
|
announces the label ("Needs attention" by default) inside the link.
|
|
507
|
+
- **Nav action**: a `ShellNavItem` with `action: { href, label, icon?,
|
|
508
|
+
visibility? }` renders a separate icon-only link after the item's link
|
|
509
|
+
(`aria-label`/tooltip = `label`, default settings-gear icon,
|
|
510
|
+
`aria-current="page"` on its own page). `visibility: 'active'` omits the link
|
|
511
|
+
from the DOM unless the item's section is current: for a `ShellNavGroup`
|
|
512
|
+
item, any item of the group, their children, or any action href is current;
|
|
513
|
+
for a top-level item, the item, its children, or its action href. A collapsed
|
|
514
|
+
nav renders no actions (the action page stays reachable by its route).
|
|
515
|
+
`AppShell` passes `nav`/`navGroups` through unchanged.
|
|
484
516
|
- **Public region ids** (`ADMIN_SHELL_REGION_IDS`): `smrt-admin-shell-header`,
|
|
485
517
|
`smrt-admin-shell-{top,left,right,bottom}-panel`, and
|
|
486
518
|
`smrt-admin-shell-main`, which is the page scroller.
|
|
@@ -505,7 +537,7 @@ importable, even if it appears in `dist/`.
|
|
|
505
537
|
| `@happyvertical/smrt-svelte/forms` | Form inputs (TextInput, Select, MoneyInput, DateTimeInput, Toggle, etc.) |
|
|
506
538
|
| `@happyvertical/smrt-svelte/settings` | Server-paged settings search, selection, and list/detail layout (`SettingsCatalog`, `paginateSettingsCatalog`) |
|
|
507
539
|
| `@happyvertical/smrt-svelte/workspace` | AdminShell, ShellState, tenant nav, focus tools, settings, activities, and system/app panels |
|
|
508
|
-
| `@happyvertical/smrt-svelte/app` | `AppShell` (Provider + themes + AdminShell + nav/dock slots), `OwnerSetupForm` (first-run owner setup), `ShellSettingsPage`, `RuntimeDiagnosticsWebMcp` |
|
|
540
|
+
| `@happyvertical/smrt-svelte/app` | `AppShell` (Provider + themes + AdminShell + nav/dock slots, `dockToggles` header buttons), `OwnerSetupForm` (first-run owner setup), `ShellSettingsPage`, `RuntimeDiagnosticsWebMcp` |
|
|
509
541
|
| `@happyvertical/smrt-svelte/app/runtime-diagnostics` | Svelte-free diagnostics WebMCP registration and its tool name/endpoint constants, importable from server routes |
|
|
510
542
|
| `@happyvertical/smrt-svelte/workspace/legacy` | Opt-in ToolsDock compatibility surface for applications migrating to AdminShell |
|
|
511
543
|
| `@happyvertical/smrt-svelte/workspace/server` | Server-side workspace helpers (Node only) |
|
|
@@ -555,7 +587,7 @@ clears it.
|
|
|
555
587
|
|
|
556
588
|
### Components by Category
|
|
557
589
|
|
|
558
|
-
**Forms**: `AddressInput`, `CheckboxInput`, `DateRangeInput`, `DateTimeInput`, `FileUpload`, `Form`, `FormGroup`, `FormMicButton`, `Input`, `MeasurementInput`, `MoneyInput`, `NumberInput`, `PhoneInput`, `SearchInput`, `Select`, `SelectInput`, `Textarea`, `TextareaInput`, `TextInput`, `Toggle`
|
|
590
|
+
**Forms**: `AddressInput`, `CheckboxInput`, `DateRangeInput`, `DateTimeInput`, `FileUpload`, `Form`, `FormGroup`, `FormMicButton`, `Input`, `MeasurementInput`, `MoneyInput`, `NumberInput`, `PhoneInput`, `RelationInput`, `SearchInput`, `Select`, `SelectInput`, `Textarea`, `TextareaInput`, `TextInput`, `Toggle`
|
|
559
591
|
|
|
560
592
|
**Layout**: `Container`, `EmptyState`, `Footer`, `Grid`, `Header`, `Masthead`, `PageHeader`, `SummaryCard`
|
|
561
593
|
|
package/agents/components.md
CHANGED
|
@@ -15,10 +15,11 @@ the top-of-stack, domain-aware pieces:
|
|
|
15
15
|
| Category | Components |
|
|
16
16
|
|----------|------------|
|
|
17
17
|
| AI | `Provider`, `AILoadingOverlay`, `CapabilityGate`, `DownloadProgress`, `STTTest`, `VoiceInput` |
|
|
18
|
-
| Forms (`/forms`) | `TextInput`, `Select`, `MoneyInput`, `DateTimeInput`, `Toggle`, `FileUpload`, `AddressInput`, + more (AI-wired inputs use the hooks/browser-ai here) |
|
|
18
|
+
| Forms (`/forms`) | `TextInput`, `Select`, `MoneyInput`, `DateTimeInput`, `Toggle`, `FileUpload`, `AddressInput`, `RelationInput`, + more (AI-wired inputs use the hooks/browser-ai here) |
|
|
19
19
|
| Module | `ModulePanel` |
|
|
20
20
|
| Settings (`/settings`) | `SettingsCatalog`, `paginateSettingsCatalog` |
|
|
21
21
|
| Audit (root export) | `RecordHistory`, `AuditList` — authorized data props; [contract](../../../docs/content/audit-trail.md) |
|
|
22
|
+
| Board (`/board`) / Sortable (`/sortable`) | `Board` (kanban) and `Sortable` (nested lists) share one headless drag engine, `SortableController` — [details](workspace.md#sortable-and-the-board-engine) |
|
|
22
23
|
| Workspace (`/workspace`) | `AdminShell`, `ShellState`, `TenantNav`, focus tools, settings, activities, and system/app panels |
|
|
23
24
|
| Legacy workspace (`/workspace/legacy`) | First-generation `ToolsDock` compatibility surface during AdminShell migration |
|
|
24
25
|
|
|
@@ -49,6 +50,48 @@ migrate domain re-rolls *onto* them rather than build new primitives:
|
|
|
49
50
|
flow.
|
|
50
51
|
- **`Card`** (`./ui`) — the standard surface/container; retire local card CSS.
|
|
51
52
|
|
|
53
|
+
### `RelationInput` (`./forms`, #3600)
|
|
54
|
+
|
|
55
|
+
Searchable single-select for relation (foreign-key) fields, a thin wrapper over
|
|
56
|
+
the smrt-ui `Combobox`. It is presentation only: the caller supplies the data,
|
|
57
|
+
so the same lookup an assistant uses to turn "Acme" into a record id backs the
|
|
58
|
+
picker. Option shape is `RelationOption` — `{ id, label, detail? }`.
|
|
59
|
+
|
|
60
|
+
- **Props.** `name`, `label`, `value` (bindable id, `''` = none), `required`,
|
|
61
|
+
`disabled`, `error`, `placeholder`, `description`, `search(query)`, optional
|
|
62
|
+
`resolve(id)` and `onCreate(query)`, plus `debounceMs` (250), `interaction`,
|
|
63
|
+
`onchange` and overridable text (`createLabel`, `clearLabel`, `loadingText`,
|
|
64
|
+
`emptyText`, `errorText`, `resultsText`).
|
|
65
|
+
- **`search`** runs with `''` when the list opens and then debounced per
|
|
66
|
+
keystroke; a response for a superseded query is dropped. A rejected search
|
|
67
|
+
shows `errorText` and the next keystroke retries.
|
|
68
|
+
- **`resolve`** supplies the label of a `value` that has not been searched yet
|
|
69
|
+
(initial value, parent rebind, form reset). Without it such a value shows an
|
|
70
|
+
empty field, never the raw id. A `null` result also leaves the field empty.
|
|
71
|
+
- **`onCreate`** adds a "New ..." button; it receives the text most recently
|
|
72
|
+
searched for and may return the created `RelationOption`, which is selected.
|
|
73
|
+
- **Form integration** is through `Combobox`'s control registration (rich `Form`
|
|
74
|
+
provides the registry), not a `FieldDefinition`: agents see a `combobox`
|
|
75
|
+
control whose value is the id. The form posts the id under `name`.
|
|
76
|
+
- **Known limit.** An agent staging a value through the registered control can
|
|
77
|
+
only choose among the options last listed (the registry matches against
|
|
78
|
+
`options`; `allowCustom` is off), so it cannot write an id it has not
|
|
79
|
+
searched. It fails closed. Writing a resolved id needs an async hook on
|
|
80
|
+
`Combobox` and is tracked separately.
|
|
81
|
+
- **Stale options.** Typing clears the listed options until the new search
|
|
82
|
+
answers, so Enter or a click can never pick a record for an older query.
|
|
83
|
+
- **Clear button** shows only when not `required`, not `disabled`, and a value
|
|
84
|
+
is set; it returns focus to the field.
|
|
85
|
+
- **Accessibility** is the combobox pattern (`role=combobox`, `aria-expanded`,
|
|
86
|
+
`aria-activedescendant`, listbox/option, Arrow/Enter/Escape) plus a polite
|
|
87
|
+
`role=status` live region announcing searching / "N results" / no matches,
|
|
88
|
+
`aria-busy` while fetching, and `aria-invalid` + `aria-describedby` for
|
|
89
|
+
`error` and `description`.
|
|
90
|
+
- Options render `detail` under the label, so the option's accessible name
|
|
91
|
+
includes it. The `Combobox` additions that make this possible (`filter`,
|
|
92
|
+
`onquery`, `status`, `busy`, `invalid`, `describedby`, `optionContent`) are
|
|
93
|
+
generic and default to the previous behaviour.
|
|
94
|
+
|
|
52
95
|
### Import convention (S10 #1415)
|
|
53
96
|
|
|
54
97
|
Domain packages **consume** these primitives; they do not re-roll them. The
|
package/agents/workspace.md
CHANGED
|
@@ -172,6 +172,64 @@ enforce permissions.
|
|
|
172
172
|
|
|
173
173
|
`ActivityTicker` accepts optional `statuses` restricted to queued/running; running-only remains the default. Opting into queued work adds an explicit localized Queued label without progress. Terminal outcomes belong in expanded `ActivityList`, with retention controlled by the app-owned data source.
|
|
174
174
|
|
|
175
|
+
## Shell layout customization
|
|
176
|
+
|
|
177
|
+
Users can show/hide the edge panels and reorder, hide, and move navigation
|
|
178
|
+
sections and items. Source: `workspace/admin-shell/layout.ts` (pure model),
|
|
179
|
+
`layout-controller.svelte.ts` (the API), `ShellLayoutEditor.svelte` (the
|
|
180
|
+
control panel), wired by `app/AppShell.svelte`.
|
|
181
|
+
|
|
182
|
+
- **`ShellLayout`** is a versioned (`version: 1`), JSON-serializable sparse
|
|
183
|
+
delta: `sectionOrder`, `itemOrder` (per section), `hidden` (section and item
|
|
184
|
+
ids), `moved` (item id to section id), `panels` (per edge `visible` /
|
|
185
|
+
`initial`). It is what a host stores or exports (smrt-planner bundles it in
|
|
186
|
+
its app blueprint). `normalizeShellLayout(unknown)` reads untrusted JSON and
|
|
187
|
+
never throws (unknown version yields an empty layout).
|
|
188
|
+
- **Ids.** Sections: `ShellNavGroup.id ?? heading`; items: `ShellNavItem.id ??
|
|
189
|
+
href`; one shared namespace (duplicates get `#2`, `#3`). Set explicit ids
|
|
190
|
+
when headings are translated or hrefs change. The flat `nav` items are the
|
|
191
|
+
implicit first section, `@root` (`SHELL_NAV_ROOT_SECTION_ID`): items can be
|
|
192
|
+
moved in and out, the section itself cannot move or hide.
|
|
193
|
+
- **`applyShellLayout(nav, groups, panels, layout)`** is pure and total. Ids the
|
|
194
|
+
host no longer has are ignored; new host items and sections keep their
|
|
195
|
+
default slots (listed ids permute among the slots they occupy). A hidden
|
|
196
|
+
section hides its items; a section the layout empties is dropped from
|
|
197
|
+
`groups`; an empty layout returns the inputs unchanged. A layout can hide a
|
|
198
|
+
panel but cannot bring back one the host removed (`false` / `initial:
|
|
199
|
+
'hidden'`). Published from the browser-safe `./workspace/layout` subpath (no
|
|
200
|
+
Svelte) as well as `./workspace`.
|
|
201
|
+
- **Persistence is host-owned.** `AppShell` takes `layout` and `onlayoutchange`:
|
|
202
|
+
with `layout` the host is the single source of truth (pass `null` for none);
|
|
203
|
+
with only `onlayoutchange` the shell keeps the value in memory and reports
|
|
204
|
+
edits; with neither, the layout lives in the settings-core user tier
|
|
205
|
+
(`ShellSettingsDelta.layout`, via `storageKey`). `AppShell` now creates the
|
|
206
|
+
`ShellState` itself and passes it to `AdminShell`, so panel overrides reach
|
|
207
|
+
it (`ShellState.setLayoutPanels`); a layout-hidden edge ignores toggles,
|
|
208
|
+
hotkeys, viewport defaults, and the restore opener.
|
|
209
|
+
- **Layout API** (`useShellLayout()` / `tryUseShellLayout()` under an
|
|
210
|
+
`AppShell`, also `ShellLayoutController`): `moveSection(id, toIndex)`,
|
|
211
|
+
`moveItem(id, sectionId, toIndex?)`, `hide(id)`, `show(id)`, `setPanel(edge,
|
|
212
|
+
{ visible?, initial? })`, `reset()`, each returning whether anything changed,
|
|
213
|
+
plus readers (`layout`, `sections`, `panels`, `applied`, `customized`,
|
|
214
|
+
`isHidden`). An assistant calls the same methods the editor does. The
|
|
215
|
+
context is separate from `useAdminShell()` because `AppShell`, not
|
|
216
|
+
`AdminShell`, owns the navigation.
|
|
217
|
+
- **`ShellLayoutEditor`**: `controller` (default: context), `preview` (default
|
|
218
|
+
true), `iconComponent`. Mount it on a settings page or in a dock tool.
|
|
219
|
+
|
|
220
|
+
### Sortable and the Board engine
|
|
221
|
+
|
|
222
|
+
`components/sortable/controller.svelte.ts` is the headless engine extracted from
|
|
223
|
+
`Board`: keyboard pick-up (Space/Enter, arrows, Escape), pointer and native-drag
|
|
224
|
+
moves with a 6px threshold, one in-flight move at a time (a rejected `commit`
|
|
225
|
+
restores and announces), focus return, and announcements as typed events the
|
|
226
|
+
host localizes. `Board` (own markup and `ui.board.*` strings) and `Sortable`
|
|
227
|
+
(`ui.sortable.*`) both use it. `Sortable` renders stacked containers of items
|
|
228
|
+
with a move handle per item, optional `reorderContainers` (a second engine; `fixed`
|
|
229
|
+
containers lead and stay put), a drop marker, and cancel-on-blur; it is
|
|
230
|
+
controlled and never mutates `items`. Item moves report `target.index` after
|
|
231
|
+
removing the item from `source`.
|
|
232
|
+
|
|
175
233
|
## App shell and owner setup (`./app`)
|
|
176
234
|
|
|
177
235
|
`@happyvertical/smrt-svelte/app` (`src/components/app/`) is browser-only. It
|
|
@@ -182,13 +240,30 @@ lives beside, not inside, `./workspace` so the AdminShell barrel stays free of
|
|
|
182
240
|
unchanged), `ThemeProvider`, the theme CSS (`themes/styles/{all,fonts}.css`
|
|
183
241
|
are imported by the component — forgetting them leaves `--smrt-*` unresolved),
|
|
184
242
|
`AdminShell`, `TenantNav` from the consumer's `nav`/`navGroups`/`currentHref`,
|
|
185
|
-
and an `AppScopePanel`. Panels start collapsed as in `AdminShell`; pass
|
|
243
|
+
and an `AppScopePanel`. `ShellNavItem.action` (trailing icon link, optional `visibility: 'active'` = only while the item's section is current; omitted when collapsed) is rendered by `TenantNav` and passed through `AppShell` unchanged. Panels start collapsed as in `AdminShell`; pass
|
|
186
244
|
`config` to change that. `dock` is a snippet rendered inside the shell: a host
|
|
187
245
|
places its assistant there in a `ShellDockTool`, so this package never
|
|
188
246
|
imports `smrt-chat`. It receives the Provider's `DataSurfaceRegistry`
|
|
189
247
|
(`{#snippet dock(registry)}`), the instance mounted routes register on
|
|
190
248
|
when `webmcp` UI is on; pass it to `<AssistantDock {registry} />`. `runtimeDiagnostics` (default false) mounts the
|
|
191
|
-
read-only `smrt.runtime.diagnostics.read` WebMCP tool.
|
|
249
|
+
read-only `smrt.runtime.diagnostics.read` WebMCP tool. `dockToggles`
|
|
250
|
+
(`{ tool, label, icon? }[]`) renders icon buttons at the right of the shell
|
|
251
|
+
header (the AdminShell `header` row, shown only when the prop is non-empty),
|
|
252
|
+
each toggling the `ShellDockTool` with that id: `aria-pressed` and
|
|
253
|
+
`aria-expanded` follow the dock, `aria-controls` is
|
|
254
|
+
`smrt-admin-shell-right-panel`, the label is the tooltip, and `assistant`
|
|
255
|
+
defaults to a chat-bubble icon (`icon: 'chat'`; other text is a glyph). A
|
|
256
|
+
toggle whose tool is not registered yet, or whose dock is not `available`
|
|
257
|
+
(the right edge removed by the host or hidden by the user's layout), is
|
|
258
|
+
`aria-disabled` and `open`/`toggle` return false. Hosts, routes and
|
|
259
|
+
assistants drive the same dock with `useShellDock()` from `./workspace`
|
|
260
|
+
(`{ active, tools, available, has, isOpen, open, close, toggle }`, throws outside a
|
|
261
|
+
shell). Opening moves focus into the dock (first focusable, else the panel);
|
|
262
|
+
closing by Escape, toggle or code returns it to the opener when focus was left
|
|
263
|
+
in the dock. `open(tool, { focus: false })` skips the focus move. The
|
|
264
|
+
dock-control state lives on `ShellState` (`openDockTool`, `toggleDockTool`,
|
|
265
|
+
`closeFocusTool`, `openFocusToolId`). Playground:
|
|
266
|
+
`playground/src/routes/app-shell-dock-toggles`. Server code that
|
|
192
267
|
lists that tool (the diagnostics route's `toolNames`) imports
|
|
193
268
|
`RUNTIME_DIAGNOSTICS_WEBMCP_TOOL_NAME` from `./app/runtime-diagnostics`,
|
|
194
269
|
which is plain TypeScript; the `./app` barrel loads Svelte components and
|
|
@@ -5,23 +5,37 @@ import {
|
|
|
5
5
|
ThemeProvider,
|
|
6
6
|
} from '@happyvertical/smrt-ui/themes';
|
|
7
7
|
import '@happyvertical/smrt-ui/themes/styles/all.css';
|
|
8
|
+
import '@happyvertical/smrt-ui/themes/styles/base.css';
|
|
8
9
|
import '@happyvertical/smrt-ui/themes/styles/fonts.css';
|
|
9
10
|
import type { DataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
10
11
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
11
|
-
import type
|
|
12
|
+
import { type Snippet, untrack } from 'svelte';
|
|
12
13
|
import { M } from '../../i18n/strings.workspace.js';
|
|
13
14
|
import Provider from '../../Provider.svelte';
|
|
14
15
|
import type { User } from '../../state/app-state.js';
|
|
15
16
|
import type { WebMcpProviderConfig } from '../../web/webmcp-provider.js';
|
|
16
17
|
import AdminShell from '../workspace/admin-shell/AdminShell.svelte';
|
|
17
18
|
import AppScopePanel from '../workspace/admin-shell/AppScopePanel.svelte';
|
|
19
|
+
import {
|
|
20
|
+
applyShellLayout,
|
|
21
|
+
normalizeShellLayout,
|
|
22
|
+
type ShellLayout,
|
|
23
|
+
} from '../workspace/admin-shell/layout.js';
|
|
24
|
+
import { setShellLayout } from '../workspace/admin-shell/layout-context.js';
|
|
25
|
+
import { ShellLayoutController } from '../workspace/admin-shell/layout-controller.svelte.js';
|
|
26
|
+
import { resolveShellConfig } from '../workspace/admin-shell/settings.js';
|
|
27
|
+
import { createShellState } from '../workspace/admin-shell/state.svelte.js';
|
|
18
28
|
import TenantNav from '../workspace/admin-shell/TenantNav.svelte';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
import {
|
|
30
|
+
PANEL_EDGES,
|
|
31
|
+
type PanelEdge,
|
|
32
|
+
type ShellNavGroup,
|
|
33
|
+
type ShellNavItem,
|
|
34
|
+
type ShellPanelDefaults,
|
|
23
35
|
} from '../workspace/admin-shell/types.js';
|
|
24
36
|
import DockSlot from './DockSlot.svelte';
|
|
37
|
+
import DockToggles from './DockToggles.svelte';
|
|
38
|
+
import type { DockToggle } from './dock-toggle.js';
|
|
25
39
|
import RuntimeDiagnosticsWebMcp from './RuntimeDiagnosticsWebMcp.svelte';
|
|
26
40
|
|
|
27
41
|
interface Props {
|
|
@@ -53,6 +67,17 @@ interface Props {
|
|
|
53
67
|
runtimeDiagnostics?: boolean;
|
|
54
68
|
/** Initial panel states and presentation; forwarded to AdminShell. */
|
|
55
69
|
config?: ShellPanelDefaults;
|
|
70
|
+
/**
|
|
71
|
+
* The user's layout customization (panel visibility, navigation order and
|
|
72
|
+
* visibility), applied over `nav`, `navGroups` and `config`. Passing it
|
|
73
|
+
* makes the host the owner: the shell never stores it, and reports edits
|
|
74
|
+
* through `onlayoutchange` for the host to pass back. Pass `null` for "no
|
|
75
|
+
* customization yet". Omit both `layout` and `onlayoutchange` to let the
|
|
76
|
+
* shell keep the layout in the user's settings (`storageKey`).
|
|
77
|
+
*/
|
|
78
|
+
layout?: ShellLayout | null;
|
|
79
|
+
/** Called with the next layout whenever the user (or an assistant) edits it. */
|
|
80
|
+
onlayoutchange?: (layout: ShellLayout) => void;
|
|
56
81
|
/** Theme preset applied by the ThemeProvider. */
|
|
57
82
|
preset?: ThemePreset;
|
|
58
83
|
/** Light, dark or system color scheme; the user's persisted choice wins. */
|
|
@@ -68,6 +93,15 @@ interface Props {
|
|
|
68
93
|
* register on it when the `webmcp` UI is enabled (`webmcp: true`).
|
|
69
94
|
*/
|
|
70
95
|
dock?: Snippet<[DataSurfaceRegistry]>;
|
|
96
|
+
/**
|
|
97
|
+
* Icon buttons rendered at the right of the shell header, each toggling
|
|
98
|
+
* the `ShellDockTool` with the same id open and closed. `aria-pressed` and
|
|
99
|
+
* `aria-expanded` follow the dock, focus moves into the dock when it
|
|
100
|
+
* opens and back to the button when it closes (Escape included). The
|
|
101
|
+
* `assistant` tool defaults to a chat-bubble icon. Hosts and assistants can
|
|
102
|
+
* drive the same dock from code with `useShellDock()`.
|
|
103
|
+
*/
|
|
104
|
+
dockToggles?: DockToggle[];
|
|
71
105
|
children: Snippet;
|
|
72
106
|
}
|
|
73
107
|
|
|
@@ -86,20 +120,99 @@ let {
|
|
|
86
120
|
permissions = [],
|
|
87
121
|
runtimeDiagnostics = false,
|
|
88
122
|
config,
|
|
123
|
+
layout,
|
|
124
|
+
onlayoutchange,
|
|
89
125
|
preset = 'smrt',
|
|
90
126
|
colorScheme = 'system',
|
|
91
127
|
appPanelDocs,
|
|
92
128
|
dock,
|
|
129
|
+
dockToggles = [],
|
|
93
130
|
children,
|
|
94
131
|
}: Props = $props();
|
|
95
132
|
const { t } = useI18n();
|
|
96
|
-
|
|
133
|
+
|
|
134
|
+
// The shell state lives here (not inside AdminShell) so the layout can reach
|
|
135
|
+
// it: panel overrides apply to it, and the user's layout is stored in it when
|
|
136
|
+
// the host does not own persistence.
|
|
137
|
+
const shell = untrack(() =>
|
|
138
|
+
createShellState({
|
|
139
|
+
config,
|
|
140
|
+
storageKey,
|
|
141
|
+
layoutPanels: normalizeShellLayout(layout).panels,
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
// Hosts that pass only `onlayoutchange` own persistence but not the value:
|
|
145
|
+
// keep it in memory so edits show at once.
|
|
146
|
+
let localLayout = $state<ShellLayout | undefined>();
|
|
147
|
+
const controlled = $derived(layout !== undefined);
|
|
148
|
+
const effectiveLayout = $derived(
|
|
149
|
+
normalizeShellLayout(
|
|
150
|
+
controlled ? layout : onlayoutchange ? localLayout : shell.settings.layout,
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
const applied = $derived(
|
|
154
|
+
applyShellLayout(nav, navGroups, config, effectiveLayout),
|
|
155
|
+
);
|
|
156
|
+
const hasNav = $derived(applied.nav.length > 0 || applied.groups.length > 0);
|
|
157
|
+
|
|
158
|
+
// Starting-state edits made through the layout API, applied once the layout
|
|
159
|
+
// carrying them is in force (or dropped if the host answered differently).
|
|
160
|
+
const pendingStart = new Map<PanelEdge, 'collapsed' | 'expanded'>();
|
|
161
|
+
$effect(() => {
|
|
162
|
+
const panels = effectiveLayout.panels ?? {};
|
|
163
|
+
shell.setLayoutPanels(panels);
|
|
164
|
+
for (const [edge, state] of pendingStart) {
|
|
165
|
+
const initial =
|
|
166
|
+
panels[edge]?.initial ?? resolveShellConfig(config).panels[edge].initial;
|
|
167
|
+
if (initial === state) shell.setPanelStart(edge, state);
|
|
168
|
+
}
|
|
169
|
+
pendingStart.clear();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
setShellLayout(
|
|
173
|
+
new ShellLayoutController({
|
|
174
|
+
nav: () => nav,
|
|
175
|
+
groups: () => navGroups,
|
|
176
|
+
panels: () => config,
|
|
177
|
+
layout: () => effectiveLayout,
|
|
178
|
+
commit(next) {
|
|
179
|
+
// A changed starting state is an explicit edit: show it now. It waits
|
|
180
|
+
// for the layout to actually change (the host may reject the edit) and
|
|
181
|
+
// is never done for a loaded layout (hydration, a late `layout` prop),
|
|
182
|
+
// so loading cannot erase the user's own open/closed toggle.
|
|
183
|
+
for (const edge of PANEL_EDGES) {
|
|
184
|
+
const before = effectiveLayout.panels?.[edge]?.initial;
|
|
185
|
+
const after = next.panels?.[edge]?.initial;
|
|
186
|
+
if (before === after) continue;
|
|
187
|
+
const state = after ?? resolveShellConfig(config).panels[edge].initial;
|
|
188
|
+
if (state === 'collapsed' || state === 'expanded') {
|
|
189
|
+
pendingStart.set(edge, state);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!controlled) {
|
|
193
|
+
if (onlayoutchange) localLayout = next;
|
|
194
|
+
else shell.setLayout(next);
|
|
195
|
+
}
|
|
196
|
+
onlayoutchange?.(next);
|
|
197
|
+
},
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
97
200
|
</script>
|
|
98
201
|
|
|
202
|
+
{#snippet dockHeader()}
|
|
203
|
+
<DockToggles toggles={dockToggles} />
|
|
204
|
+
{/snippet}
|
|
205
|
+
|
|
99
206
|
<Provider {webmcp} {user} {permissions}>
|
|
100
207
|
<RuntimeDiagnosticsWebMcp enabled={runtimeDiagnostics} />
|
|
101
208
|
<ThemeProvider {preset} {colorScheme} persist={true}>
|
|
102
|
-
<AdminShell
|
|
209
|
+
<AdminShell
|
|
210
|
+
{title}
|
|
211
|
+
{subtitle}
|
|
212
|
+
state={shell}
|
|
213
|
+
path={currentHref}
|
|
214
|
+
header={dockToggles.length > 0 ? dockHeader : undefined}
|
|
215
|
+
>
|
|
103
216
|
{#snippet appPanel()}
|
|
104
217
|
<AppScopePanel
|
|
105
218
|
appName={title}
|
|
@@ -119,8 +232,8 @@ const hasNav = $derived(nav.length > 0 || navGroups.length > 0);
|
|
|
119
232
|
{#snippet tenantPanel()}
|
|
120
233
|
{#if hasNav}
|
|
121
234
|
<TenantNav
|
|
122
|
-
items={nav}
|
|
123
|
-
groups={
|
|
235
|
+
items={applied.nav}
|
|
236
|
+
groups={applied.groups}
|
|
124
237
|
{currentHref}
|
|
125
238
|
aria-label={t(M['ui.app_shell.navigation'])}
|
|
126
239
|
/>
|
|
@@ -136,21 +249,6 @@ const hasNav = $derived(nav.length > 0 || navGroups.length > 0);
|
|
|
136
249
|
</Provider>
|
|
137
250
|
|
|
138
251
|
<style>
|
|
139
|
-
:global(*),
|
|
140
|
-
:global(*::before),
|
|
141
|
-
:global(*::after) {
|
|
142
|
-
box-sizing: border-box;
|
|
143
|
-
}
|
|
144
|
-
:global(html),
|
|
145
|
-
:global(body) {
|
|
146
|
-
min-height: 100%;
|
|
147
|
-
margin: 0;
|
|
148
|
-
}
|
|
149
|
-
:global(body) {
|
|
150
|
-
background: var(--smrt-color-background);
|
|
151
|
-
color: var(--smrt-color-on-background);
|
|
152
|
-
font-family: var(--smrt-font-family, Inter, system-ui, sans-serif);
|
|
153
|
-
}
|
|
154
252
|
a {
|
|
155
253
|
color: var(--smrt-color-primary);
|
|
156
254
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type ColorScheme, type ThemePreset } from '@happyvertical/smrt-ui/themes';
|
|
2
2
|
import '@happyvertical/smrt-ui/themes/styles/all.css';
|
|
3
|
+
import '@happyvertical/smrt-ui/themes/styles/base.css';
|
|
3
4
|
import '@happyvertical/smrt-ui/themes/styles/fonts.css';
|
|
4
5
|
import type { DataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
5
|
-
import type
|
|
6
|
+
import { type Snippet } from 'svelte';
|
|
6
7
|
import type { User } from '../../state/app-state.js';
|
|
7
8
|
import type { WebMcpProviderConfig } from '../../web/webmcp-provider.js';
|
|
8
|
-
import
|
|
9
|
+
import { type ShellLayout } from '../workspace/admin-shell/layout.js';
|
|
10
|
+
import { type ShellNavGroup, type ShellNavItem, type ShellPanelDefaults } from '../workspace/admin-shell/types.js';
|
|
11
|
+
import type { DockToggle } from './dock-toggle.js';
|
|
9
12
|
interface Props {
|
|
10
13
|
/** Application name shown in the shell brand and app panel. */
|
|
11
14
|
title?: string;
|
|
@@ -35,6 +38,17 @@ interface Props {
|
|
|
35
38
|
runtimeDiagnostics?: boolean;
|
|
36
39
|
/** Initial panel states and presentation; forwarded to AdminShell. */
|
|
37
40
|
config?: ShellPanelDefaults;
|
|
41
|
+
/**
|
|
42
|
+
* The user's layout customization (panel visibility, navigation order and
|
|
43
|
+
* visibility), applied over `nav`, `navGroups` and `config`. Passing it
|
|
44
|
+
* makes the host the owner: the shell never stores it, and reports edits
|
|
45
|
+
* through `onlayoutchange` for the host to pass back. Pass `null` for "no
|
|
46
|
+
* customization yet". Omit both `layout` and `onlayoutchange` to let the
|
|
47
|
+
* shell keep the layout in the user's settings (`storageKey`).
|
|
48
|
+
*/
|
|
49
|
+
layout?: ShellLayout | null;
|
|
50
|
+
/** Called with the next layout whenever the user (or an assistant) edits it. */
|
|
51
|
+
onlayoutchange?: (layout: ShellLayout) => void;
|
|
38
52
|
/** Theme preset applied by the ThemeProvider. */
|
|
39
53
|
preset?: ThemePreset;
|
|
40
54
|
/** Light, dark or system color scheme; the user's persisted choice wins. */
|
|
@@ -50,6 +64,15 @@ interface Props {
|
|
|
50
64
|
* register on it when the `webmcp` UI is enabled (`webmcp: true`).
|
|
51
65
|
*/
|
|
52
66
|
dock?: Snippet<[DataSurfaceRegistry]>;
|
|
67
|
+
/**
|
|
68
|
+
* Icon buttons rendered at the right of the shell header, each toggling
|
|
69
|
+
* the `ShellDockTool` with the same id open and closed. `aria-pressed` and
|
|
70
|
+
* `aria-expanded` follow the dock, focus moves into the dock when it
|
|
71
|
+
* opens and back to the button when it closes (Escape included). The
|
|
72
|
+
* `assistant` tool defaults to a chat-bubble icon. Hosts and assistants can
|
|
73
|
+
* drive the same dock from code with `useShellDock()`.
|
|
74
|
+
*/
|
|
75
|
+
dockToggles?: DockToggle[];
|
|
53
76
|
children: Snippet;
|
|
54
77
|
}
|
|
55
78
|
declare const AppShell: import("svelte").Component<Props, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppShell.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/app/AppShell.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAEjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,8CAA8C,CAAC;AACtD,OAAO,gDAAgD,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"AppShell.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/app/AppShell.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAEjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,8CAA8C,CAAC;AACtD,OAAO,+CAA+C,CAAC;AACvD,OAAO,gDAAgD,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,QAAQ,CAAC;AAG/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGzE,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,oCAAoC,CAAC;AAM5C,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,UAAU,KAAK;IACb,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC;IACrB,uEAAuE;IACvE,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAC5B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;IACxC,yFAAyF;IACzF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,+FAA+F;IAC/F,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sEAAsE;IACtE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,gFAAgF;IAChF,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC/C,iDAAiD;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACtC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;CACnB;AA+JD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useShellDock } from '../workspace/admin-shell/dock.js';
|
|
3
|
+
import type { DockToggle } from './dock-toggle.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
toggles: DockToggle[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { toggles }: Props = $props();
|
|
10
|
+
const dock = useShellDock();
|
|
11
|
+
|
|
12
|
+
function iconFor(toggle: DockToggle): string | undefined {
|
|
13
|
+
return toggle.icon ?? (toggle.tool === 'assistant' ? 'chat' : undefined);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function press(event: MouseEvent, toggle: DockToggle): void {
|
|
17
|
+
if (!dock.has(toggle.tool) || !dock.available) return;
|
|
18
|
+
dock.toggle(toggle.tool, {
|
|
19
|
+
returnFocus: event.currentTarget as HTMLElement,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div class="smrt-dock-toggles" data-testid="dock-toggles">
|
|
25
|
+
{#each toggles as toggle (toggle.tool)}
|
|
26
|
+
{@const registered = dock.has(toggle.tool) && dock.available}
|
|
27
|
+
{@const open = dock.isOpen(toggle.tool)}
|
|
28
|
+
{@const icon = iconFor(toggle)}
|
|
29
|
+
<!-- raw-primitive-allow: shell chrome toggle, not a content button -->
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class="smrt-dock-toggles__button"
|
|
33
|
+
class:active={open}
|
|
34
|
+
data-dock-tool={toggle.tool}
|
|
35
|
+
aria-label={toggle.label}
|
|
36
|
+
title={toggle.label}
|
|
37
|
+
aria-pressed={open}
|
|
38
|
+
aria-expanded={open}
|
|
39
|
+
aria-controls="smrt-admin-shell-right-panel"
|
|
40
|
+
aria-disabled={registered ? undefined : true}
|
|
41
|
+
onclick={(event) => press(event, toggle)}
|
|
42
|
+
>
|
|
43
|
+
{#if icon === 'chat'}
|
|
44
|
+
<svg
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
width="20"
|
|
47
|
+
height="20"
|
|
48
|
+
fill="none"
|
|
49
|
+
stroke="currentColor"
|
|
50
|
+
stroke-width="2"
|
|
51
|
+
stroke-linecap="round"
|
|
52
|
+
stroke-linejoin="round"
|
|
53
|
+
aria-hidden="true"
|
|
54
|
+
>
|
|
55
|
+
<path d="M21 11.5a8.5 8.5 0 0 1-12.4 7.5L3 20.5l1.6-5A8.5 8.5 0 1 1 21 11.5z" />
|
|
56
|
+
</svg>
|
|
57
|
+
{:else}
|
|
58
|
+
<span aria-hidden="true">{icon ?? toggle.label.charAt(0)}</span>
|
|
59
|
+
{/if}
|
|
60
|
+
</button>
|
|
61
|
+
{/each}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.smrt-dock-toggles {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
gap: var(--smrt-spacing-1);
|
|
69
|
+
margin-inline-start: auto;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.smrt-dock-toggles__button {
|
|
73
|
+
display: inline-grid;
|
|
74
|
+
place-items: center;
|
|
75
|
+
inline-size: 2.25rem;
|
|
76
|
+
block-size: 2.25rem;
|
|
77
|
+
padding: 0;
|
|
78
|
+
border: 1px solid transparent;
|
|
79
|
+
border-radius: var(--smrt-radius-md, 0.5rem);
|
|
80
|
+
background: transparent;
|
|
81
|
+
color: var(--smrt-color-on-surface);
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.smrt-dock-toggles__button:hover {
|
|
86
|
+
background: var(--smrt-color-surface-container-high);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.smrt-dock-toggles__button:focus-visible {
|
|
90
|
+
outline: 2px solid var(--smrt-color-primary);
|
|
91
|
+
outline-offset: 2px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.smrt-dock-toggles__button.active {
|
|
95
|
+
background: var(--smrt-color-primary-container, var(--smrt-color-surface-container-high));
|
|
96
|
+
color: var(--smrt-color-on-primary-container, var(--smrt-color-primary));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.smrt-dock-toggles__button[aria-disabled='true'] {
|
|
100
|
+
opacity: 0.5;
|
|
101
|
+
cursor: default;
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DockToggle } from './dock-toggle.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
toggles: DockToggle[];
|
|
4
|
+
}
|
|
5
|
+
declare const DockToggles: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type DockToggles = ReturnType<typeof DockToggles>;
|
|
7
|
+
export default DockToggles;
|
|
8
|
+
//# sourceMappingURL=DockToggles.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DockToggles.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/app/DockToggles.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,UAAU,KAAK;IACb,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AA2CD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A header button that toggles one dock tool. */
|
|
2
|
+
export interface DockToggle {
|
|
3
|
+
/** Id of the `ShellDockTool` to toggle. */
|
|
4
|
+
tool: string;
|
|
5
|
+
/** Accessible name and tooltip. */
|
|
6
|
+
label: string;
|
|
7
|
+
/**
|
|
8
|
+
* Icon name. `chat` (the default for the `assistant` tool) draws a
|
|
9
|
+
* chat bubble; any other text is shown as a glyph. Without an icon the
|
|
10
|
+
* label's first letter stands in.
|
|
11
|
+
*/
|
|
12
|
+
icon?: string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=dock-toggle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dock-toggle.d.ts","sourceRoot":"","sources":["../../../src/components/app/dock-toggle.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,WAAW,UAAU;IACzB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|