@happyvertical/smrt-svelte 0.51.39 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -0
- package/README.md +214 -1
- package/agents/components.md +8 -1
- package/agents/workspace.md +24 -0
- package/dist/Provider.svelte +19 -15
- package/dist/Provider.svelte.d.ts +2 -2
- package/dist/Provider.svelte.d.ts.map +1 -1
- package/dist/__tests__/provider-mode-harness.svelte +14 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts +11 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +1 -0
- package/dist/__tests__/provider-mode-probe.svelte +13 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts +8 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
- package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
- package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/index.js +2 -1
- package/dist/components/forms/FileUpload.svelte +120 -16
- package/dist/components/forms/FileUpload.svelte.d.ts +7 -1
- package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +118 -8
- package/dist/components/forms/Form.svelte.d.ts +18 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormScope.svelte +202 -0
- package/dist/components/forms/FormScope.svelte.d.ts +22 -0
- package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/FileUpload.native-form.test.js +249 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +24 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
- package/dist/components/forms/__tests__/FormScope.test.js +39 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +1 -0
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/workspace/README.md +11 -0
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +597 -0
- package/dist/components/workspace/__tests__/AdminShell.test.js +257 -1
- package/dist/components/workspace/__tests__/TenantNav.test.js +115 -0
- package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +6 -0
- package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
- package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
- package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
- package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +1099 -59
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +44 -1
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +116 -13
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -2
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +16 -2
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +2 -0
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +84 -2
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +57 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +156 -6
- package/dist/components/workspace/admin-shell/types.d.ts +115 -1
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/types.js +13 -0
- package/dist/components/workspace/index.d.ts +11 -4
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +12 -3
- package/dist/i18n/strings.forms.d.ts +3 -0
- package/dist/i18n/strings.forms.d.ts.map +1 -1
- package/dist/i18n/strings.forms.js +3 -0
- package/dist/i18n/strings.workspace.d.ts +14 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/provider-mode.test.js +86 -0
- package/dist/state/app-state.svelte.d.ts.map +1 -1
- package/dist/state/app-state.svelte.js +8 -8
- package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
- package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
- package/dist/web/__tests__/webmcp-ui.test.js +18 -0
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +4 -1
- package/dist/web/link-surface.svelte.d.ts +14 -0
- package/dist/web/link-surface.svelte.d.ts.map +1 -0
- package/dist/web/link-surface.svelte.js +49 -0
- package/dist/web/list-surface.svelte.d.ts +80 -0
- package/dist/web/list-surface.svelte.d.ts.map +1 -0
- package/dist/web/list-surface.svelte.js +309 -0
- package/dist/web/step-surface.svelte.d.ts +17 -0
- package/dist/web/step-surface.svelte.d.ts.map +1 -0
- package/dist/web/step-surface.svelte.js +56 -0
- package/dist/web/webmcp-ui-context.d.ts +7 -0
- package/dist/web/webmcp-ui-context.d.ts.map +1 -1
- package/dist/web/webmcp-ui-context.js +10 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -1
- package/dist/web/webmcp-ui.js +16 -3
- package/package.json +17 -17
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-shell.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/mobile-shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Classify a viewport from the phone and desktop media-query matches. */
|
|
2
|
+
export function viewportFor(matches) {
|
|
3
|
+
if (matches.phone)
|
|
4
|
+
return 'phone';
|
|
5
|
+
return matches.desktop ? 'desktop' : 'tablet';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Hide-on-scroll state machine for the phone top bar (and anything that
|
|
9
|
+
* slides with it). Feed it every scroll position; read `hidden`. Travel
|
|
10
|
+
* accumulates in one direction and resets when the direction reverses.
|
|
11
|
+
*/
|
|
12
|
+
export class ScrollChrome {
|
|
13
|
+
hidden = false;
|
|
14
|
+
#lastY = 0;
|
|
15
|
+
#travel = 0;
|
|
16
|
+
#revealWithin;
|
|
17
|
+
#hideAfter;
|
|
18
|
+
#showAfter;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.#revealWithin = options.revealWithin ?? 56;
|
|
21
|
+
this.#hideAfter = options.hideAfter ?? 12;
|
|
22
|
+
this.#showAfter = options.showAfter ?? 4;
|
|
23
|
+
}
|
|
24
|
+
/** Feed a scroll sample; returns whether the chrome is now hidden. */
|
|
25
|
+
update(sample) {
|
|
26
|
+
const max = sample.maxY ?? Number.POSITIVE_INFINITY;
|
|
27
|
+
const y = Math.min(Math.max(sample.y, 0), Math.max(max, 0));
|
|
28
|
+
if (sample.pinned || y <= this.#revealWithin) {
|
|
29
|
+
this.hidden = false;
|
|
30
|
+
this.#lastY = y;
|
|
31
|
+
this.#travel = 0;
|
|
32
|
+
return this.hidden;
|
|
33
|
+
}
|
|
34
|
+
const delta = y - this.#lastY;
|
|
35
|
+
this.#lastY = y;
|
|
36
|
+
if (delta === 0)
|
|
37
|
+
return this.hidden;
|
|
38
|
+
this.#travel =
|
|
39
|
+
Math.sign(delta) === Math.sign(this.#travel)
|
|
40
|
+
? this.#travel + delta
|
|
41
|
+
: delta;
|
|
42
|
+
if (this.#travel >= this.#hideAfter)
|
|
43
|
+
this.hidden = true;
|
|
44
|
+
else if (-this.#travel >= this.#showAfter)
|
|
45
|
+
this.hidden = false;
|
|
46
|
+
return this.hidden;
|
|
47
|
+
}
|
|
48
|
+
/** Show the chrome and forget the scroll history (e.g. after navigating). */
|
|
49
|
+
reset() {
|
|
50
|
+
this.hidden = false;
|
|
51
|
+
this.#lastY = 0;
|
|
52
|
+
this.#travel = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ---- On-screen keyboard ---------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* `visualViewport` heuristic: the keyboard is open when the visible viewport
|
|
58
|
+
* is much shorter than the layout viewport. `scale` undoes pinch zoom, which
|
|
59
|
+
* also shrinks `visualViewport.height`.
|
|
60
|
+
*/
|
|
61
|
+
export function keyboardLikelyOpen(input) {
|
|
62
|
+
const visible = input.visualHeight * (input.scale ?? 1);
|
|
63
|
+
return input.layoutHeight - visible > (input.threshold ?? 150);
|
|
64
|
+
}
|
|
65
|
+
/** Drop trailing slashes (`/` stays `/`). */
|
|
66
|
+
export function normalizeShellPath(path) {
|
|
67
|
+
return path.replace(/\/+$/, '') || '/';
|
|
68
|
+
}
|
|
69
|
+
/** Whether `path` is the item's page or (unless `exact`) below it. */
|
|
70
|
+
export function shellNavItemMatches(item, path) {
|
|
71
|
+
const href = normalizeShellPath(item.href);
|
|
72
|
+
const current = normalizeShellPath(path);
|
|
73
|
+
if (href === current)
|
|
74
|
+
return true;
|
|
75
|
+
if (item.exact || href === '/')
|
|
76
|
+
return false;
|
|
77
|
+
return current.startsWith(`${href}/`);
|
|
78
|
+
}
|
|
79
|
+
/** The chain of nav items from the top level down to the one matching `path`. */
|
|
80
|
+
export function findShellNavTrail(items, path) {
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
const childTrail = item.children
|
|
83
|
+
? findShellNavTrail(item.children, path)
|
|
84
|
+
: [];
|
|
85
|
+
if (childTrail.length > 0)
|
|
86
|
+
return [item, ...childTrail];
|
|
87
|
+
if (shellNavItemMatches(item, path))
|
|
88
|
+
return [item];
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The single source for a page's place in the workspace: breadcrumbs on
|
|
94
|
+
* wider screens and the phone top bar's back target both come from here.
|
|
95
|
+
*
|
|
96
|
+
* Section homes (the home page and top-level nav pages) have no ancestors.
|
|
97
|
+
* Anything else lists the workspace home, the nav items above it (a nested
|
|
98
|
+
* nav page's parent item counts, even when its URL is not below it), then the
|
|
99
|
+
* page-given `parents`. The current page is never a crumb.
|
|
100
|
+
*/
|
|
101
|
+
export function shellPageTrailFor(input) {
|
|
102
|
+
const path = normalizeShellPath(input.path);
|
|
103
|
+
const home = normalizeShellPath(input.homeHref);
|
|
104
|
+
const parents = [...(input.parents ?? [])];
|
|
105
|
+
const trail = findShellNavTrail(input.navItems, path);
|
|
106
|
+
const last = trail.at(-1);
|
|
107
|
+
const currentItem = last && normalizeShellPath(last.href) === path ? last : null;
|
|
108
|
+
const current = currentItem
|
|
109
|
+
? { label: currentItem.label, href: currentItem.href }
|
|
110
|
+
: null;
|
|
111
|
+
const topLevelHome = path === home ||
|
|
112
|
+
input.navItems.some((item) => normalizeShellPath(item.href) === path);
|
|
113
|
+
if (topLevelHome && parents.length === 0) {
|
|
114
|
+
return { sectionHome: true, crumbs: [], current };
|
|
115
|
+
}
|
|
116
|
+
const crumbs = [
|
|
117
|
+
{ label: input.homeTitle, href: input.homeHref },
|
|
118
|
+
];
|
|
119
|
+
for (const item of trail) {
|
|
120
|
+
if (item === currentItem)
|
|
121
|
+
continue;
|
|
122
|
+
const href = normalizeShellPath(item.href);
|
|
123
|
+
if (href === home || href === path)
|
|
124
|
+
continue;
|
|
125
|
+
crumbs.push({ label: item.label, href: item.href });
|
|
126
|
+
}
|
|
127
|
+
for (const parent of parents) {
|
|
128
|
+
const href = normalizeShellPath(parent.href);
|
|
129
|
+
if (href === path)
|
|
130
|
+
continue;
|
|
131
|
+
// A page that names an ancestor the nav already supplied (a form under
|
|
132
|
+
// its list's nav item) must not show that crumb twice.
|
|
133
|
+
if (crumbs.some((crumb) => normalizeShellPath(crumb.href) === href))
|
|
134
|
+
continue;
|
|
135
|
+
crumbs.push({ label: parent.label, href: parent.href });
|
|
136
|
+
}
|
|
137
|
+
return { sectionHome: false, crumbs, current };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Section homes (see {@link shellPageTrailFor}) show the workspace name.
|
|
141
|
+
* Every other page shows a back arrow to its nearest ancestor (the last
|
|
142
|
+
* breadcrumb) and the page's title.
|
|
143
|
+
*/
|
|
144
|
+
export function phoneTopBarFor(input) {
|
|
145
|
+
const trail = shellPageTrailFor(input);
|
|
146
|
+
if (trail.sectionHome && !input.backHref) {
|
|
147
|
+
return { kind: 'home', title: input.homeTitle };
|
|
148
|
+
}
|
|
149
|
+
const parent = trail.crumbs.at(-1) ?? {
|
|
150
|
+
label: input.homeTitle,
|
|
151
|
+
href: input.homeHref,
|
|
152
|
+
};
|
|
153
|
+
const backHref = input.backHref ?? parent.href;
|
|
154
|
+
const backLabel = input.backHref ? (input.backLabel ?? 'Back') : parent.label;
|
|
155
|
+
const title = input.pageTitle?.trim() || trail.current?.label || parent.label;
|
|
156
|
+
return { kind: 'detail', title, backHref, backLabel };
|
|
157
|
+
}
|
|
158
|
+
/** Decide the {@link BottomBarMode}. */
|
|
159
|
+
export function bottomBarMode(input) {
|
|
160
|
+
if (input.keyboardOpen)
|
|
161
|
+
return 'none';
|
|
162
|
+
return input.formActions ? 'form' : 'nav';
|
|
163
|
+
}
|
|
164
|
+
/** Selector a form's action row carries to replace the phone bottom bar. */
|
|
165
|
+
export const FORM_ACTION_BAR_SELECTOR = '[data-form-action-bar]';
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
import type { PanelEdge, PanelState, ResolvedShellConfig, ShellHotkeyBinding, ShellPanelConfig, ShellPanelDefaults, ShellSettingsAdapter, ShellSettingsDelta } from './types.js';
|
|
1
|
+
import type { PanelEdge, PanelState, ResolvedShellConfig, ShellHotkeyBinding, ShellPanelConfig, ShellPanelDefaults, ShellPanelResize, ShellSettingsAdapter, ShellSettingsDelta, ShellViewport } from './types.js';
|
|
2
2
|
export declare const DEFAULT_SHELL_KEYMAP: Record<PanelEdge, ShellHotkeyBinding>;
|
|
3
3
|
export declare function resolveShellConfig(appDefaults?: ShellPanelDefaults): ResolvedShellConfig;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* An edge's state: hidden edges stay hidden; otherwise the user's setting,
|
|
6
|
+
* then the edge's default for `viewport` (when it declares
|
|
7
|
+
* `viewportDefaults`), then its configured initial state.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveInitialPanelState(edge: PanelEdge, config: ShellPanelConfig, settings?: ShellSettingsDelta, viewport?: ShellViewport): PanelState;
|
|
10
|
+
/** Default limits for a resizable side edge (CSS px). */
|
|
11
|
+
export declare const DEFAULT_SHELL_PANEL_RESIZE: Required<ShellPanelResize>;
|
|
12
|
+
/**
|
|
13
|
+
* Resolved resize limits for an edge, or `null` when it is not resizable.
|
|
14
|
+
* Only `left` and `right` edges resize.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolvePanelResize(edge: PanelEdge, config: ShellPanelConfig): Required<ShellPanelResize> | null;
|
|
17
|
+
/** Clamp a requested width into an edge's resize limits (rounded to px). */
|
|
18
|
+
export declare function clampPanelSize(size: number, limits: Required<ShellPanelResize>): number;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the settings adapter stores an edge's `state` (open/closed) or
|
|
21
|
+
* `size`. Edges with `viewportDefaults` never store their state: it follows
|
|
22
|
+
* the viewport class.
|
|
23
|
+
*/
|
|
24
|
+
export declare function panelPersists(config: ShellPanelConfig, what: 'state' | 'size'): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Drop the panel states and sizes the config says not to store, so they are
|
|
27
|
+
* neither written by nor read back from a settings adapter.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stripUnpersistedSettings(delta: ShellSettingsDelta, config: ResolvedShellConfig): ShellSettingsDelta;
|
|
5
30
|
export declare function resolveHotkey(edge: PanelEdge, config: ShellPanelConfig, settings?: ShellSettingsDelta): ShellHotkeyBinding | null;
|
|
6
31
|
export declare function mergeShellSettingsDelta(base: ShellSettingsDelta, next: ShellSettingsDelta): ShellSettingsDelta;
|
|
7
32
|
export declare function pruneShellSettingsDelta(delta: ShellSettingsDelta): ShellSettingsDelta;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACd,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,SAAS,EAAE,kBAAkB,CAKtE,CAAC;AA6CF,wBAAgB,kBAAkB,CAChC,WAAW,GAAE,kBAAuB,GACnC,mBAAmB,CA2BrB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,gBAAgB,EACxB,QAAQ,GAAE,kBAAuB,EACjC,QAAQ,CAAC,EAAE,aAAa,GACvB,UAAU,CAOZ;AAED,yDAAyD;AACzD,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,gBAAgB,CAIjE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,gBAAgB,GACvB,QAAQ,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAUnC;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GACjC,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,OAAO,CAKT;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,mBAAmB,GAC1B,kBAAkB,CAqBpB;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,gBAAgB,EACxB,QAAQ,GAAE,kBAAuB,GAChC,kBAAkB,GAAG,IAAI,CAM3B;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,kBAAkB,GACvB,kBAAkB,CAiBpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,CAuBpB;AAED,qBAAa,gCAAiC,YAAW,oBAAoB;IAC/D,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAExC,IAAI,IAAI,kBAAkB,GAAG,IAAI;IAWjC,KAAK,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;CAOvC"}
|
|
@@ -72,10 +72,84 @@ export function resolveShellConfig(appDefaults = {}) {
|
|
|
72
72
|
}));
|
|
73
73
|
return { panels };
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
/**
|
|
76
|
+
* An edge's state: hidden edges stay hidden; otherwise the user's setting,
|
|
77
|
+
* then the edge's default for `viewport` (when it declares
|
|
78
|
+
* `viewportDefaults`), then its configured initial state.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveInitialPanelState(edge, config, settings = {}, viewport) {
|
|
76
81
|
if (config.initial === 'hidden')
|
|
77
82
|
return 'hidden';
|
|
78
|
-
return settings.panels?.[edge] ??
|
|
83
|
+
return (settings.panels?.[edge] ??
|
|
84
|
+
(viewport ? config.viewportDefaults?.[viewport] : undefined) ??
|
|
85
|
+
config.initial);
|
|
86
|
+
}
|
|
87
|
+
/** Default limits for a resizable side edge (CSS px). */
|
|
88
|
+
export const DEFAULT_SHELL_PANEL_RESIZE = {
|
|
89
|
+
min: 240,
|
|
90
|
+
max: 720,
|
|
91
|
+
step: 16,
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Resolved resize limits for an edge, or `null` when it is not resizable.
|
|
95
|
+
* Only `left` and `right` edges resize.
|
|
96
|
+
*/
|
|
97
|
+
export function resolvePanelResize(edge, config) {
|
|
98
|
+
if (edge !== 'left' && edge !== 'right')
|
|
99
|
+
return null;
|
|
100
|
+
if (!config.resizable || config.initial === 'hidden')
|
|
101
|
+
return null;
|
|
102
|
+
const limits = config.resizable === true ? {} : config.resizable;
|
|
103
|
+
const min = Math.max(0, limits.min ?? DEFAULT_SHELL_PANEL_RESIZE.min);
|
|
104
|
+
return {
|
|
105
|
+
min,
|
|
106
|
+
max: Math.max(min, limits.max ?? DEFAULT_SHELL_PANEL_RESIZE.max),
|
|
107
|
+
step: Math.max(1, limits.step ?? DEFAULT_SHELL_PANEL_RESIZE.step),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Clamp a requested width into an edge's resize limits (rounded to px). */
|
|
111
|
+
export function clampPanelSize(size, limits) {
|
|
112
|
+
return Math.round(Math.min(limits.max, Math.max(limits.min, size)));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether the settings adapter stores an edge's `state` (open/closed) or
|
|
116
|
+
* `size`. Edges with `viewportDefaults` never store their state: it follows
|
|
117
|
+
* the viewport class.
|
|
118
|
+
*/
|
|
119
|
+
export function panelPersists(config, what) {
|
|
120
|
+
if (what === 'state' && config.viewportDefaults)
|
|
121
|
+
return false;
|
|
122
|
+
const persist = config.persist ?? true;
|
|
123
|
+
if (typeof persist === 'boolean')
|
|
124
|
+
return persist;
|
|
125
|
+
return persist[what] ?? true;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Drop the panel states and sizes the config says not to store, so they are
|
|
129
|
+
* neither written by nor read back from a settings adapter.
|
|
130
|
+
*/
|
|
131
|
+
export function stripUnpersistedSettings(delta, config) {
|
|
132
|
+
const stripped = { ...delta };
|
|
133
|
+
for (const key of ['panels', 'sizes']) {
|
|
134
|
+
const values = delta[key];
|
|
135
|
+
if (!values)
|
|
136
|
+
continue;
|
|
137
|
+
const kept = {};
|
|
138
|
+
for (const edge of PANEL_EDGES) {
|
|
139
|
+
if (!(edge in values))
|
|
140
|
+
continue;
|
|
141
|
+
if (panelPersists(config.panels[edge], key === 'panels' ? 'state' : 'size')) {
|
|
142
|
+
kept[edge] = values[edge];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(kept).length > 0) {
|
|
146
|
+
stripped[key] = kept;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
delete stripped[key];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return stripped;
|
|
79
153
|
}
|
|
80
154
|
export function resolveHotkey(edge, config, settings = {}) {
|
|
81
155
|
if (config.initial === 'hidden')
|
|
@@ -95,6 +169,9 @@ export function mergeShellSettingsDelta(base, next) {
|
|
|
95
169
|
panels: base.panels || next.panels
|
|
96
170
|
? { ...(base.panels ?? {}), ...(next.panels ?? {}) }
|
|
97
171
|
: undefined,
|
|
172
|
+
sizes: base.sizes || next.sizes
|
|
173
|
+
? { ...(base.sizes ?? {}), ...(next.sizes ?? {}) }
|
|
174
|
+
: undefined,
|
|
98
175
|
};
|
|
99
176
|
}
|
|
100
177
|
export function pruneShellSettingsDelta(delta) {
|
|
@@ -111,6 +188,11 @@ export function pruneShellSettingsDelta(delta) {
|
|
|
111
188
|
if (delta.panels && Object.keys(delta.panels).length > 0) {
|
|
112
189
|
pruned.panels = delta.panels;
|
|
113
190
|
}
|
|
191
|
+
if (delta.sizes) {
|
|
192
|
+
const sizes = Object.fromEntries(Object.entries(delta.sizes).filter(([, size]) => typeof size === 'number' && Number.isFinite(size)));
|
|
193
|
+
if (Object.keys(sizes).length > 0)
|
|
194
|
+
pruned.sizes = sizes;
|
|
195
|
+
}
|
|
114
196
|
return pruned;
|
|
115
197
|
}
|
|
116
198
|
export class LocalStorageShellSettingsAdapter {
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import type { PanelEdge, PanelState, ResolvedShellConfig, ShellActivity, ShellActivityBadge, ShellActivityEvent, ShellActivityFilter, ShellFocusTool, ShellPanelDefaults, ShellScope, ShellSettingsAdapter, ShellSettingsDelta, ShellStateSnapshot, VisiblePanelState } from './types.js';
|
|
1
|
+
import type { PanelEdge, PanelPresentation, PanelState, PhonePanelPresentation, ResolvedShellConfig, ShellActivity, ShellActivityBadge, ShellActivityEvent, ShellActivityFilter, ShellFocusTool, ShellPanelDefaults, ShellPanelResize, ShellScope, ShellSettingsAdapter, ShellSettingsDelta, ShellStateSnapshot, ShellViewport, VisiblePanelState } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phones: up to 48rem (768px itself is a phone). The same boundary the
|
|
4
|
+
* shell's CSS uses to turn side edges into drawers.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ADMIN_SHELL_PHONE_QUERY = "(max-width: 48rem)";
|
|
7
|
+
/** Desktop starts at 64rem; between the two queries is a tablet. */
|
|
8
|
+
export declare const ADMIN_SHELL_DESKTOP_QUERY = "(min-width: 64rem)";
|
|
9
|
+
/** The viewport class for the current window (`desktop` without a DOM). */
|
|
10
|
+
export declare function detectShellViewport(): ShellViewport;
|
|
2
11
|
type ActivityListener = (event: ShellActivityEvent) => void;
|
|
3
12
|
type ShellActivityPatch = Partial<Omit<ShellActivity, 'id'>>;
|
|
4
13
|
export interface ShellStateOptions {
|
|
@@ -6,18 +15,63 @@ export interface ShellStateOptions {
|
|
|
6
15
|
settings?: ShellSettingsDelta;
|
|
7
16
|
settingsAdapter?: ShellSettingsAdapter;
|
|
8
17
|
storageKey?: string;
|
|
18
|
+
/** Initial viewport class (default: detected from `window`, else desktop). */
|
|
19
|
+
viewport?: ShellViewport;
|
|
9
20
|
}
|
|
10
21
|
export declare class ShellState {
|
|
11
22
|
readonly config: ResolvedShellConfig;
|
|
12
23
|
readonly adapter: ShellSettingsAdapter | null;
|
|
13
24
|
settings: ShellSettingsDelta;
|
|
14
25
|
panels: Record<PanelEdge, PanelState>;
|
|
26
|
+
/** Width class the shell is laid out for; `AdminShell` keeps it current. */
|
|
27
|
+
viewport: ShellViewport;
|
|
15
28
|
focusTools: ShellFocusTool[];
|
|
16
29
|
activeFocusToolId: string | null;
|
|
17
30
|
activities: ShellActivity[];
|
|
31
|
+
/**
|
|
32
|
+
* Side edges whose `overlayMedia` currently matches; `AdminShell` keeps
|
|
33
|
+
* it current. Read it through `presentationFor`.
|
|
34
|
+
*/
|
|
35
|
+
overlayMatches: Partial<Record<PanelEdge, boolean>>;
|
|
18
36
|
private activityListeners;
|
|
19
37
|
constructor(options?: ShellStateOptions);
|
|
20
38
|
hydrate(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Record the viewport class. When it changes, edges with
|
|
41
|
+
* `viewportDefaults` switch to their default for the new class (the
|
|
42
|
+
* user's toggle for the old class is dropped). Not persisted.
|
|
43
|
+
*/
|
|
44
|
+
setViewport(viewport: ShellViewport): void;
|
|
45
|
+
/** How a side edge is presented on phones (`drawer` unless configured). */
|
|
46
|
+
phonePresentation(edge: PanelEdge): PhonePanelPresentation;
|
|
47
|
+
/**
|
|
48
|
+
* How an edge is presented right now: its configured presentation, or
|
|
49
|
+
* `overlay` for a side edge whose `overlayMedia` matches off phones.
|
|
50
|
+
*/
|
|
51
|
+
presentationFor(edge: PanelEdge): PanelPresentation;
|
|
52
|
+
/**
|
|
53
|
+
* Close side edges that would open as overlays (a restored or initial
|
|
54
|
+
* `expanded` state), without changing the stored preference: an overlay
|
|
55
|
+
* never covers the page on load, while a docked edge still restores.
|
|
56
|
+
*/
|
|
57
|
+
closeOverlaidEdges(): void;
|
|
58
|
+
/** Record whether a side edge's `overlayMedia` matches. */
|
|
59
|
+
setOverlayMatch(edge: PanelEdge, matches: boolean): void;
|
|
60
|
+
/** Whether an edge is rendered at the current viewport. */
|
|
61
|
+
isEdgeShown(edge: PanelEdge): boolean;
|
|
62
|
+
/** Resize limits for an edge, or `null` when it is not resizable. */
|
|
63
|
+
resizeLimits(edge: PanelEdge): Required<ShellPanelResize> | null;
|
|
64
|
+
/** The user's resized width for an edge in px, or `null` (configured size). */
|
|
65
|
+
panelSize(edge: PanelEdge): number | null;
|
|
66
|
+
/**
|
|
67
|
+
* Resize an edge's expanded width (px, clamped to its limits); `null`
|
|
68
|
+
* resets it to the configured `expandedSize`. Persisted unless
|
|
69
|
+
* `persist: false` (use that while a drag is in progress) or the edge
|
|
70
|
+
* opts out of storing its size.
|
|
71
|
+
*/
|
|
72
|
+
setPanelSize(edge: PanelEdge, size: number | null, options?: {
|
|
73
|
+
persist?: boolean;
|
|
74
|
+
}): void;
|
|
21
75
|
snapshot(): ShellStateSnapshot;
|
|
22
76
|
applySettings(delta: ShellSettingsDelta, options?: {
|
|
23
77
|
persist?: boolean;
|
|
@@ -25,6 +79,8 @@ export declare class ShellState {
|
|
|
25
79
|
setHotkeysEnabled(enabled: boolean): void;
|
|
26
80
|
setHotkey(edge: PanelEdge, code: string | null): void;
|
|
27
81
|
setPanel(edge: PanelEdge, state: VisiblePanelState): void;
|
|
82
|
+
/** Set and persist a runtime panel preference, including hidden. */
|
|
83
|
+
setPanelState(edge: PanelEdge, state: PanelState): void;
|
|
28
84
|
togglePanel(edge: PanelEdge): void;
|
|
29
85
|
expandPanel(edge: PanelEdge): void;
|
|
30
86
|
collapsePanel(edge: PanelEdge): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/state.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/state.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { untrack } from 'svelte';
|
|
2
|
-
import { LocalStorageShellSettingsAdapter, mergeShellSettingsDelta, resolveInitialPanelState, resolveShellConfig, } from './settings.js';
|
|
2
|
+
import { clampPanelSize, LocalStorageShellSettingsAdapter, mergeShellSettingsDelta, resolveInitialPanelState, resolvePanelResize, resolveShellConfig, stripUnpersistedSettings, } from './settings.js';
|
|
3
3
|
import { PANEL_EDGES, SCOPE_EDGES } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Phones: up to 48rem (768px itself is a phone). The same boundary the
|
|
6
|
+
* shell's CSS uses to turn side edges into drawers.
|
|
7
|
+
*/
|
|
8
|
+
export const ADMIN_SHELL_PHONE_QUERY = '(max-width: 48rem)';
|
|
9
|
+
/** Desktop starts at 64rem; between the two queries is a tablet. */
|
|
10
|
+
export const ADMIN_SHELL_DESKTOP_QUERY = '(min-width: 64rem)';
|
|
11
|
+
/** The viewport class for the current window (`desktop` without a DOM). */
|
|
12
|
+
export function detectShellViewport() {
|
|
13
|
+
if (typeof window === 'undefined' ||
|
|
14
|
+
typeof window.matchMedia !== 'function') {
|
|
15
|
+
return 'desktop';
|
|
16
|
+
}
|
|
17
|
+
if (window.matchMedia(ADMIN_SHELL_PHONE_QUERY).matches)
|
|
18
|
+
return 'phone';
|
|
19
|
+
return window.matchMedia(ADMIN_SHELL_DESKTOP_QUERY).matches
|
|
20
|
+
? 'desktop'
|
|
21
|
+
: 'tablet';
|
|
22
|
+
}
|
|
4
23
|
export class ShellState {
|
|
5
24
|
config;
|
|
6
25
|
adapter;
|
|
@@ -11,9 +30,16 @@ export class ShellState {
|
|
|
11
30
|
right: 'collapsed',
|
|
12
31
|
bottom: 'collapsed',
|
|
13
32
|
});
|
|
33
|
+
/** Width class the shell is laid out for; `AdminShell` keeps it current. */
|
|
34
|
+
viewport = $state('desktop');
|
|
14
35
|
focusTools = $state([]);
|
|
15
36
|
activeFocusToolId = $state(null);
|
|
16
37
|
activities = $state([]);
|
|
38
|
+
/**
|
|
39
|
+
* Side edges whose `overlayMedia` currently matches; `AdminShell` keeps
|
|
40
|
+
* it current. Read it through `presentationFor`.
|
|
41
|
+
*/
|
|
42
|
+
overlayMatches = $state({});
|
|
17
43
|
activityListeners = new Set();
|
|
18
44
|
constructor(options = {}) {
|
|
19
45
|
this.config = resolveShellConfig(options.config);
|
|
@@ -22,23 +48,135 @@ export class ShellState {
|
|
|
22
48
|
(options.storageKey
|
|
23
49
|
? new LocalStorageShellSettingsAdapter(options.storageKey)
|
|
24
50
|
: null);
|
|
51
|
+
this.viewport = options.viewport ?? detectShellViewport();
|
|
25
52
|
this.applySettings(options.settings ?? {}, { persist: false });
|
|
26
53
|
}
|
|
27
54
|
async hydrate() {
|
|
28
55
|
if (!this.adapter)
|
|
29
56
|
return;
|
|
30
57
|
const delta = await this.adapter.read();
|
|
31
|
-
if (delta)
|
|
32
|
-
this.applySettings(delta, {
|
|
58
|
+
if (delta) {
|
|
59
|
+
this.applySettings(stripUnpersistedSettings(delta, this.config), {
|
|
60
|
+
persist: false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Record the viewport class. When it changes, edges with
|
|
66
|
+
* `viewportDefaults` switch to their default for the new class (the
|
|
67
|
+
* user's toggle for the old class is dropped). Not persisted.
|
|
68
|
+
*/
|
|
69
|
+
setViewport(viewport) {
|
|
70
|
+
untrack(() => {
|
|
71
|
+
if (viewport === this.viewport)
|
|
72
|
+
return;
|
|
73
|
+
this.viewport = viewport;
|
|
74
|
+
let changed = false;
|
|
75
|
+
const panels = { ...(this.settings.panels ?? {}) };
|
|
76
|
+
for (const edge of PANEL_EDGES) {
|
|
77
|
+
const config = this.config.panels[edge];
|
|
78
|
+
const next = config.viewportDefaults?.[viewport];
|
|
79
|
+
if (!next || config.initial === 'hidden')
|
|
80
|
+
continue;
|
|
81
|
+
delete panels[edge];
|
|
82
|
+
changed = true;
|
|
83
|
+
if (next === 'expanded')
|
|
84
|
+
this.closeExclusivePeers(edge);
|
|
85
|
+
this.panels[edge] = next;
|
|
86
|
+
}
|
|
87
|
+
if (changed)
|
|
88
|
+
this.settings = { ...this.settings, panels };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** How a side edge is presented on phones (`drawer` unless configured). */
|
|
92
|
+
phonePresentation(edge) {
|
|
93
|
+
return this.config.panels[edge].phone ?? 'drawer';
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* How an edge is presented right now: its configured presentation, or
|
|
97
|
+
* `overlay` for a side edge whose `overlayMedia` matches off phones.
|
|
98
|
+
*/
|
|
99
|
+
presentationFor(edge) {
|
|
100
|
+
const config = this.config.panels[edge];
|
|
101
|
+
if ((edge === 'left' || edge === 'right') &&
|
|
102
|
+
this.viewport !== 'phone' &&
|
|
103
|
+
config.overlayMedia &&
|
|
104
|
+
this.overlayMatches[edge]) {
|
|
105
|
+
return 'overlay';
|
|
106
|
+
}
|
|
107
|
+
return config.presentation;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Close side edges that would open as overlays (a restored or initial
|
|
111
|
+
* `expanded` state), without changing the stored preference: an overlay
|
|
112
|
+
* never covers the page on load, while a docked edge still restores.
|
|
113
|
+
*/
|
|
114
|
+
closeOverlaidEdges() {
|
|
115
|
+
untrack(() => {
|
|
116
|
+
for (const edge of ['left', 'right']) {
|
|
117
|
+
if (this.panels[edge] === 'expanded' &&
|
|
118
|
+
this.presentationFor(edge) === 'overlay') {
|
|
119
|
+
this.panels[edge] = 'collapsed';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** Record whether a side edge's `overlayMedia` matches. */
|
|
125
|
+
setOverlayMatch(edge, matches) {
|
|
126
|
+
untrack(() => {
|
|
127
|
+
if (Boolean(this.overlayMatches[edge]) === matches)
|
|
128
|
+
return;
|
|
129
|
+
this.overlayMatches = { ...this.overlayMatches, [edge]: matches };
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Whether an edge is rendered at the current viewport. */
|
|
133
|
+
isEdgeShown(edge) {
|
|
134
|
+
if (this.panels[edge] === 'hidden')
|
|
135
|
+
return false;
|
|
136
|
+
return !(this.viewport === 'phone' && this.phonePresentation(edge) === 'hidden');
|
|
137
|
+
}
|
|
138
|
+
/** Resize limits for an edge, or `null` when it is not resizable. */
|
|
139
|
+
resizeLimits(edge) {
|
|
140
|
+
return resolvePanelResize(edge, this.config.panels[edge]);
|
|
141
|
+
}
|
|
142
|
+
/** The user's resized width for an edge in px, or `null` (configured size). */
|
|
143
|
+
panelSize(edge) {
|
|
144
|
+
if (!this.resizeLimits(edge))
|
|
145
|
+
return null;
|
|
146
|
+
const size = this.settings.sizes?.[edge];
|
|
147
|
+
return typeof size === 'number' && Number.isFinite(size) ? size : null;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resize an edge's expanded width (px, clamped to its limits); `null`
|
|
151
|
+
* resets it to the configured `expandedSize`. Persisted unless
|
|
152
|
+
* `persist: false` (use that while a drag is in progress) or the edge
|
|
153
|
+
* opts out of storing its size.
|
|
154
|
+
*/
|
|
155
|
+
setPanelSize(edge, size, options = {}) {
|
|
156
|
+
untrack(() => {
|
|
157
|
+
const limits = this.resizeLimits(edge);
|
|
158
|
+
if (!limits)
|
|
159
|
+
return;
|
|
160
|
+
const next = size === null || !Number.isFinite(size)
|
|
161
|
+
? null
|
|
162
|
+
: clampPanelSize(size, limits);
|
|
163
|
+
this.settings = mergeShellSettingsDelta(this.settings, {
|
|
164
|
+
sizes: { [edge]: next },
|
|
165
|
+
});
|
|
166
|
+
if (options.persist !== false)
|
|
167
|
+
void this.persistSettings();
|
|
168
|
+
});
|
|
33
169
|
}
|
|
34
170
|
snapshot() {
|
|
35
171
|
return {
|
|
36
172
|
panels: { ...this.panels },
|
|
173
|
+
viewport: this.viewport,
|
|
37
174
|
activeFocusToolId: this.activeFocusToolId,
|
|
38
175
|
settings: {
|
|
39
176
|
...this.settings,
|
|
40
177
|
keymap: this.settings.keymap ? { ...this.settings.keymap } : undefined,
|
|
41
178
|
panels: this.settings.panels ? { ...this.settings.panels } : undefined,
|
|
179
|
+
sizes: this.settings.sizes ? { ...this.settings.sizes } : undefined,
|
|
42
180
|
},
|
|
43
181
|
};
|
|
44
182
|
}
|
|
@@ -48,7 +186,7 @@ export class ShellState {
|
|
|
48
186
|
untrack(() => {
|
|
49
187
|
this.settings = mergeShellSettingsDelta(this.settings, nextDelta);
|
|
50
188
|
for (const edge of PANEL_EDGES) {
|
|
51
|
-
this.panels[edge] = resolveInitialPanelState(edge, this.config.panels[edge], this.settings);
|
|
189
|
+
this.panels[edge] = resolveInitialPanelState(edge, this.config.panels[edge], this.settings, this.viewport);
|
|
52
190
|
}
|
|
53
191
|
this.activeFocusToolId =
|
|
54
192
|
this.settings.activeFocusToolId ?? this.activeFocusToolId;
|
|
@@ -69,6 +207,13 @@ export class ShellState {
|
|
|
69
207
|
});
|
|
70
208
|
}
|
|
71
209
|
setPanel(edge, state) {
|
|
210
|
+
this.setPanelState(edge, state);
|
|
211
|
+
}
|
|
212
|
+
/** Set and persist a runtime panel preference, including hidden. */
|
|
213
|
+
setPanelState(edge, state) {
|
|
214
|
+
if (!PANEL_EDGES.includes(edge) ||
|
|
215
|
+
!['hidden', 'collapsed', 'expanded'].includes(state))
|
|
216
|
+
return;
|
|
72
217
|
untrack(() => {
|
|
73
218
|
if (this.config.panels[edge].initial === 'hidden') {
|
|
74
219
|
this.panels[edge] = 'hidden';
|
|
@@ -99,7 +244,7 @@ export class ShellState {
|
|
|
99
244
|
closeTopmostExpanded() {
|
|
100
245
|
return untrack(() => {
|
|
101
246
|
for (const edge of [...PANEL_EDGES].reverse()) {
|
|
102
|
-
if (this.panels[edge] === 'expanded') {
|
|
247
|
+
if (this.panels[edge] === 'expanded' && this.isEdgeShown(edge)) {
|
|
103
248
|
this.collapsePanel(edge);
|
|
104
249
|
return true;
|
|
105
250
|
}
|
|
@@ -255,7 +400,9 @@ export class ShellState {
|
|
|
255
400
|
}
|
|
256
401
|
}
|
|
257
402
|
async persistSettings() {
|
|
258
|
-
|
|
403
|
+
if (!this.adapter)
|
|
404
|
+
return;
|
|
405
|
+
await this.adapter.write(stripUnpersistedSettings(this.settings, this.config));
|
|
259
406
|
}
|
|
260
407
|
emitActivity(event) {
|
|
261
408
|
for (const listener of this.activityListeners)
|
|
@@ -297,6 +444,9 @@ function snapshotShellSettingsDelta(delta) {
|
|
|
297
444
|
if ('panels' in delta) {
|
|
298
445
|
snapshot.panels = delta.panels ? { ...delta.panels } : delta.panels;
|
|
299
446
|
}
|
|
447
|
+
if ('sizes' in delta) {
|
|
448
|
+
snapshot.sizes = delta.sizes ? { ...delta.sizes } : delta.sizes;
|
|
449
|
+
}
|
|
300
450
|
return snapshot;
|
|
301
451
|
}
|
|
302
452
|
function snapshotShellKeymap(keymap) {
|