@happyvertical/smrt-svelte 0.51.38 → 0.52.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 +167 -1
- package/agents/workspace.md +24 -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/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__/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 +18 -0
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +586 -0
- package/dist/components/workspace/__tests__/AdminShell.test.js +85 -0
- package/dist/components/workspace/__tests__/TenantNav.test.js +49 -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-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 +998 -40
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +42 -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 +46 -1
- package/dist/components/workspace/admin-shell/TenantNav.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 +55 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +149 -6
- package/dist/components/workspace/admin-shell/types.d.ts +97 -0
- 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/components/workspace/tools-dock/ToolsDock.svelte +2 -0
- package/dist/i18n/strings.workspace.d.ts +10 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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 +16 -16
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM wiring for AdminShell's responsive chrome. Every installer returns a
|
|
3
|
+
* disposer and is a no-op without the browser API it needs, so callers can
|
|
4
|
+
* run them from `onMount` without guards. The decisions live in
|
|
5
|
+
* `./mobile-shell.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { isEditableTarget } from './hotkeys.js';
|
|
8
|
+
import { FORM_ACTION_BAR_SELECTOR, keyboardLikelyOpen, ScrollChrome, viewportFor, } from './mobile-shell.js';
|
|
9
|
+
import { ADMIN_SHELL_DESKTOP_QUERY, ADMIN_SHELL_PHONE_QUERY, } from './state.svelte.js';
|
|
10
|
+
/**
|
|
11
|
+
* Keep `shell.viewport` in step with the phone/desktop media queries (which
|
|
12
|
+
* applies each edge's `viewportDefaults` when the class changes).
|
|
13
|
+
*/
|
|
14
|
+
export function installShellViewport(shell) {
|
|
15
|
+
if (typeof window === 'undefined' ||
|
|
16
|
+
typeof window.matchMedia !== 'function') {
|
|
17
|
+
return () => { };
|
|
18
|
+
}
|
|
19
|
+
const phone = window.matchMedia(ADMIN_SHELL_PHONE_QUERY);
|
|
20
|
+
const desktop = window.matchMedia(ADMIN_SHELL_DESKTOP_QUERY);
|
|
21
|
+
const sync = () => shell.setViewport(viewportFor({ phone: phone.matches, desktop: desktop.matches }));
|
|
22
|
+
sync();
|
|
23
|
+
phone.addEventListener('change', sync);
|
|
24
|
+
desktop.addEventListener('change', sync);
|
|
25
|
+
return () => {
|
|
26
|
+
phone.removeEventListener('change', sync);
|
|
27
|
+
desktop.removeEventListener('change', sync);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Keep `shell.overlayMatches` in step with each side edge's `overlayMedia`
|
|
32
|
+
* query (edges without one are skipped).
|
|
33
|
+
*/
|
|
34
|
+
export function installShellOverlayMedia(shell) {
|
|
35
|
+
if (typeof window === 'undefined' ||
|
|
36
|
+
typeof window.matchMedia !== 'function') {
|
|
37
|
+
return () => { };
|
|
38
|
+
}
|
|
39
|
+
const disposers = [];
|
|
40
|
+
for (const edge of ['left', 'right']) {
|
|
41
|
+
const query = shell.config.panels[edge].overlayMedia;
|
|
42
|
+
if (!query)
|
|
43
|
+
continue;
|
|
44
|
+
const media = window.matchMedia(query);
|
|
45
|
+
const sync = () => shell.setOverlayMatch(edge, media.matches);
|
|
46
|
+
sync();
|
|
47
|
+
media.addEventListener('change', sync);
|
|
48
|
+
disposers.push(() => media.removeEventListener('change', sync));
|
|
49
|
+
}
|
|
50
|
+
return () => {
|
|
51
|
+
for (const dispose of disposers)
|
|
52
|
+
dispose();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Hide-on-scroll for one scroller (AdminShell's main region). Samples once
|
|
57
|
+
* per animation frame and reports changes of the hidden state.
|
|
58
|
+
*/
|
|
59
|
+
export function installScrollChrome(scroller, options) {
|
|
60
|
+
const chrome = new ScrollChrome(options.tuning);
|
|
61
|
+
let frame = 0;
|
|
62
|
+
let last = false;
|
|
63
|
+
function sample() {
|
|
64
|
+
frame = 0;
|
|
65
|
+
const hidden = chrome.update({
|
|
66
|
+
y: scroller.scrollTop,
|
|
67
|
+
maxY: scroller.scrollHeight - scroller.clientHeight,
|
|
68
|
+
pinned: options.isPinned(),
|
|
69
|
+
});
|
|
70
|
+
if (hidden !== last) {
|
|
71
|
+
last = hidden;
|
|
72
|
+
options.onChange(hidden);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function onScroll() {
|
|
76
|
+
if (!frame)
|
|
77
|
+
frame = requestAnimationFrame(sample);
|
|
78
|
+
}
|
|
79
|
+
scroller.addEventListener('scroll', onScroll, { passive: true });
|
|
80
|
+
return {
|
|
81
|
+
recheck: sample,
|
|
82
|
+
reset() {
|
|
83
|
+
chrome.reset();
|
|
84
|
+
if (last) {
|
|
85
|
+
last = false;
|
|
86
|
+
options.onChange(false);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
destroy() {
|
|
90
|
+
if (frame)
|
|
91
|
+
cancelAnimationFrame(frame);
|
|
92
|
+
scroller.removeEventListener('scroll', onScroll);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Watch for the on-screen keyboard. Sets `data-keyboard-open` on `<html>`
|
|
98
|
+
* (phone bars and `FormActionBar` hide on it) and keeps the focused field in
|
|
99
|
+
* view when the keyboard opens.
|
|
100
|
+
*/
|
|
101
|
+
export function installKeyboardWatcher(onChange) {
|
|
102
|
+
if (typeof window === 'undefined' || !window.visualViewport) {
|
|
103
|
+
return () => { };
|
|
104
|
+
}
|
|
105
|
+
const viewport = window.visualViewport;
|
|
106
|
+
let open = false;
|
|
107
|
+
function sync() {
|
|
108
|
+
const next = keyboardLikelyOpen({
|
|
109
|
+
layoutHeight: window.innerHeight,
|
|
110
|
+
visualHeight: viewport.height,
|
|
111
|
+
scale: viewport.scale,
|
|
112
|
+
});
|
|
113
|
+
if (next === open)
|
|
114
|
+
return;
|
|
115
|
+
open = next;
|
|
116
|
+
if (open)
|
|
117
|
+
document.documentElement.dataset.keyboardOpen = '';
|
|
118
|
+
else
|
|
119
|
+
delete document.documentElement.dataset.keyboardOpen;
|
|
120
|
+
onChange(open);
|
|
121
|
+
const active = document.activeElement;
|
|
122
|
+
if (open && active instanceof HTMLElement && isEditableTarget(active)) {
|
|
123
|
+
active.scrollIntoView({ block: 'center' });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
viewport.addEventListener('resize', sync);
|
|
127
|
+
return () => {
|
|
128
|
+
viewport.removeEventListener('resize', sync);
|
|
129
|
+
delete document.documentElement.dataset.keyboardOpen;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Report whether `container` holds a form action bar
|
|
134
|
+
* (`[data-form-action-bar]`), now and whenever its subtree changes.
|
|
135
|
+
*/
|
|
136
|
+
export function watchFormActionBar(container, onChange) {
|
|
137
|
+
let present = Boolean(container.querySelector(FORM_ACTION_BAR_SELECTOR));
|
|
138
|
+
onChange(present);
|
|
139
|
+
if (typeof MutationObserver === 'undefined')
|
|
140
|
+
return () => { };
|
|
141
|
+
const observer = new MutationObserver(() => {
|
|
142
|
+
const next = Boolean(container.querySelector(FORM_ACTION_BAR_SELECTOR));
|
|
143
|
+
if (next === present)
|
|
144
|
+
return;
|
|
145
|
+
present = next;
|
|
146
|
+
onChange(present);
|
|
147
|
+
});
|
|
148
|
+
observer.observe(container, { childList: true, subtree: true });
|
|
149
|
+
return () => observer.disconnect();
|
|
150
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic behind AdminShell's phone chrome: the hide-on-scroll state
|
|
3
|
+
* machine, the on-screen keyboard heuristic, the phone top-bar model, and the
|
|
4
|
+
* bottom-bar mode. DOM-free and SSR-safe so it can be unit tested; the DOM
|
|
5
|
+
* wiring lives in `./mobile-shell-dom.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Component, ComponentType, SvelteComponent } from 'svelte';
|
|
8
|
+
import type { ShellViewport } from './types.js';
|
|
9
|
+
/** Classify a viewport from the phone and desktop media-query matches. */
|
|
10
|
+
export declare function viewportFor(matches: {
|
|
11
|
+
phone: boolean;
|
|
12
|
+
desktop: boolean;
|
|
13
|
+
}): ShellViewport;
|
|
14
|
+
/** Tuning for {@link ScrollChrome}. */
|
|
15
|
+
export interface ScrollChromeOptions {
|
|
16
|
+
/** Always show the chrome within this many px of the top (default 56). */
|
|
17
|
+
revealWithin?: number;
|
|
18
|
+
/** Downward travel in px before the chrome hides (default 12). */
|
|
19
|
+
hideAfter?: number;
|
|
20
|
+
/** Upward travel in px before it comes back (default 4, "first scroll up"). */
|
|
21
|
+
showAfter?: number;
|
|
22
|
+
}
|
|
23
|
+
/** One scroll position fed to {@link ScrollChrome.update}. */
|
|
24
|
+
export interface ScrollSample {
|
|
25
|
+
/** Current scrollTop. */
|
|
26
|
+
y: number;
|
|
27
|
+
/** Largest reachable scrollTop, to ignore iOS overscroll bounce. */
|
|
28
|
+
maxY?: number;
|
|
29
|
+
/** Keep the chrome shown (a banner is visible, a drawer is open, …). */
|
|
30
|
+
pinned?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Hide-on-scroll state machine for the phone top bar (and anything that
|
|
34
|
+
* slides with it). Feed it every scroll position; read `hidden`. Travel
|
|
35
|
+
* accumulates in one direction and resets when the direction reverses.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ScrollChrome {
|
|
38
|
+
#private;
|
|
39
|
+
hidden: boolean;
|
|
40
|
+
constructor(options?: ScrollChromeOptions);
|
|
41
|
+
/** Feed a scroll sample; returns whether the chrome is now hidden. */
|
|
42
|
+
update(sample: ScrollSample): boolean;
|
|
43
|
+
/** Show the chrome and forget the scroll history (e.g. after navigating). */
|
|
44
|
+
reset(): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `visualViewport` heuristic: the keyboard is open when the visible viewport
|
|
48
|
+
* is much shorter than the layout viewport. `scale` undoes pinch zoom, which
|
|
49
|
+
* also shrinks `visualViewport.height`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function keyboardLikelyOpen(input: {
|
|
52
|
+
layoutHeight: number;
|
|
53
|
+
visualHeight: number;
|
|
54
|
+
scale?: number;
|
|
55
|
+
threshold?: number;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/** A nav entry as far as path matching is concerned. */
|
|
58
|
+
export interface ShellNavPathItem {
|
|
59
|
+
href: string;
|
|
60
|
+
exact?: boolean;
|
|
61
|
+
children?: ShellNavPathItem[];
|
|
62
|
+
}
|
|
63
|
+
/** Drop trailing slashes (`/` stays `/`). */
|
|
64
|
+
export declare function normalizeShellPath(path: string): string;
|
|
65
|
+
/** Whether `path` is the item's page or (unless `exact`) below it. */
|
|
66
|
+
export declare function shellNavItemMatches(item: ShellNavPathItem, path: string): boolean;
|
|
67
|
+
/** The chain of nav items from the top level down to the one matching `path`. */
|
|
68
|
+
export declare function findShellNavTrail<T extends ShellNavPathItem>(items: T[], path: string): T[];
|
|
69
|
+
/** One ancestor of the current page: a breadcrumb and a possible back target. */
|
|
70
|
+
export interface ShellCrumb {
|
|
71
|
+
label: string;
|
|
72
|
+
href: string;
|
|
73
|
+
}
|
|
74
|
+
/** Input for {@link shellPageTrailFor}. */
|
|
75
|
+
export interface ShellPageTrailInput<T extends ShellNavPathItem & {
|
|
76
|
+
label: string;
|
|
77
|
+
}> {
|
|
78
|
+
/** Current pathname. */
|
|
79
|
+
path: string;
|
|
80
|
+
/** The workspace home (overview) page. */
|
|
81
|
+
homeHref: string;
|
|
82
|
+
/** Workspace name: the first ancestor of every non-home page. */
|
|
83
|
+
homeTitle: string;
|
|
84
|
+
/** The workspace nav. */
|
|
85
|
+
navItems: T[];
|
|
86
|
+
/**
|
|
87
|
+
* Ancestors deeper than the nav knows (an article above its video, a
|
|
88
|
+
* council above its meeting), nearest last. They follow the nav trail.
|
|
89
|
+
*/
|
|
90
|
+
parents?: readonly ShellCrumb[] | null;
|
|
91
|
+
}
|
|
92
|
+
/** Where a page sits: a section home, or a page with ancestors. */
|
|
93
|
+
export interface ShellPageTrail {
|
|
94
|
+
/** The home page or a top-level nav page (and no page-given parents). */
|
|
95
|
+
sectionHome: boolean;
|
|
96
|
+
/** Ancestors only, never the current page; empty on section homes. */
|
|
97
|
+
crumbs: ShellCrumb[];
|
|
98
|
+
/** The nav item for the current page itself, if it is one. */
|
|
99
|
+
current: {
|
|
100
|
+
label: string;
|
|
101
|
+
href: string;
|
|
102
|
+
} | null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The single source for a page's place in the workspace: breadcrumbs on
|
|
106
|
+
* wider screens and the phone top bar's back target both come from here.
|
|
107
|
+
*
|
|
108
|
+
* Section homes (the home page and top-level nav pages) have no ancestors.
|
|
109
|
+
* Anything else lists the workspace home, the nav items above it (a nested
|
|
110
|
+
* nav page's parent item counts, even when its URL is not below it), then the
|
|
111
|
+
* page-given `parents`. The current page is never a crumb.
|
|
112
|
+
*/
|
|
113
|
+
export declare function shellPageTrailFor<T extends ShellNavPathItem & {
|
|
114
|
+
label: string;
|
|
115
|
+
}>(input: ShellPageTrailInput<T>): ShellPageTrail;
|
|
116
|
+
/**
|
|
117
|
+
* What the phone top bar shows: the workspace name on section home pages, or
|
|
118
|
+
* a back arrow (to the parent, never `history.back()`) plus the page's
|
|
119
|
+
* title on every other page.
|
|
120
|
+
*/
|
|
121
|
+
export type PhoneTopBarModel = {
|
|
122
|
+
kind: 'home';
|
|
123
|
+
title: string;
|
|
124
|
+
} | {
|
|
125
|
+
kind: 'detail';
|
|
126
|
+
title: string;
|
|
127
|
+
backHref: string;
|
|
128
|
+
backLabel: string;
|
|
129
|
+
};
|
|
130
|
+
/** Input for {@link phoneTopBarFor}. */
|
|
131
|
+
export interface PhoneTopBarInput<T extends ShellNavPathItem & {
|
|
132
|
+
label: string;
|
|
133
|
+
}> extends ShellPageTrailInput<T> {
|
|
134
|
+
/** Title the page provided (article headline, person's name, …). */
|
|
135
|
+
pageTitle?: string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Explicit back target. Prefer `parents` (the same ancestors the page's
|
|
138
|
+
* breadcrumbs show); this overrides the last one.
|
|
139
|
+
*/
|
|
140
|
+
backHref?: string | null;
|
|
141
|
+
/** Back label used with an explicit `backHref` (default "Back"). */
|
|
142
|
+
backLabel?: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Section homes (see {@link shellPageTrailFor}) show the workspace name.
|
|
146
|
+
* Every other page shows a back arrow to its nearest ancestor (the last
|
|
147
|
+
* breadcrumb) and the page's title.
|
|
148
|
+
*/
|
|
149
|
+
export declare function phoneTopBarFor<T extends ShellNavPathItem & {
|
|
150
|
+
label: string;
|
|
151
|
+
}>(input: PhoneTopBarInput<T>): PhoneTopBarModel;
|
|
152
|
+
/**
|
|
153
|
+
* What sits at the bottom of a phone screen: the nav bar, a form's own
|
|
154
|
+
* action bar (which replaces it), or nothing while the keyboard is open.
|
|
155
|
+
*/
|
|
156
|
+
export type BottomBarMode = 'nav' | 'form' | 'none';
|
|
157
|
+
/** Decide the {@link BottomBarMode}. */
|
|
158
|
+
export declare function bottomBarMode(input: {
|
|
159
|
+
formActions: boolean;
|
|
160
|
+
keyboardOpen: boolean;
|
|
161
|
+
}): BottomBarMode;
|
|
162
|
+
/** Selector a form's action row carries to replace the phone bottom bar. */
|
|
163
|
+
export declare const FORM_ACTION_BAR_SELECTOR = "[data-form-action-bar]";
|
|
164
|
+
/**
|
|
165
|
+
* An icon component: a Svelte 5 function component or a legacy class
|
|
166
|
+
* component (e.g. lucide-svelte icons).
|
|
167
|
+
*/
|
|
168
|
+
export type ShellIcon = Component<{
|
|
169
|
+
size?: number | string;
|
|
170
|
+
}> | ComponentType<SvelteComponent<{
|
|
171
|
+
size?: number | string;
|
|
172
|
+
}>>;
|
|
173
|
+
/** One phone bottom bar entry: a link (`href`; `onclick` also runs) or a button. */
|
|
174
|
+
export interface PhoneBottomBarItem {
|
|
175
|
+
id: string;
|
|
176
|
+
label: string;
|
|
177
|
+
icon: ShellIcon;
|
|
178
|
+
href?: string;
|
|
179
|
+
onclick?: () => void;
|
|
180
|
+
active?: boolean;
|
|
181
|
+
/** Count badge (hidden at 0). */
|
|
182
|
+
badge?: number;
|
|
183
|
+
/** Small dot, e.g. an unread reply. */
|
|
184
|
+
dot?: boolean;
|
|
185
|
+
/** Accessible description of the dot (default "new"). */
|
|
186
|
+
dotLabel?: string;
|
|
187
|
+
/** For items that open a panel: its expanded state and id. */
|
|
188
|
+
expanded?: boolean;
|
|
189
|
+
controls?: string;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=mobile-shell.d.ts.map
|
|
@@ -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 {
|