@happyvertical/smrt-svelte 0.55.17 → 0.55.19
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/agents/cookbook-shell.md +26 -2
- package/agents/overview-surfaces.md +13 -3
- package/agents/workspace.md +56 -0
- package/dist/components/app/AppShell.svelte +35 -13
- package/dist/components/app/AppShell.svelte.d.ts +13 -5
- package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
- package/dist/components/app/DockToggles.svelte +115 -57
- package/dist/components/app/DockToggles.svelte.d.ts +1 -1
- package/dist/components/app/DockToggles.svelte.d.ts.map +1 -1
- package/dist/components/app/PhoneTabBar.svelte +37 -12
- package/dist/components/app/PhoneTabBar.svelte.d.ts +1 -1
- package/dist/components/app/PhoneTabBar.svelte.d.ts.map +1 -1
- package/dist/components/app/ShellSettingsPage.svelte +2 -2
- package/dist/components/app/ShellSettingsPage.svelte.d.ts +2 -2
- package/dist/components/app/ShellSettingsPage.svelte.d.ts.map +1 -1
- package/dist/components/app/dock-toggle.d.ts +45 -0
- package/dist/components/app/dock-toggle.d.ts.map +1 -1
- package/dist/components/app/dock-toggle.js +26 -1
- package/dist/components/app/index.d.ts +1 -0
- package/dist/components/app/index.d.ts.map +1 -1
- package/dist/components/app/shell-user.d.ts +14 -0
- package/dist/components/app/shell-user.d.ts.map +1 -0
- package/dist/components/app/shell-user.js +1 -0
- package/dist/components/cookbook/CookbookSectionOverview.svelte +22 -3
- package/dist/components/cookbook/CookbookSectionOverview.svelte.d.ts +11 -2
- package/dist/components/cookbook/CookbookSectionOverview.svelte.d.ts.map +1 -1
- package/dist/components/cookbook/CookbookSectionPage.svelte +27 -9
- package/dist/components/cookbook/CookbookSectionPage.svelte.d.ts.map +1 -1
- package/dist/components/cookbook/index.d.ts +3 -1
- package/dist/components/cookbook/index.d.ts.map +1 -1
- package/dist/components/cookbook/index.js +2 -1
- package/dist/components/cookbook/registry.d.ts +18 -4
- package/dist/components/cookbook/registry.d.ts.map +1 -1
- package/dist/components/cookbook/registry.js +40 -2
- package/dist/components/cookbook/types.d.ts +39 -4
- package/dist/components/cookbook/types.d.ts.map +1 -1
- package/dist/components/overview/OverviewGrid.svelte +1 -0
- package/dist/components/overview/index.d.ts +1 -1
- package/dist/components/overview/index.d.ts.map +1 -1
- package/dist/components/overview/types.d.ts +6 -0
- package/dist/components/overview/types.d.ts.map +1 -1
- package/dist/components/overview/widgets/ChartWidget.svelte +158 -18
- package/dist/components/overview/widgets/ChartWidget.svelte.d.ts.map +1 -1
- package/dist/components/overview/widgets/ShortcutsWidget.svelte +32 -3
- package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts.map +1 -1
- package/dist/components/overview/widgets/core.d.ts +9 -1
- package/dist/components/overview/widgets/core.d.ts.map +1 -1
- package/dist/components/overview/widgets/core.js +20 -1
- package/dist/components/overview/widgets/data.d.ts +16 -6
- package/dist/components/overview/widgets/data.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/ActivityToasts.svelte +5 -3
- package/dist/components/workspace/admin-shell/AdminShell.svelte +164 -65
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +37 -6
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +11 -5
- package/dist/components/workspace/admin-shell/ShellIconButton.svelte +7 -6
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +2 -2
- package/dist/components/workspace/admin-shell/ShellNavEditor.svelte +6 -6
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +4 -4
- package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte +9 -7
- package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte +52 -7
- package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts +6 -2
- package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/TenantNav.svelte +33 -9
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +91 -27
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +7 -2
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +9 -0
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +14 -0
- package/dist/i18n/strings.overview.d.ts +5 -0
- package/dist/i18n/strings.overview.d.ts.map +1 -1
- package/dist/i18n/strings.overview.js +5 -0
- package/dist/i18n/strings.workspace.d.ts +2 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +3 -0
- package/package.json +16 -16
package/agents/cookbook-shell.md
CHANGED
|
@@ -18,7 +18,7 @@ sample data source are not part of it.
|
|
|
18
18
|
| Entry | Contents |
|
|
19
19
|
|---|---|
|
|
20
20
|
| `./cookbook/server` | Svelte-free and Node-safe. `loadCookbookApp` (file + manifests + validation), `readInstalledManifests`, `hasCookbook`, and the pure builder/catalog/theme functions and types. Use it in `+layout.server.ts`. |
|
|
21
|
-
| `./cookbook` | Everything above that is browser-safe, plus `CookbookApp`, `CookbookSectionPage`, `applyCookbookTheme`, `createCookbookRegistry`, `createSectionOverview`. |
|
|
21
|
+
| `./cookbook` | Everything above that is browser-safe, plus `CookbookApp`, `CookbookSectionPage`, `CookbookSectionOverview`, `applyCookbookTheme`, `createCookbookRegistry`, `createSectionOverview`. |
|
|
22
22
|
|
|
23
23
|
## Pure core (no DOM, Node-testable)
|
|
24
24
|
|
|
@@ -84,7 +84,8 @@ sample data source are not part of it.
|
|
|
84
84
|
`slotItems`, `settingsHref`, ...), and:
|
|
85
85
|
|
|
86
86
|
- `loaders` (`metric` / `records` / `chart` data loaders; with `metric` and
|
|
87
|
-
`records` the section pages add the count and latest records
|
|
87
|
+
`records` the section pages add the count and latest records; `shortcuts`
|
|
88
|
+
is a `CookbookShortcutsLoader`, below),
|
|
88
89
|
- `widgetContext` (host capabilities for browser-side widget loads),
|
|
89
90
|
- `resolveExport` (a static map of dynamic imports; turns a recipe export
|
|
90
91
|
reference into a component, used for `shell-widget` slot items and recipe
|
|
@@ -107,6 +108,19 @@ registry, recipe widgets and slot items are resolved in effects (a bundled
|
|
|
107
108
|
`resolveExport` is async), so a section page's overview appears after hydration,
|
|
108
109
|
not in the server HTML.
|
|
109
110
|
|
|
111
|
+
## Host shortcuts loader (#3888)
|
|
112
|
+
|
|
113
|
+
`CookbookApp loaders.shortcuts` is `(ctx, defaults: ShortcutEntry[]) =>
|
|
114
|
+
Promise<ShortcutEntry[]>` (`ShortcutEntry = { href, label, icon?, description?,
|
|
115
|
+
count? }`, exported from `./cookbook` and `./overview`).
|
|
116
|
+
`createCookbookRegistry` computes the default entries from the shell's resolved
|
|
117
|
+
sections, then, when the host supplies `shortcuts`, passes them through it
|
|
118
|
+
(`composeShortcutsLoader`); with no host loader the registry's loader is
|
|
119
|
+
unchanged. The host returns the entries to show (add `count`, reorder, add or
|
|
120
|
+
drop). If it throws, that load keeps the defaults (the page never empties
|
|
121
|
+
because a count query failed) unless the load itself was aborted. The shortcuts
|
|
122
|
+
widget renders `count` as "N records" (plural-aware i18n, tabular figures).
|
|
123
|
+
|
|
110
124
|
## `CookbookSectionPage`
|
|
111
125
|
|
|
112
126
|
A section's own page: title, icon and description (the user's renames win over
|
|
@@ -117,6 +131,16 @@ route: `s/[slug]/+page.svelte` renders
|
|
|
117
131
|
`<CookbookSectionPage slug={page.params.slug} />`. It must be inside
|
|
118
132
|
`CookbookApp`.
|
|
119
133
|
|
|
134
|
+
Props (also on `CookbookSectionOverview`, #3888): `presentation?: 'plain' | 'card'`
|
|
135
|
+
(default `'plain'`) is passed to `OverviewGrid`, so the default section body
|
|
136
|
+
lists shortcut entries as unframed rows on the panel surface (the shortcuts
|
|
137
|
+
widget receives the grid's `presentation` and drops its per-entry frame); `'card'`
|
|
138
|
+
keeps the framed look, and editing always keeps its frame.
|
|
139
|
+
`heading?: Snippet<[{ section: CookbookSectionHeadingInfo }]>` replaces the
|
|
140
|
+
default title block (`{ id, title, description?, icon }`) on the page; on a
|
|
141
|
+
standalone `CookbookSectionOverview` it renders above the grid and needs the
|
|
142
|
+
`section` prop.
|
|
143
|
+
|
|
120
144
|
## Using it in an app
|
|
121
145
|
|
|
122
146
|
```ts
|
|
@@ -198,16 +198,26 @@ shape. Data shapes are in `widgets/data.ts`.
|
|
|
198
198
|
| Type | Options | Data |
|
|
199
199
|
|---|---|---|
|
|
200
200
|
| `metric` | `title`, `model`*, `measure`, `field`, `filter` | `{ value, format?, currency?, label?, change?, href? }` (money = integer minor units) |
|
|
201
|
-
| `chart` | `title`, `model`*, `groupBy`, `measure`, `field`, `period`, `style` (bar/line), `filter` | `{ points: [{ label, value }], format?, currency?, href? }`, one measure of a runtime report |
|
|
201
|
+
| `chart` | `title`, `model`*, `groupBy`, `measure`, `field`, `period`, `style` (bar/line/table/pie/stat), `filter` | `{ points: [{ label, value }], format?, currency?, href?, delta? }`, one measure of a runtime report |
|
|
202
202
|
| `records` | `title`, `model`*, `sort`, `direction`, `limit`, `filter` | `{ rows: [{ id, title, subtitle?, meta?, href? }], total?, href? }`; the loader chooses the title field and the visible records |
|
|
203
|
-
| `shortcuts` | `title`, `section` | `{ items: [{ id, label, href, icon?, description? }] }
|
|
203
|
+
| `shortcuts` | `title`, `section` | `{ items: [{ id, label, href, icon?, description?, count? }] }` (`count` draws as "N records"; widgets also receive the grid's `presentation` and the shortcuts widget drops its entry frame under `plain`); `shortcutsFromNav(sections, sectionId)` maps `resolveShellNavModel` / `useShellLayout().sections` (visible entries, user renames) |
|
|
204
204
|
| `note` | `title`, `body` (Markdown) | none |
|
|
205
205
|
|
|
206
206
|
`note` renders `parseMarkdown` output as elements (headings, paragraphs, lists,
|
|
207
207
|
quotes, fenced code, bold/italic/code/links). There is no `{@html}`; raw HTML is
|
|
208
208
|
literal text and `safeHref` limits links to http(s), mailto, same-site paths and
|
|
209
209
|
fragments. Record and shortcut hrefs pass `safeHref` too. Charts carry their
|
|
210
|
-
numbers as text (inline for bars, a visually hidden table for the line
|
|
210
|
+
numbers as text (inline for bars, a visually hidden table for the line and the
|
|
211
|
+
donut, a visible table for `table`, the value itself for `stat`).
|
|
212
|
+
|
|
213
|
+
Chart styles (#3888, the renderer half of #3878's runtime-report chart hints):
|
|
214
|
+
`table` is a plain table of the series (tabular figures); `pie` is an SVG donut
|
|
215
|
+
with a legend (value and share) and a visually hidden data table, slices cycling
|
|
216
|
+
through the primary/secondary/tertiary/info tokens then their containers;
|
|
217
|
+
`stat` is the last point as a headline value with its change (`delta`, percent,
|
|
218
|
+
else derived from the last two points). Motion honours `prefers-reduced-motion`.
|
|
219
|
+
The chart option schema is **version 2**: v1 (`style` bar or line, or no version)
|
|
220
|
+
migrates by identity (`migrateChartOptions`), so saved overviews keep validating.
|
|
211
221
|
|
|
212
222
|
## Recipe widgets (`recipe-widgets.ts`)
|
|
213
223
|
|
package/agents/workspace.md
CHANGED
|
@@ -226,6 +226,52 @@ dialog and the global shortcut, so it mounts once. Planner-style hosts that
|
|
|
226
226
|
rendered their own `header.end` assistant toggle should pass `dockToggles`
|
|
227
227
|
(`dock:assistant`) and `user` instead.
|
|
228
228
|
|
|
229
|
+
Async sign-out (#3860): `onSignOut?: () => void | Promise<void>` (AppShell,
|
|
230
|
+
`WorkspaceAccountMenu`, `ShellSettingsPage`/`ShellSettingsPanel`) is awaited.
|
|
231
|
+
While pending the control is busy (`aria-busy`) and disabled, so a second
|
|
232
|
+
activation never fires the callback again; on success the account menu closes;
|
|
233
|
+
on rejection the menu (or Account section) stays as it was and shows an inline
|
|
234
|
+
`role="alert"` "Couldn't sign out. Try again." (`ui.shell_settings.sign_out_failed`).
|
|
235
|
+
A rejection is caught, never an unhandled rejection; the control is usable again
|
|
236
|
+
for a retry. `signOutHref` links are unchanged.
|
|
237
|
+
|
|
238
|
+
Unrendered regions in settings (#3860): `ShellState.regionsRendered:
|
|
239
|
+
Record<PanelEdge, boolean>` (read-only for consumers; `AdminShell` writes it
|
|
240
|
+
from the same `topRendered`/`bottomRendered`/`rightRendered`/`isEdgeShown('left')`
|
|
241
|
+
logic that collapses empty bands and edges, default all `true`) says whether a
|
|
242
|
+
region is actually on screen, distinct from the user's visibility choice. The
|
|
243
|
+
Layout rows in `ShellSettingsPanel` list a region when it is rendered, or when
|
|
244
|
+
the user hid it (so Show can always restore it); a shown region that is not
|
|
245
|
+
rendered (empty band, edge not presented at this width) has no row. The
|
|
246
|
+
"cannot hide the last region" guard still counts every shown region. In edit
|
|
247
|
+
mode every band renders, so every available region is listed.
|
|
248
|
+
|
|
249
|
+
`user` prop type (#3860): `AppShell`'s `user` is `ShellUser` (exported from
|
|
250
|
+
`./app`): `null` or `{ id: string | null | undefined; email?; name?; avatarUrl?
|
|
251
|
+
(string | null) }`, all readonly. The runtime's `SessionLayoutUser`
|
|
252
|
+
(`data.session.user`) and a full `User` both assign without a cast
|
|
253
|
+
(`__tests__/shell-user.test.ts` proves it with `expectTypeOf`). `id` stays
|
|
254
|
+
required but accepts `null`/`undefined` because an unsaved `User` carries
|
|
255
|
+
that. The shell reads `name`, `email` and `avatarUrl` for the account menu
|
|
256
|
+
(the avatar now shows) and forwards the same object to `Provider` untouched
|
|
257
|
+
(one internal cast; the Provider is typed for the full `User`), so nothing is
|
|
258
|
+
added or invented on the way.
|
|
259
|
+
|
|
260
|
+
DockToggle fallback (#3860): a `dockToggles` entry also takes `href?`, `ready?`
|
|
261
|
+
(reactive, default `true`), `labelNotReady?` and `onpress?(event, { registered,
|
|
262
|
+
ready }) => boolean | void`. Precedence on activation: (1) `onpress` runs first;
|
|
263
|
+
returning `false` or calling `event.preventDefault()` stops everything (no
|
|
264
|
+
toggle, no navigation). (2) If the tool is registered with `useShellDock()`
|
|
265
|
+
(and the dock is available) and `ready !== false`, the dock toggles as before.
|
|
266
|
+
(3) Otherwise `href` is followed, else nothing happens (the inert
|
|
267
|
+
`aria-disabled` button, unchanged). With an `href` in state 3 the toggle renders
|
|
268
|
+
as an `<a>` (middle-click and open-in-new-tab work) with no `aria-pressed` /
|
|
269
|
+
`aria-expanded`. Its name and tooltip are `labelNotReady`, else "Set up {label}"
|
|
270
|
+
(`ui.dock_toggles.set_up`), whenever it is not ready or links. The phone tab
|
|
271
|
+
bar's dock tab (the first toggle) follows the same resolution through the shared
|
|
272
|
+
`resolveDockToggle` / `runDockTogglePress` helpers in `dock-toggle.ts`; that tab
|
|
273
|
+
becomes a link entry. `PhoneBottomBarItem.onclick` now receives the click event.
|
|
274
|
+
|
|
229
275
|
`ShellLayout.placements?: Record<itemId, ShellSlot>` (additive, still version
|
|
230
276
|
1) overrides default slots. Pure helpers in `layout.ts` (also `./workspace/layout`):
|
|
231
277
|
`placeShellItem(layout, id, slot, defaultSlot?)` (passing the default drops a
|
|
@@ -622,3 +668,13 @@ lives beside, not inside, `./workspace` so the AdminShell barrel stays free of
|
|
|
622
668
|
form is a plain `method="POST"`; pass `enhance` from `$app/forms` to enhance
|
|
623
669
|
it. The token is a hidden field only and is never stored, logged or shown.
|
|
624
670
|
Single-use, loopback-only enforcement stays server-side.
|
|
671
|
+
|
|
672
|
+
## Theme (premium baseline)
|
|
673
|
+
|
|
674
|
+
The shell only consumes theme tokens; CSS and tokens changed, DOM and behaviour did not.
|
|
675
|
+
|
|
676
|
+
- Surface ladder: main and phone top bar on `--smrt-surface-ground`; header, footer and top/bottom bands, corners and the left sidebar on `--smrt-surface-panel`; the dock (right edge), drawers, phone sheet and phone drawers, popovers, toasts and the shortcuts dialog on raised glass (`--smrt-glass-surface-strong`, `--smrt-elevation-3`, drawers and sheet `--smrt-elevation-4`, plus `--smrt-inset-highlight`). Edit-mode strips use `--smrt-surface-sunken`.
|
|
677
|
+
- Region edges are `--smrt-hairline`; no solid outline-variant borders remain in the shell. Edit-mode drop zones and strips use the hairline with `border-style: dashed` and state tokens for fill. The resize separator keeps its own primary affordance.
|
|
678
|
+
- Glass: the header, phone top bar, phone tab bar, dock, drawers, sheet and popovers set `backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation))` over `--smrt-glass-surface` (`-strong` for raised regions). Outside the Glass preset the tokens are opaque with blur 0. `backdrop-filter` makes the element a containing block for fixed descendants; the e2e suite covers the account menu and the floating layout toggle.
|
|
679
|
+
- State layers: rows, tabs, rail buttons, toggles and the account trigger use one `::after` veil (`background: currentColor`, opacity from `--smrt-state-hover` / `-pressed` / `-selected`); transparent icon buttons use the equivalent `color-mix`. The selected nav row keeps its container fill and start indicator under the veil. Disabled uses `--smrt-state-disabled`. Focus rings come from `--smrt-focus-ring-*`; control radius and heights from `--smrt-control-*`.
|
|
680
|
+
- E2E fixture: `ShellFixture` accepts `?theme=glass|material|studio|smrt` and `?scheme=dark` (all presets are imported) for per-preset screenshots.
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
resolvePhoneTabBar,
|
|
71
71
|
} from './phone-tabs.js';
|
|
72
72
|
import RuntimeDiagnosticsWebMcp from './RuntimeDiagnosticsWebMcp.svelte';
|
|
73
|
+
import type { ShellUser } from './shell-user.js';
|
|
73
74
|
import type {
|
|
74
75
|
AppShellAccountMenu,
|
|
75
76
|
AppShellCommandPalette,
|
|
@@ -112,8 +113,12 @@ interface Props {
|
|
|
112
113
|
* default slot (users can still move it in the layout editor).
|
|
113
114
|
*/
|
|
114
115
|
accountMenu?: AppShellAccountMenu;
|
|
115
|
-
/**
|
|
116
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Called when the account menu's Sign out is chosen (wins over
|
|
118
|
+
* `signOutHref`). May be async: the menu awaits it, shows a busy state, and
|
|
119
|
+
* on rejection stays open with an inline error.
|
|
120
|
+
*/
|
|
121
|
+
onSignOut?: () => unknown;
|
|
117
122
|
/**
|
|
118
123
|
* Where the account menu's Sign out link goes when there is no `onSignOut`.
|
|
119
124
|
* A link is a GET: never point it at the runtime's POST-only `/sign-out`
|
|
@@ -136,8 +141,12 @@ interface Props {
|
|
|
136
141
|
commandPalette?: AppShellCommandPalette;
|
|
137
142
|
/** Options for the Provider's WebMCP integration; passed through unchanged. */
|
|
138
143
|
webmcp?: boolean | WebMcpProviderConfig;
|
|
139
|
-
/**
|
|
140
|
-
|
|
144
|
+
/**
|
|
145
|
+
* The signed-in identity (`ShellUser`): the runtime's `SessionLayoutUser`
|
|
146
|
+
* (`data.session.user`) or a full `User` both fit without a cast. It is
|
|
147
|
+
* forwarded to the Provider unchanged; `null` renders the shell signed out.
|
|
148
|
+
*/
|
|
149
|
+
user?: ShellUser;
|
|
141
150
|
/** The user's resolved permission slugs, forwarded to the Provider for permission-gated UI. */
|
|
142
151
|
permissions?: string[];
|
|
143
152
|
/** Register the read-only runtime diagnostics WebMCP tool. */
|
|
@@ -571,11 +580,23 @@ function accountPlacement(
|
|
|
571
580
|
return alignStart ? 'bottom-start' : 'bottom-end';
|
|
572
581
|
}
|
|
573
582
|
const accountIdentity = $derived.by(() => {
|
|
574
|
-
const record = (user ?? {}) as {
|
|
583
|
+
const record = (user ?? {}) as {
|
|
584
|
+
name?: unknown;
|
|
585
|
+
email?: unknown;
|
|
586
|
+
avatarUrl?: unknown;
|
|
587
|
+
};
|
|
575
588
|
const name = typeof record.name === 'string' ? record.name.trim() : '';
|
|
576
589
|
const email = typeof record.email === 'string' ? record.email.trim() : '';
|
|
577
|
-
|
|
590
|
+
const avatarUrl =
|
|
591
|
+
typeof record.avatarUrl === 'string' && record.avatarUrl
|
|
592
|
+
? record.avatarUrl
|
|
593
|
+
: undefined;
|
|
594
|
+
return { name, email, avatarUrl };
|
|
578
595
|
});
|
|
596
|
+
// The Provider is typed for the full `User`. The shell hands it exactly the
|
|
597
|
+
// object the host passed (no fields added or invented), so a minimal session
|
|
598
|
+
// projection reaches it just as it did when the prop was typed as `User`.
|
|
599
|
+
const providerUser = $derived(user as unknown as User | null);
|
|
579
600
|
// The floating toggle sits over the header's end (header visible), the top of
|
|
580
601
|
// the right sidebar (header hidden) or the top of main (neither); reserve that room so it covers no
|
|
581
602
|
// control. AdminShell reads these custom properties.
|
|
@@ -867,6 +888,7 @@ const EDGE_OF_REGION: Record<ShellRegion, PanelEdge> = {
|
|
|
867
888
|
<WorkspaceAccountMenu
|
|
868
889
|
userName={accountIdentity.name || accountIdentity.email}
|
|
869
890
|
email={accountIdentity.email}
|
|
891
|
+
avatarUrl={accountIdentity.avatarUrl}
|
|
870
892
|
{compact}
|
|
871
893
|
{tenantLabel}
|
|
872
894
|
{onSignOut}
|
|
@@ -949,7 +971,7 @@ const EDGE_OF_REGION: Record<ShellRegion, PanelEdge> = {
|
|
|
949
971
|
/>
|
|
950
972
|
{/snippet}
|
|
951
973
|
|
|
952
|
-
<Provider {webmcp} {
|
|
974
|
+
<Provider {webmcp} user={providerUser} {permissions}>
|
|
953
975
|
<RuntimeDiagnosticsWebMcp enabled={runtimeDiagnostics} />
|
|
954
976
|
<ThemeProvider {preset} {colorScheme} persist={true}>
|
|
955
977
|
{#if !chrome}
|
|
@@ -1049,26 +1071,26 @@ const EDGE_OF_REGION: Record<ShellRegion, PanelEdge> = {
|
|
|
1049
1071
|
color: var(--smrt-color-primary);
|
|
1050
1072
|
}
|
|
1051
1073
|
.smrt-layout-toggle { display: inline-flex; align-items: center; gap: var(--smrt-spacing-1); }
|
|
1052
|
-
.smrt-layout-toggle--floating { position: fixed; z-index: 45; inset-block-start: max(var(--smrt-spacing-3), env(safe-area-inset-top)); inset-inline-end: max(var(--smrt-spacing-3), env(safe-area-inset-right)); border-radius: var(--smrt-radius-full); background: var(--smrt-
|
|
1074
|
+
.smrt-layout-toggle--floating { position: fixed; z-index: 45; inset-block-start: max(var(--smrt-spacing-3), env(safe-area-inset-top)); inset-inline-end: max(var(--smrt-spacing-3), env(safe-area-inset-right)); border: var(--smrt-hairline); border-radius: var(--smrt-radius-full); background: var(--smrt-surface-raised); box-shadow: var(--smrt-elevation-3), var(--smrt-inset-highlight); }
|
|
1053
1075
|
.smrt-layout-toggle--floating :global(.smrt-shell-icon-button) { border-radius: var(--smrt-radius-full); }
|
|
1054
1076
|
.smrt-layout-toggle :global(.smrt-shell-icon-button[aria-pressed='true']) { background: var(--smrt-color-primary-container); color: var(--smrt-color-on-primary-container); }
|
|
1055
1077
|
.smrt-app-shell-color-scheme { display: inline-flex; align-items: center; }
|
|
1056
1078
|
.smrt-app-shell-color-scheme :global(.smrt-color-scheme-toggle__switch) { min-block-size: var(--smrt-control-target-min); }
|
|
1057
1079
|
.smrt-app-shell-palette { display: inline-flex; min-inline-size: 0; }
|
|
1058
|
-
.smrt-app-shell-palette[data-search] :global(.smrt-command-palette-trigger) { inline-size: min(28rem, 40vw); justify-content: flex-start; border:
|
|
1080
|
+
.smrt-app-shell-palette[data-search] :global(.smrt-command-palette-trigger) { inline-size: min(28rem, 40vw); justify-content: flex-start; border: var(--smrt-hairline-strong); border-radius: var(--smrt-radius-full); background: var(--smrt-surface-control); color: var(--smrt-color-on-surface-variant); }
|
|
1059
1081
|
.smrt-app-shell-palette[data-search] :global(.smrt-command-palette-trigger__hint) { margin-inline-start: auto; }
|
|
1060
1082
|
.smrt-layout-live { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
|
|
1061
1083
|
.smrt-edit-zone__label { flex: 0 0 auto; color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-label-small-font); white-space: nowrap; }
|
|
1062
|
-
.smrt-edit-item { display: inline-flex; align-items: center; min-inline-size: 0; border:
|
|
1063
|
-
.smrt-edit-item--dragging { opacity:
|
|
1084
|
+
.smrt-edit-item { display: inline-flex; align-items: center; min-inline-size: 0; border: var(--smrt-hairline); border-radius: var(--smrt-control-radius); background: var(--smrt-surface-raised); }
|
|
1085
|
+
.smrt-edit-item--dragging { opacity: var(--smrt-state-disabled); }
|
|
1064
1086
|
@media (prefers-reduced-motion: no-preference) {
|
|
1065
1087
|
.smrt-edit-item { animation: smrt-edit-item-in var(--smrt-duration-short2) var(--smrt-easing-standard) backwards; }
|
|
1066
1088
|
}
|
|
1067
1089
|
@keyframes smrt-edit-item-in { from { opacity: 0; transform: scale(0.96); } }
|
|
1068
1090
|
.smrt-edit-item__body { display: inline-flex; align-items: center; min-inline-size: 0; pointer-events: none; }
|
|
1069
1091
|
.smrt-edit-item__grip { display: inline-grid; place-items: center; flex: 0 0 auto; inline-size: 1.5rem; block-size: 2rem; padding: 0; border: 0; border-radius: var(--smrt-radius-sm); background: transparent; color: var(--smrt-color-on-surface-variant); cursor: grab; touch-action: none; }
|
|
1070
|
-
.smrt-edit-item__grip:hover { background: var(--smrt-
|
|
1071
|
-
.smrt-edit-item__grip:focus-visible { outline:
|
|
1092
|
+
.smrt-edit-item__grip:hover { background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent); }
|
|
1093
|
+
.smrt-edit-item__grip:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
|
|
1072
1094
|
.smrt-edit-item__grip[aria-pressed='true'] { background: var(--smrt-color-primary-container); color: var(--smrt-color-on-primary-container); }
|
|
1073
1095
|
}
|
|
1074
1096
|
</style>
|
|
@@ -4,13 +4,13 @@ import '@happyvertical/smrt-ui/themes/styles/base.css';
|
|
|
4
4
|
import '@happyvertical/smrt-ui/themes/styles/fonts.css';
|
|
5
5
|
import type { DataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
|
|
6
6
|
import { type Component, type Snippet } from 'svelte';
|
|
7
|
-
import type { User } from '../../state/app-state.js';
|
|
8
7
|
import type { WebMcpProviderConfig } from '../../web/webmcp-provider.js';
|
|
9
8
|
import { type ShellLayout } from '../workspace/admin-shell/layout.js';
|
|
10
9
|
import { type ShellSlot } from '../workspace/admin-shell/slots.js';
|
|
11
10
|
import { type AdminShellProps, type ShellNavGroup, type ShellNavItem, type ShellPanelDefaults, type ShellSectionActionsContext } from '../workspace/admin-shell/types.js';
|
|
12
11
|
import type { DockToggle } from './dock-toggle.js';
|
|
13
12
|
import { type PhoneTabBarOptions } from './phone-tabs.js';
|
|
13
|
+
import type { ShellUser } from './shell-user.js';
|
|
14
14
|
import type { AppShellAccountMenu, AppShellCommandPalette, ShellSlotItem } from './slot-item.js';
|
|
15
15
|
interface Props {
|
|
16
16
|
/** Application name shown in the shell brand and app panel. */
|
|
@@ -47,8 +47,12 @@ interface Props {
|
|
|
47
47
|
* default slot (users can still move it in the layout editor).
|
|
48
48
|
*/
|
|
49
49
|
accountMenu?: AppShellAccountMenu;
|
|
50
|
-
/**
|
|
51
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Called when the account menu's Sign out is chosen (wins over
|
|
52
|
+
* `signOutHref`). May be async: the menu awaits it, shows a busy state, and
|
|
53
|
+
* on rejection stays open with an inline error.
|
|
54
|
+
*/
|
|
55
|
+
onSignOut?: () => unknown;
|
|
52
56
|
/**
|
|
53
57
|
* Where the account menu's Sign out link goes when there is no `onSignOut`.
|
|
54
58
|
* A link is a GET: never point it at the runtime's POST-only `/sign-out`
|
|
@@ -73,8 +77,12 @@ interface Props {
|
|
|
73
77
|
commandPalette?: AppShellCommandPalette;
|
|
74
78
|
/** Options for the Provider's WebMCP integration; passed through unchanged. */
|
|
75
79
|
webmcp?: boolean | WebMcpProviderConfig;
|
|
76
|
-
/**
|
|
77
|
-
|
|
80
|
+
/**
|
|
81
|
+
* The signed-in identity (`ShellUser`): the runtime's `SessionLayoutUser`
|
|
82
|
+
* (`data.session.user`) or a full `User` both fit without a cast. It is
|
|
83
|
+
* forwarded to the Provider unchanged; `null` renders the shell signed out.
|
|
84
|
+
*/
|
|
85
|
+
user?: ShellUser;
|
|
78
86
|
/** The user's resolved permission slugs, forwarded to the Provider for permission-gated UI. */
|
|
79
87
|
permissions?: string[];
|
|
80
88
|
/** Register the read-only runtime diagnostics WebMCP tool. */
|
|
@@ -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,EAEhB,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,SAAS,EAAE,KAAK,OAAO,EAAiB,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"AppShell.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/app/AppShell.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
+
import { M } from '../../i18n/strings.workspace.js';
|
|
2
4
|
import { useShellDock } from '../workspace/admin-shell/dock.js';
|
|
3
5
|
import {
|
|
4
6
|
isShellIconName,
|
|
5
7
|
SHELL_DEFAULT_TOOL_ICON,
|
|
6
8
|
SHELL_ICON_PATHS,
|
|
7
9
|
} from '../workspace/admin-shell/shell-icons.js';
|
|
8
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
type DockToggle,
|
|
12
|
+
type DockToggleResolution,
|
|
13
|
+
resolveDockToggle,
|
|
14
|
+
runDockTogglePress,
|
|
15
|
+
} from './dock-toggle.js';
|
|
9
16
|
|
|
10
17
|
interface Props {
|
|
11
18
|
toggles: DockToggle[];
|
|
@@ -13,67 +20,101 @@ interface Props {
|
|
|
13
20
|
|
|
14
21
|
let { toggles }: Props = $props();
|
|
15
22
|
const dock = useShellDock();
|
|
23
|
+
const { t } = useI18n();
|
|
16
24
|
|
|
17
25
|
function iconFor(toggle: DockToggle): string | undefined {
|
|
18
26
|
return toggle.icon ?? (toggle.tool === 'assistant' ? 'chat' : undefined);
|
|
19
27
|
}
|
|
20
28
|
|
|
29
|
+
function resolve(toggle: DockToggle): DockToggleResolution {
|
|
30
|
+
return resolveDockToggle(toggle, dock.has(toggle.tool) && dock.available);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nameFor(toggle: DockToggle, notReady: boolean): string {
|
|
34
|
+
if (!notReady) return toggle.label;
|
|
35
|
+
return (
|
|
36
|
+
toggle.labelNotReady ??
|
|
37
|
+
t(M['ui.dock_toggles.set_up'], { label: toggle.label })
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
function press(event: MouseEvent, toggle: DockToggle): void {
|
|
22
|
-
|
|
42
|
+
const resolution = resolve(toggle);
|
|
43
|
+
if (!runDockTogglePress(toggle, event, resolution)) return;
|
|
44
|
+
if (resolution.mode !== 'toggle') return;
|
|
23
45
|
dock.toggle(toggle.tool, {
|
|
24
46
|
returnFocus: event.currentTarget as HTMLElement,
|
|
25
47
|
});
|
|
26
48
|
}
|
|
27
49
|
</script>
|
|
28
50
|
|
|
51
|
+
{#snippet glyph(icon: string | undefined)}
|
|
52
|
+
{#if icon === 'chat'}
|
|
53
|
+
<svg
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
width="20"
|
|
56
|
+
height="20"
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke="currentColor"
|
|
59
|
+
stroke-width="2"
|
|
60
|
+
stroke-linecap="round"
|
|
61
|
+
stroke-linejoin="round"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
>
|
|
64
|
+
<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" />
|
|
65
|
+
</svg>
|
|
66
|
+
{:else if icon === undefined || isShellIconName(icon)}
|
|
67
|
+
<svg
|
|
68
|
+
viewBox="0 0 24 24"
|
|
69
|
+
width="20"
|
|
70
|
+
height="20"
|
|
71
|
+
fill="currentColor"
|
|
72
|
+
aria-hidden="true"
|
|
73
|
+
focusable="false"
|
|
74
|
+
>
|
|
75
|
+
<path d={SHELL_ICON_PATHS[icon === undefined ? SHELL_DEFAULT_TOOL_ICON : icon]} />
|
|
76
|
+
</svg>
|
|
77
|
+
{:else}
|
|
78
|
+
<span aria-hidden="true">{icon}</span>
|
|
79
|
+
{/if}
|
|
80
|
+
{/snippet}
|
|
81
|
+
|
|
29
82
|
<div class="smrt-dock-toggles" data-testid="dock-toggles">
|
|
30
83
|
{#each toggles as toggle (toggle.tool)}
|
|
31
|
-
{@const
|
|
32
|
-
{@const
|
|
84
|
+
{@const resolution = resolve(toggle)}
|
|
85
|
+
{@const registered = resolution.mode === 'toggle'}
|
|
86
|
+
{@const open = registered && dock.isOpen(toggle.tool)}
|
|
33
87
|
{@const icon = iconFor(toggle)}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
viewBox="0 0 24 24"
|
|
65
|
-
width="20"
|
|
66
|
-
height="20"
|
|
67
|
-
fill="currentColor"
|
|
68
|
-
aria-hidden="true"
|
|
69
|
-
focusable="false"
|
|
70
|
-
>
|
|
71
|
-
<path d={SHELL_ICON_PATHS[icon === undefined ? SHELL_DEFAULT_TOOL_ICON : icon]} />
|
|
72
|
-
</svg>
|
|
73
|
-
{:else}
|
|
74
|
-
<span aria-hidden="true">{icon}</span>
|
|
75
|
-
{/if}
|
|
76
|
-
</button>
|
|
88
|
+
{@const name = nameFor(toggle, resolution.notReady)}
|
|
89
|
+
{#if resolution.mode === 'link'}
|
|
90
|
+
<a
|
|
91
|
+
class="smrt-dock-toggles__button"
|
|
92
|
+
href={toggle.href}
|
|
93
|
+
data-dock-tool={toggle.tool}
|
|
94
|
+
aria-label={name}
|
|
95
|
+
title={name}
|
|
96
|
+
onclick={(event) => press(event, toggle)}
|
|
97
|
+
>
|
|
98
|
+
{@render glyph(icon)}
|
|
99
|
+
</a>
|
|
100
|
+
{:else}
|
|
101
|
+
<!-- raw-primitive-allow: shell chrome toggle, not a content button -->
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
class="smrt-dock-toggles__button"
|
|
105
|
+
class:active={open}
|
|
106
|
+
data-dock-tool={toggle.tool}
|
|
107
|
+
aria-label={name}
|
|
108
|
+
title={name}
|
|
109
|
+
aria-pressed={open}
|
|
110
|
+
aria-expanded={open}
|
|
111
|
+
aria-controls="smrt-admin-shell-right-panel"
|
|
112
|
+
aria-disabled={registered ? undefined : true}
|
|
113
|
+
onclick={(event) => press(event, toggle)}
|
|
114
|
+
>
|
|
115
|
+
{@render glyph(icon)}
|
|
116
|
+
</button>
|
|
117
|
+
{/if}
|
|
77
118
|
{/each}
|
|
78
119
|
</div>
|
|
79
120
|
|
|
@@ -88,12 +129,14 @@ function press(event: MouseEvent, toggle: DockToggle): void {
|
|
|
88
129
|
|
|
89
130
|
.smrt-dock-toggles__button {
|
|
90
131
|
display: inline-grid;
|
|
132
|
+
text-decoration: none;
|
|
91
133
|
place-items: center;
|
|
92
|
-
|
|
93
|
-
|
|
134
|
+
position: relative;
|
|
135
|
+
inline-size: var(--smrt-control-height-small);
|
|
136
|
+
block-size: var(--smrt-control-height-small);
|
|
94
137
|
padding: 0;
|
|
95
138
|
border: 1px solid transparent;
|
|
96
|
-
border-radius: var(--smrt-radius
|
|
139
|
+
border-radius: var(--smrt-control-radius);
|
|
97
140
|
background: transparent;
|
|
98
141
|
color: var(--smrt-color-on-surface);
|
|
99
142
|
cursor: pointer;
|
|
@@ -106,22 +149,37 @@ function press(event: MouseEvent, toggle: DockToggle): void {
|
|
|
106
149
|
}
|
|
107
150
|
}
|
|
108
151
|
|
|
109
|
-
.
|
|
110
|
-
|
|
152
|
+
/* State layer over the button; the active container fill sits under it. */
|
|
153
|
+
.smrt-dock-toggles__button::after {
|
|
154
|
+
content: '';
|
|
155
|
+
position: absolute;
|
|
156
|
+
inset: 0;
|
|
157
|
+
border-radius: inherit;
|
|
158
|
+
background: currentColor;
|
|
159
|
+
opacity: 0;
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.smrt-dock-toggles__button:hover::after {
|
|
164
|
+
opacity: var(--smrt-state-hover);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.smrt-dock-toggles__button:active::after {
|
|
168
|
+
opacity: var(--smrt-state-pressed);
|
|
111
169
|
}
|
|
112
170
|
|
|
113
171
|
.smrt-dock-toggles__button:focus-visible {
|
|
114
|
-
outline:
|
|
115
|
-
outline-offset:
|
|
172
|
+
outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
|
|
173
|
+
outline-offset: var(--smrt-focus-ring-offset);
|
|
116
174
|
}
|
|
117
175
|
|
|
118
176
|
.smrt-dock-toggles__button.active {
|
|
119
|
-
background: var(--smrt-color-primary-container
|
|
120
|
-
color: var(--smrt-color-on-primary-container
|
|
177
|
+
background: var(--smrt-color-primary-container);
|
|
178
|
+
color: var(--smrt-color-on-primary-container);
|
|
121
179
|
}
|
|
122
180
|
|
|
123
181
|
.smrt-dock-toggles__button[aria-disabled='true'] {
|
|
124
|
-
opacity:
|
|
182
|
+
opacity: var(--smrt-state-disabled);
|
|
125
183
|
cursor: default;
|
|
126
184
|
}
|
|
127
185
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DockToggles.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/app/DockToggles.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DockToggles.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/app/DockToggles.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,kBAAkB,CAAC;AAG1B,UAAU,KAAK;IACb,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AA6ED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -18,7 +18,11 @@ import {
|
|
|
18
18
|
SHELL_PLACEHOLDER_ICON,
|
|
19
19
|
} from '../workspace/admin-shell/shell-icons.js';
|
|
20
20
|
import type { PhoneBottomBarEntry } from '../workspace/admin-shell/types.js';
|
|
21
|
-
import
|
|
21
|
+
import {
|
|
22
|
+
type DockToggle,
|
|
23
|
+
resolveDockToggle,
|
|
24
|
+
runDockTogglePress,
|
|
25
|
+
} from './dock-toggle.js';
|
|
22
26
|
import {
|
|
23
27
|
isTabCurrent,
|
|
24
28
|
type PhoneTabNavEntry,
|
|
@@ -93,17 +97,38 @@ const items = $derived.by(() => {
|
|
|
93
97
|
}));
|
|
94
98
|
if (dockToggle) {
|
|
95
99
|
const tool = dockToggle.tool;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
const resolution = resolveDockToggle(
|
|
101
|
+
dockToggle,
|
|
102
|
+
dock.has(tool) && dock.available,
|
|
103
|
+
);
|
|
104
|
+
const toggling = resolution.mode === 'toggle';
|
|
105
|
+
const label = resolution.notReady
|
|
106
|
+
? (dockToggle.labelNotReady ??
|
|
107
|
+
t(M['ui.dock_toggles.set_up'], { label: dockToggle.label }))
|
|
108
|
+
: dockToggle.label;
|
|
109
|
+
const press = (event: MouseEvent) => {
|
|
110
|
+
if (!runDockTogglePress(dockToggle, event, resolution)) return;
|
|
111
|
+
if (toggling) dock.toggle(tool);
|
|
112
|
+
};
|
|
113
|
+
out.push(
|
|
114
|
+
resolution.mode === 'link'
|
|
115
|
+
? {
|
|
116
|
+
id: `dock:${tool}`,
|
|
117
|
+
label,
|
|
118
|
+
iconName: dockIcon(dockToggle),
|
|
119
|
+
href: dockToggle.href,
|
|
120
|
+
onclick: press,
|
|
121
|
+
}
|
|
122
|
+
: {
|
|
123
|
+
id: `dock:${tool}`,
|
|
124
|
+
label,
|
|
125
|
+
iconName: dockIcon(dockToggle),
|
|
126
|
+
active: toggling && dock.isOpen(tool),
|
|
127
|
+
expanded: toggling ? dock.isOpen(tool) : undefined,
|
|
128
|
+
controls: toggling ? 'smrt-admin-shell-right-panel' : undefined,
|
|
129
|
+
onclick: press,
|
|
130
|
+
},
|
|
131
|
+
);
|
|
107
132
|
} else if (toolsTab) {
|
|
108
133
|
out.push({
|
|
109
134
|
id: 'tools',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
|
-
import type
|
|
2
|
+
import { type DockToggle } from './dock-toggle.js';
|
|
3
3
|
import { type PhoneTabNavEntry } from './phone-tabs.js';
|
|
4
4
|
interface Props {
|
|
5
5
|
/** Navigation destinations in order (see `phoneTabNavEntries`). */
|