@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.
Files changed (143) hide show
  1. package/AGENTS.md +3 -0
  2. package/README.md +214 -1
  3. package/agents/components.md +8 -1
  4. package/agents/workspace.md +24 -0
  5. package/dist/Provider.svelte +19 -15
  6. package/dist/Provider.svelte.d.ts +2 -2
  7. package/dist/Provider.svelte.d.ts.map +1 -1
  8. package/dist/__tests__/provider-mode-harness.svelte +14 -0
  9. package/dist/__tests__/provider-mode-harness.svelte.d.ts +11 -0
  10. package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +1 -0
  11. package/dist/__tests__/provider-mode-probe.svelte +13 -0
  12. package/dist/__tests__/provider-mode-probe.svelte.d.ts +8 -0
  13. package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +1 -0
  14. package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
  15. package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
  16. package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
  17. package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
  18. package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
  19. package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
  20. package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
  21. package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
  22. package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
  23. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
  24. package/dist/browser-ai/adapters/stt/index.js +2 -1
  25. package/dist/components/forms/FileUpload.svelte +120 -16
  26. package/dist/components/forms/FileUpload.svelte.d.ts +7 -1
  27. package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -1
  28. package/dist/components/forms/Form.svelte +118 -8
  29. package/dist/components/forms/Form.svelte.d.ts +18 -2
  30. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  31. package/dist/components/forms/FormScope.svelte +202 -0
  32. package/dist/components/forms/FormScope.svelte.d.ts +22 -0
  33. package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
  34. package/dist/components/forms/__tests__/FileUpload.native-form.test.js +249 -0
  35. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +24 -0
  36. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +10 -0
  37. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +1 -0
  38. package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
  39. package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
  40. package/dist/components/forms/__tests__/FormScope.test.js +39 -0
  41. package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
  42. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
  43. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
  44. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
  45. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
  46. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
  47. package/dist/components/forms/index.d.ts +1 -0
  48. package/dist/components/forms/index.d.ts.map +1 -1
  49. package/dist/components/forms/index.js +1 -0
  50. package/dist/components/workspace/README.md +11 -0
  51. package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +597 -0
  52. package/dist/components/workspace/__tests__/AdminShell.test.js +257 -1
  53. package/dist/components/workspace/__tests__/TenantNav.test.js +115 -0
  54. package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +6 -0
  55. package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
  56. package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
  57. package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
  58. package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
  59. package/dist/components/workspace/__tests__/admin-shell-state.test.js +25 -0
  60. package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
  61. package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
  62. package/dist/components/workspace/admin-shell/AdminShell.svelte +1099 -59
  63. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +44 -1
  64. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  65. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
  66. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
  67. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
  68. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
  69. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
  70. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
  71. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
  72. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
  73. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
  74. package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
  75. package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
  76. package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
  77. package/dist/components/workspace/admin-shell/TenantNav.svelte +116 -13
  78. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -2
  79. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  80. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +16 -2
  81. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +2 -0
  82. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
  83. package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
  84. package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
  85. package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
  86. package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
  87. package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
  88. package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
  89. package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
  90. package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
  91. package/dist/components/workspace/admin-shell/settings.js +84 -2
  92. package/dist/components/workspace/admin-shell/state.svelte.d.ts +57 -1
  93. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  94. package/dist/components/workspace/admin-shell/state.svelte.js +156 -6
  95. package/dist/components/workspace/admin-shell/types.d.ts +115 -1
  96. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  97. package/dist/components/workspace/admin-shell/types.js +13 -0
  98. package/dist/components/workspace/index.d.ts +11 -4
  99. package/dist/components/workspace/index.d.ts.map +1 -1
  100. package/dist/components/workspace/index.js +12 -3
  101. package/dist/i18n/strings.forms.d.ts +3 -0
  102. package/dist/i18n/strings.forms.d.ts.map +1 -1
  103. package/dist/i18n/strings.forms.js +3 -0
  104. package/dist/i18n/strings.workspace.d.ts +14 -0
  105. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  106. package/dist/i18n/strings.workspace.js +14 -0
  107. package/dist/index.d.ts +1 -1
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +1 -1
  110. package/dist/provider-mode.test.js +86 -0
  111. package/dist/state/app-state.svelte.d.ts.map +1 -1
  112. package/dist/state/app-state.svelte.js +8 -8
  113. package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
  114. package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
  115. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
  116. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
  117. package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
  118. package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
  119. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
  120. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
  121. package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
  122. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
  123. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
  124. package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
  125. package/dist/web/__tests__/webmcp-ui.test.js +18 -0
  126. package/dist/web/index.d.ts +4 -1
  127. package/dist/web/index.d.ts.map +1 -1
  128. package/dist/web/index.js +4 -1
  129. package/dist/web/link-surface.svelte.d.ts +14 -0
  130. package/dist/web/link-surface.svelte.d.ts.map +1 -0
  131. package/dist/web/link-surface.svelte.js +49 -0
  132. package/dist/web/list-surface.svelte.d.ts +80 -0
  133. package/dist/web/list-surface.svelte.d.ts.map +1 -0
  134. package/dist/web/list-surface.svelte.js +309 -0
  135. package/dist/web/step-surface.svelte.d.ts +17 -0
  136. package/dist/web/step-surface.svelte.d.ts.map +1 -0
  137. package/dist/web/step-surface.svelte.js +56 -0
  138. package/dist/web/webmcp-ui-context.d.ts +7 -0
  139. package/dist/web/webmcp-ui-context.d.ts.map +1 -1
  140. package/dist/web/webmcp-ui-context.js +10 -0
  141. package/dist/web/webmcp-ui.d.ts.map +1 -1
  142. package/dist/web/webmcp-ui.js +16 -3
  143. 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
- export declare function resolveInitialPanelState(edge: PanelEdge, config: ShellPanelConfig, settings?: ShellSettingsDelta): PanelState;
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,EACnB,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,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,gBAAgB,EACxB,QAAQ,GAAE,kBAAuB,GAChC,UAAU,CAGZ;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,CAapB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,CAepB;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"}
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
- export function resolveInitialPanelState(edge, config, settings = {}) {
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] ?? config.initial;
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":"AAOA,OAAO,KAAK,EAEV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAGpB,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAC5D,KAAK,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAE9C,QAAQ,qBAAkC;IAC1C,MAAM,gCAKH;IACH,UAAU,mBAAgC;IAC1C,iBAAiB,gBAA+B;IAChD,UAAU,kBAA+B;IAEzC,OAAO,CAAC,iBAAiB,CAA+B;gBAE5C,OAAO,GAAE,iBAAsB;IAUrC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,QAAQ,IAAI,kBAAkB;IAY9B,aAAa,CACX,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,IAAI;IAkBP,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAUrD,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAezD,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAUlC,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAIlC,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAIpC,oBAAoB,IAAI,OAAO;IAY/B,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,IAAI;IAYnD,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IASrC,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAY/B,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAWjC,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IA4B7C,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAc3D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAWhC,eAAe,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAKvD,cAAc,CAAC,MAAM,GAAE,mBAAwB,GAAG,aAAa,EAAE;IASjE,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,kBAAkB;IAsBlD,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS;IAW9C,OAAO,CAAC,mBAAmB;YAmBb,eAAe;IAI7B,OAAO,CAAC,YAAY;CAGrB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAE5E"}
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, { persist: false });
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
- await this.adapter?.write(this.settings);
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) {