@liberation-data/desk 0.1.0 → 0.2.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 (57) hide show
  1. package/README.md +92 -1
  2. package/dist/core/desk.d.ts.map +1 -1
  3. package/dist/core/desk.js +19 -2
  4. package/dist/core/desk.js.map +1 -1
  5. package/dist/core/index.d.ts +2 -0
  6. package/dist/core/index.d.ts.map +1 -1
  7. package/dist/core/index.js +1 -0
  8. package/dist/core/index.js.map +1 -1
  9. package/dist/core/layouts.d.ts +26 -0
  10. package/dist/core/layouts.d.ts.map +1 -0
  11. package/dist/core/layouts.js +70 -0
  12. package/dist/core/layouts.js.map +1 -0
  13. package/dist/core/types.d.ts +3 -0
  14. package/dist/core/types.d.ts.map +1 -1
  15. package/dist/desk.css +146 -1
  16. package/dist/react/Desktop.d.ts +7 -1
  17. package/dist/react/Desktop.d.ts.map +1 -1
  18. package/dist/react/Desktop.js +4 -4
  19. package/dist/react/Desktop.js.map +1 -1
  20. package/dist/react/Dock.d.ts +16 -1
  21. package/dist/react/Dock.d.ts.map +1 -1
  22. package/dist/react/Dock.js +44 -6
  23. package/dist/react/Dock.js.map +1 -1
  24. package/dist/react/MenuBar.d.ts +16 -0
  25. package/dist/react/MenuBar.d.ts.map +1 -1
  26. package/dist/react/MenuBar.js +36 -27
  27. package/dist/react/MenuBar.js.map +1 -1
  28. package/dist/react/context.d.ts +2 -0
  29. package/dist/react/context.d.ts.map +1 -1
  30. package/dist/react/context.js +2 -0
  31. package/dist/react/context.js.map +1 -1
  32. package/dist/react/contextMenu.d.ts +22 -0
  33. package/dist/react/contextMenu.d.ts.map +1 -0
  34. package/dist/react/contextMenu.js +146 -0
  35. package/dist/react/contextMenu.js.map +1 -0
  36. package/dist/react/iconView.d.ts +34 -0
  37. package/dist/react/iconView.d.ts.map +1 -0
  38. package/dist/react/iconView.js +112 -0
  39. package/dist/react/iconView.js.map +1 -0
  40. package/dist/react/index.d.ts +6 -2
  41. package/dist/react/index.d.ts.map +1 -1
  42. package/dist/react/index.js +3 -1
  43. package/dist/react/index.js.map +1 -1
  44. package/llms.txt +22 -4
  45. package/package.json +1 -1
  46. package/src/core/desk.ts +17 -2
  47. package/src/core/index.ts +2 -0
  48. package/src/core/layouts.ts +88 -0
  49. package/src/core/types.ts +4 -0
  50. package/src/desk.css +146 -1
  51. package/src/react/Desktop.tsx +11 -2
  52. package/src/react/Dock.tsx +74 -5
  53. package/src/react/MenuBar.tsx +43 -30
  54. package/src/react/context.tsx +3 -0
  55. package/src/react/contextMenu.tsx +198 -0
  56. package/src/react/iconView.tsx +206 -0
  57. package/src/react/index.ts +6 -2
@@ -0,0 +1,146 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { perform } from '../core/commands.js';
5
+ import { formatShortcut, isApplePlatform } from '../core/shortcuts.js';
6
+ import { useOptionalDesk } from './context.js';
7
+ import { resolveMenuItems, selectable, stepMenu, typeAhead } from './MenuBar.js';
8
+ export function useContextMenu({ items, label, disabled }) {
9
+ const desk = useOptionalDesk();
10
+ const [state, setState] = useState(null);
11
+ const panel = useRef(null);
12
+ const menuId = useId();
13
+ const apple = isApplePlatform();
14
+ const show = useCallback((x, y, opener, activeFirst) => {
15
+ const resolved = resolveMenuItems(desk, items());
16
+ if (!resolved.length)
17
+ return;
18
+ setState({ x, y, items: resolved, active: activeFirst ? stepMenu(resolved, -1, 1) : -1, opener });
19
+ }, [desk, items]);
20
+ const close = useCallback((returnFocus) => {
21
+ setState(current => {
22
+ if (current && returnFocus && current.opener.isConnected)
23
+ current.opener.focus({ preventScroll: true });
24
+ return null;
25
+ });
26
+ }, []);
27
+ const choose = (resolved) => {
28
+ if (!state || !resolved || !selectable(resolved))
29
+ return;
30
+ const { opener } = state;
31
+ setState(null);
32
+ // Back to the item first, so a command starts its search for a responder from there.
33
+ if (opener.isConnected)
34
+ opener.focus({ preventScroll: true });
35
+ const { item } = resolved;
36
+ if (item.type === 'command')
37
+ perform(desk, item.command, item.args);
38
+ else if (item.type === 'action')
39
+ item.onSelect();
40
+ };
41
+ const target = {
42
+ onContextMenu: event => {
43
+ if (disabled)
44
+ return;
45
+ event.preventDefault();
46
+ const opener = event.currentTarget;
47
+ opener.focus({ preventScroll: true });
48
+ show(event.clientX, event.clientY, opener, false);
49
+ },
50
+ onKeyDown: event => {
51
+ if (disabled)
52
+ return;
53
+ if (event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey)) {
54
+ event.preventDefault();
55
+ const box = event.currentTarget.getBoundingClientRect();
56
+ show(box.left + 8, box.bottom - 4, event.currentTarget, true);
57
+ }
58
+ },
59
+ };
60
+ // Keep it on screen: measured once it exists, then moved in from the edges it would cross.
61
+ useLayoutEffect(() => {
62
+ const element = panel.current;
63
+ if (!state || !element)
64
+ return;
65
+ const { width, height } = element.getBoundingClientRect();
66
+ const x = Math.max(8, Math.min(state.x, innerWidth - width - 8));
67
+ const y = Math.max(8, state.y + height > innerHeight - 8 ? state.y - height : state.y);
68
+ element.style.left = `${x}px`;
69
+ element.style.top = `${y}px`;
70
+ element.focus({ preventScroll: true });
71
+ }, [state]);
72
+ useEffect(() => {
73
+ if (!state)
74
+ return;
75
+ const onPointerDown = (event) => {
76
+ if (!panel.current?.contains(event.target))
77
+ close(false);
78
+ };
79
+ const onAway = () => close(false);
80
+ document.addEventListener('pointerdown', onPointerDown, true);
81
+ addEventListener('blur', onAway);
82
+ addEventListener('resize', onAway);
83
+ document.addEventListener('scroll', onAway, true);
84
+ return () => {
85
+ document.removeEventListener('pointerdown', onPointerDown, true);
86
+ removeEventListener('blur', onAway);
87
+ removeEventListener('resize', onAway);
88
+ document.removeEventListener('scroll', onAway, true);
89
+ };
90
+ }, [state, close]);
91
+ const onMenuKeyDown = (event) => {
92
+ if (!state)
93
+ return;
94
+ const move = (active) => setState({ ...state, active });
95
+ switch (event.key) {
96
+ case 'ArrowDown':
97
+ move(stepMenu(state.items, state.active, 1));
98
+ break;
99
+ case 'ArrowUp':
100
+ move(stepMenu(state.items, state.active < 0 ? state.items.length : state.active, -1));
101
+ break;
102
+ case 'Home':
103
+ move(stepMenu(state.items, -1, 1));
104
+ break;
105
+ case 'End':
106
+ move(stepMenu(state.items, state.items.length, -1));
107
+ break;
108
+ case 'Enter':
109
+ case ' ':
110
+ choose(state.items[state.active]);
111
+ break;
112
+ case 'Escape':
113
+ close(true);
114
+ break;
115
+ case 'Tab':
116
+ close(true);
117
+ break;
118
+ default: {
119
+ if (event.key.length !== 1 || event.metaKey || event.ctrlKey || event.altKey)
120
+ return;
121
+ const index = typeAhead(state.items, state.active, event.key);
122
+ if (index < 0)
123
+ return;
124
+ move(index);
125
+ }
126
+ }
127
+ event.preventDefault();
128
+ event.stopPropagation();
129
+ };
130
+ const menu = state && typeof document !== 'undefined'
131
+ ? createPortal(_jsx("div", { ref: panel, role: "menu", "aria-label": label, tabIndex: -1, className: "desk-menu desk-context-menu", style: { left: state.x, top: state.y }, "aria-activedescendant": state.active >= 0 ? `${menuId}-${state.active}` : undefined, onKeyDown: onMenuKeyDown, onContextMenu: event => event.preventDefault(), children: state.items.map((resolved, i) => {
132
+ const { item } = resolved;
133
+ if (item.type === 'separator')
134
+ return _jsx("div", { role: "separator", className: "desk-menu-separator" }, i);
135
+ if (item.type === 'header')
136
+ return _jsx("div", { role: "presentation", className: "desk-menu-header", children: item.label }, i);
137
+ const checkable = resolved.checked !== undefined;
138
+ return (_jsxs("div", { id: `${menuId}-${i}`, role: checkable ? 'menuitemcheckbox' : 'menuitem', "aria-checked": checkable ? resolved.checked : undefined, "aria-disabled": !resolved.enabled || undefined, className: "desk-menu-item", "data-active": i === state.active || undefined, onPointerMove: () => {
139
+ if (state.active !== i && selectable(resolved))
140
+ setState({ ...state, active: i });
141
+ }, onClick: () => choose(resolved), children: [_jsx("span", { className: "desk-menu-check", "aria-hidden": "true", children: resolved.checked ? '✓' : '' }), _jsx("span", { className: "desk-menu-label", children: item.label }), item.detail && _jsx("span", { className: "desk-menu-detail", children: item.detail }), item.shortcut && _jsx("kbd", { className: "desk-menu-shortcut", children: formatShortcut(item.shortcut, apple) })] }, i));
142
+ }) }), document.body)
143
+ : null;
144
+ return { target, menu, open: state !== null };
145
+ }
146
+ //# sourceMappingURL=contextMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextMenu.js","sourceRoot":"","sources":["../../src/react/contextMenu.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { MenuItem } from './MenuBar.js';
3
+ export interface IconViewItem {
4
+ readonly id: string;
5
+ readonly label: string;
6
+ readonly icon: ReactNode;
7
+ /** A second line, muted: a kind, a scope, a size. */
8
+ readonly subtitle?: string;
9
+ readonly badge?: ReactNode;
10
+ readonly disabled?: boolean;
11
+ }
12
+ export interface IconViewProps<T extends IconViewItem> {
13
+ readonly items: readonly T[];
14
+ /** Names the collection: "Apps". */
15
+ readonly label: string;
16
+ readonly onOpen: (item: T) => void;
17
+ /** `double` (the default): click selects, double-click or Return opens. `single`: a click opens. */
18
+ readonly openOn?: 'double' | 'single';
19
+ /** Controlled selection. Leave both out and the view keeps its own. */
20
+ readonly selected?: string | null;
21
+ readonly onSelectionChange?: (id: string | null) => void;
22
+ /** The item's context menu: what can be done to this one thing. */
23
+ readonly contextMenu?: (item: T) => readonly MenuItem[];
24
+ /** Lets an icon be picked up and carried, to the dock or into another window. */
25
+ readonly drag?: {
26
+ readonly type: string;
27
+ readonly payload: (item: T) => unknown;
28
+ };
29
+ /** Shown when there are no items. */
30
+ readonly empty?: ReactNode;
31
+ readonly className?: string;
32
+ }
33
+ export declare function IconView<T extends IconViewItem>({ items, label, onOpen, openOn, selected, onSelectionChange, contextMenu, drag, empty, className, }: IconViewProps<T>): import("react").JSX.Element;
34
+ //# sourceMappingURL=iconView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iconView.d.ts","sourceRoot":"","sources":["../../src/react/iconView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAuC,SAAS,EAAE,MAAM,OAAO,CAAA;AAG3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAY5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,YAAY;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;IAClC,oGAAoG;IACpG,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACrC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxD,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,QAAQ,EAAE,CAAA;IACvD,iFAAiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAA;KAAE,CAAA;IACjF,qCAAqC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,YAAY,EAAE,EAC/C,KAAK,EACL,KAAK,EACL,MAAM,EACN,MAAiB,EACjB,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,IAAI,EACJ,KAAK,EACL,SAAS,GACV,EAAE,aAAa,CAAC,CAAC,CAAC,+BAmGlB"}
@@ -0,0 +1,112 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { useContextMenu } from './contextMenu.js';
4
+ import { useDragSource } from './dnd.js';
5
+ export function IconView({ items, label, onOpen, openOn = 'double', selected, onSelectionChange, contextMenu, drag, empty, className, }) {
6
+ const [ownSelection, setOwnSelection] = useState(null);
7
+ const current = selected !== undefined ? selected : ownSelection;
8
+ const select = (id) => {
9
+ if (selected === undefined)
10
+ setOwnSelection(id);
11
+ onSelectionChange?.(id);
12
+ };
13
+ const list = useRef(null);
14
+ const typed = useRef({ text: '', at: 0 });
15
+ const source = useDragSource({ type: drag?.type ?? 'desk.icon', disabled: !drag });
16
+ // The one that takes Tab: the selected item, or the first.
17
+ const focusable = items.some(i => i.id === current) ? current : (items[0]?.id ?? null);
18
+ const cells = () => [...(list.current?.querySelectorAll('[data-icon-id]') ?? [])];
19
+ const focusAt = (index) => {
20
+ const all = cells();
21
+ const target = all[Math.max(0, Math.min(index, all.length - 1))];
22
+ if (!target)
23
+ return;
24
+ target.focus();
25
+ select(target.dataset.iconId ?? null);
26
+ };
27
+ // How many icons fit in a row right now: the ones that share the first one's top edge.
28
+ const columns = () => {
29
+ const all = cells();
30
+ const top = all[0]?.offsetTop;
31
+ const count = all.filter(cell => cell.offsetTop === top).length;
32
+ return Math.max(1, count);
33
+ };
34
+ const onKeyDown = (event) => {
35
+ const all = cells();
36
+ const index = all.findIndex(cell => cell === document.activeElement);
37
+ if (index < 0)
38
+ return;
39
+ const item = items[index];
40
+ switch (event.key) {
41
+ case 'ArrowRight':
42
+ focusAt(index + 1);
43
+ break;
44
+ case 'ArrowLeft':
45
+ focusAt(index - 1);
46
+ break;
47
+ case 'ArrowDown':
48
+ focusAt(index + columns());
49
+ break;
50
+ case 'ArrowUp':
51
+ focusAt(index - columns());
52
+ break;
53
+ case 'Home':
54
+ focusAt(0);
55
+ break;
56
+ case 'End':
57
+ focusAt(all.length - 1);
58
+ break;
59
+ case 'Enter':
60
+ if (item && !item.disabled)
61
+ onOpen(item);
62
+ break;
63
+ case ' ':
64
+ if (item)
65
+ select(item.id);
66
+ break;
67
+ default: {
68
+ if (event.key.length !== 1 || event.metaKey || event.ctrlKey || event.altKey)
69
+ return;
70
+ // Typing a name finds it: letters typed close together build one search, as in a Finder.
71
+ const now = Date.now();
72
+ typed.current = { text: (now - typed.current.at < 700 ? typed.current.text : '') + event.key.toLowerCase(), at: now };
73
+ const search = typed.current.text;
74
+ const order = [...items.slice(index + (search.length === 1 ? 1 : 0)), ...items.slice(0, index + (search.length === 1 ? 1 : 0))];
75
+ const found = order.find(i => i.label.toLowerCase().startsWith(search));
76
+ if (!found)
77
+ return;
78
+ focusAt(items.indexOf(found));
79
+ }
80
+ }
81
+ event.preventDefault();
82
+ };
83
+ // A selection that no longer exists is dropped, so Tab never lands nowhere.
84
+ useEffect(() => {
85
+ if (current && !items.some(i => i.id === current))
86
+ select(null);
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ }, [items, current]);
89
+ if (!items.length)
90
+ return _jsx("div", { className: ['desk-icon-view-empty', className].filter(Boolean).join(' '), children: empty });
91
+ return (_jsx("ul", { ref: list, role: "listbox", "aria-label": label, className: ['desk-icon-view', className].filter(Boolean).join(' '), onKeyDown: onKeyDown, onPointerDown: event => {
92
+ if (event.target === event.currentTarget)
93
+ select(null);
94
+ }, children: items.map(item => (_jsx(IconCell, { item: item, selected: item.id === current, tabbable: item.id === focusable, openOn: openOn, onSelect: () => select(item.id), onOpen: () => onOpen(item), menu: contextMenu ? () => contextMenu(item) : null, dragProps: drag ? source.dragProps(drag.payload(item), _jsx("span", { className: "desk-icon-drag", children: item.icon })) : null }, item.id))) }));
95
+ }
96
+ function IconCell({ item, selected, tabbable, openOn, onSelect, onOpen, menu, dragProps }) {
97
+ const context = useContextMenu({ label: `Actions for ${item.label}`, items: menu ?? (() => []), disabled: !menu });
98
+ // The menu sits beside the cell, not in it: React bubbles a portal's events through its owner,
99
+ // and a click on "Open" must not also count as a click on the icon.
100
+ return (_jsxs(_Fragment, { children: [_jsxs("li", { role: "option", "aria-selected": selected, "aria-disabled": item.disabled || undefined, tabIndex: tabbable ? 0 : -1, "data-icon-id": item.id, className: "desk-icon-cell", ...(dragProps ?? {}), onClick: () => {
101
+ onSelect();
102
+ if (openOn === 'single' && !item.disabled)
103
+ onOpen();
104
+ }, onDoubleClick: () => {
105
+ if (openOn === 'double' && !item.disabled)
106
+ onOpen();
107
+ }, onContextMenu: event => {
108
+ onSelect();
109
+ context.target.onContextMenu(event);
110
+ }, onKeyDown: context.target.onKeyDown, children: [_jsxs("span", { className: "desk-icon-glyph", "aria-hidden": "true", children: [item.icon, item.badge != null && _jsx("span", { className: "desk-icon-badge", children: item.badge })] }), _jsx("span", { className: "desk-icon-label", children: item.label }), item.subtitle && _jsx("span", { className: "desk-icon-subtitle", children: item.subtitle })] }), context.menu] }));
111
+ }
112
+ //# sourceMappingURL=iconView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iconView.js","sourceRoot":"","sources":["../../src/react/iconView.tsx"],"names":[],"mappings":"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"}
@@ -37,7 +37,11 @@ export type { ComposerProps, Message, ThreadProps } from './conversation.js';
37
37
  export { Alert, Popover, Sheet, ToastProvider, useToast } from './overlays.js';
38
38
  export type { AlertProps, PopoverProps, PopoverTriggerProps, SheetProps, Toast, ToastInput } from './overlays.js';
39
39
  export { MenuBar, menuAction, menuCommand, menuHeader, menuSeparator, windowMenuItems } from './MenuBar.js';
40
+ export { useContextMenu } from './contextMenu.js';
41
+ export type { ContextMenuOptions, ContextMenuResult, ContextMenuTargetProps } from './contextMenu.js';
40
42
  export type { Menu, MenuBarProps, MenuItem } from './MenuBar.js';
41
- export { Dock, dockItem, dockSeparator, dockStack } from './Dock.js';
42
- export type { DockEntry, DockItem, DockProps, DockStack } from './Dock.js';
43
+ export { IconView } from './iconView.js';
44
+ export type { IconViewItem, IconViewProps } from './iconView.js';
45
+ export { Dock, DOCK_ITEM, dockItem, dockSeparator, dockStack } from './Dock.js';
46
+ export type { DockEntry, DockItem, DockPins, DockProps, DockStack } from './Dock.js';
43
47
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAChG,YAAY,EACV,OAAO,EACP,IAAI,EACJ,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/E,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACnF,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACtE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACtG,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACzE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACnE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACrG,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC9E,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AACjH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACrG,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC/E,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA"}
@@ -18,5 +18,7 @@ export { BusProvider, InputBar, useBus, useDeskEvent, usePublish, useWindowInput
18
18
  export { Composer, Thread } from './conversation.js';
19
19
  export { Alert, Popover, Sheet, ToastProvider, useToast } from './overlays.js';
20
20
  export { MenuBar, menuAction, menuCommand, menuHeader, menuSeparator, windowMenuItems } from './MenuBar.js';
21
- export { Dock, dockItem, dockSeparator, dockStack } from './Dock.js';
21
+ export { useContextMenu } from './contextMenu.js';
22
+ export { IconView } from './iconView.js';
23
+ export { Dock, DOCK_ITEM, dockItem, dockSeparator, dockStack } from './Dock.js';
22
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE1E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAWhG,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEtC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/E,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEnF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAetH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAGtE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAElC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEzE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAErG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE9E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE3G,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE1E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAWhG,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEtC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/E,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEnF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAetH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAGtE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAElC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEzE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAErG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE9E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA"}
package/llms.txt CHANGED
@@ -46,7 +46,12 @@ Rules that are easy to get wrong:
46
46
 
47
47
  ```ts
48
48
  createDesk(options?: DeskOptions): Desk
49
- interface DeskOptions { cascade?: Partial<CascadeOptions>; stage?: () => Size; initial?: DeskState }
49
+ interface DeskOptions { cascade?: Partial<CascadeOptions>; stage?: () => Size; initial?: DeskState; layouts?: LayoutStore }
50
+ // layouts: remembers each window type's last mode/frame (saved by float/fill/zoom, not placeAll or open frames);
51
+ // open() without mode/frame restores it, fitted to the stage (fills if too small); more instances step +x,+y.
52
+ localLayoutStore(key): LayoutStore // localStorage 'desk.layouts.<key>'
53
+ interface LayoutStore { load(type): SavedLayout | undefined; save(type, layout: SavedLayout): void }
54
+ fitFrame(frame, stage, min?): Frame | null; stepFrom(frame, open: Frame[], stage, step, margin): Frame
50
55
 
51
56
  interface Desk {
52
57
  getState(): DeskState
@@ -177,6 +182,7 @@ useWindowId(): string | null // inside a window's content
177
182
  <Desktop
178
183
  renderWindow={(id) => ReactNode} // memoised: chrome re-renders, content does not
179
184
  title={(id) => ReactNode}
185
+ note?={(id) => ReactNode} // quiet text in the title bar: what this window is looking at
180
186
  actions?={(id) => ReactNode} // window-wide controls in the title bar
181
187
  loading?={(id) => ReactNode} // inside a window while its code loads
182
188
  failed?={(id, error, reload) => ReactNode} // inside a window that could not open
@@ -199,14 +205,17 @@ window; `lazyWindow` retries its import on reload, unlike `React.lazy`, which ca
199
205
  ### Dock
200
206
 
201
207
  ```tsx
202
- <Dock entries={DockEntry[]} label?="Dock" placement?={'overlay' | 'inline'} />
203
- dockItem({ id, label, icon, window?, description?, badge?, onSelect?, disabled? })
208
+ <Dock entries={DockEntry[]} label?="Dock" placement?={'overlay' | 'inline'}
209
+ pins?={{ accepts, onPin: (drag, before: id | null) => void, onMove?: (id, before: id | null) => void }} />
210
+ dockItem({ id, label, icon, window?, description?, badge?, onSelect?, disabled?, contextMenu?: () => MenuItem[], movable? })
204
211
  dockStack({ id, label, items: DockItem[], icon? })
205
212
  dockSeparator(id)
206
213
  ```
207
214
 
208
215
  An item opens `window ?? id` and shows a dot while that window is open, accent when it is key. A stack fans
209
216
  out above the dock. One tab stop; arrow keys along it; Escape folds a stack away.
217
+ Pins: the app stores kept items and passes them back as entries. A drop on the dock keeps at the end; on a
218
+ `movable` item, in front of it. Movable items drag along the dock (type DOCK_ITEM, payload id).
210
219
 
211
220
  ### Menu bar
212
221
 
@@ -218,6 +227,15 @@ menuCommand(label, command, { shortcut?, checked?, detail?, args? })
218
227
  menuAction(label, onSelect, { disabled?, shortcut?, checked?, detail? })
219
228
  menuSeparator(); menuHeader(label)
220
229
  windowMenuItems(state, focus, title): MenuItem[]
230
+
231
+ const { target, menu, open } = useContextMenu({ label, items: () => MenuItem[], disabled? })
232
+ // spread `target` (onContextMenu, onKeyDown) on the element; render `menu` anywhere (portal).
233
+ // Right-click opens at the pointer; Menu key / Shift+F10 at the element. Commands run from that element.
234
+
235
+ <IconView label items={{id, label, icon, subtitle?, badge?, disabled?}[]} onOpen={item => …}
236
+ openOn?="double"|"single" selected? onSelectionChange? contextMenu?={item => MenuItem[]}
237
+ drag?={{ type, payload: item => unknown }} empty? className? />
238
+ // click selects, double-click / Return opens (single: click opens); arrows, Home/End, type-ahead. Needs a DeskProvider.
221
239
  ```
222
240
 
223
241
  Command items are enabled via `canPerform` as the menu opens, and menus bind their own shortcuts. Opening a
@@ -406,5 +424,5 @@ in the accessibility tree.
406
424
  - A `DeskProvider` already carries an event bus; `BusProvider` is only for supplying or sharing one.
407
425
  - `renderWindow` must be stable (module scope or `useCallback`) for the memoisation to hold.
408
426
  - `parse`/`syncWithLocation` warn once when no `isKnown` is given: unknown ids from a URL are then trusted.
409
- - `npm run size` enforces a gzipped budget: core 10 KiB, react 46 KiB, css 10 KiB.
427
+ - `npm run size` enforces a gzipped budget: core 11 KiB, react 46 KiB, css 10 KiB.
410
428
  - The package is ESM only, with `"sideEffects": ["**/*.css"]`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liberation-data/desk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A desktop for the web, for React: windows, a dock, menus and commands, controls, tours and setup assistants.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Jasper Blues",
package/src/core/desk.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  Size,
9
9
  WindowId,
10
10
  } from './types.js'
11
+ import { fitFrame, stepFrom } from './layouts.js'
11
12
 
12
13
  export interface Desk {
13
14
  getState(): DeskState
@@ -106,6 +107,7 @@ export function createDesk(options: DeskOptions = {}): Desk {
106
107
  let stage = options.stage ?? (() => DEFAULT_STAGE)
107
108
  let state = normalise(options.initial ?? EMPTY)
108
109
  const listeners = new Set<(state: DeskState) => void>()
110
+ const layouts = options.layouts
109
111
 
110
112
  const commit = (next: DeskState) => {
111
113
  if (next === state) return
@@ -142,6 +144,7 @@ export function createDesk(options: DeskOptions = {}): Desk {
142
144
  if (window.mode === 'floating' && !frame) return
143
145
  const next = frame ?? fits(remembered.get(id)) ?? nextFrame(state)
144
146
  if (frame) remembered.set(id, frame)
147
+ layouts?.save(windowType(id), { mode: 'floating', frame: next })
145
148
  commit(toFront(replace(state, { id, mode: 'floating', frame: next }), id))
146
149
  }
147
150
 
@@ -149,6 +152,7 @@ export function createDesk(options: DeskOptions = {}): Desk {
149
152
  const window = find(id)
150
153
  if (!window || window.mode === 'filled') return
151
154
  remembered.set(id, window.frame)
155
+ layouts?.save(windowType(id), { mode: 'filled' })
152
156
  commit(toFront(replace(state, { id, mode: 'filled' }), id))
153
157
  }
154
158
 
@@ -167,8 +171,19 @@ export function createDesk(options: DeskOptions = {}): Desk {
167
171
  commit(toFront(state, id))
168
172
  return
169
173
  }
170
- const mode = opts.mode ?? (opts.frame ? 'floating' : 'filled')
171
- const window: DeskWindow = mode === 'filled' ? { id, mode } : { id, mode, frame: opts.frame ?? nextFrame(state) }
174
+ const saved = opts.mode || opts.frame ? undefined : layouts?.load(windowType(id))
175
+ const kept = saved?.mode === 'floating' ? fitFrame(saved.frame, stage()) : null
176
+ const mode = opts.mode ?? (opts.frame || kept ? 'floating' : 'filled')
177
+ const frame = () => {
178
+ if (opts.frame) return opts.frame
179
+ if (!kept) return nextFrame(state)
180
+ const siblings = state.stack.flatMap(other => {
181
+ const w = find(other)
182
+ return w?.mode === 'floating' && windowType(w.id) === windowType(id) ? [w.frame] : []
183
+ })
184
+ return stepFrom(kept, siblings, stage(), cascade.step, cascade.margin)
185
+ }
186
+ const window: DeskWindow = mode === 'filled' ? { id, mode } : { id, mode, frame: frame() }
172
187
  commit({ windows: [...state.windows, window], stack: [...state.stack, id] })
173
188
  },
174
189
 
package/src/core/index.ts CHANGED
@@ -29,6 +29,8 @@ export { bindShortcuts, formatShortcut, isApplePlatform, isInstalledApp, matches
29
29
  export type { Keymap, Shortcut, ShortcutOptions } from './shortcuts.js'
30
30
  export { parse, serialize, syncWithLocation } from './location.js'
31
31
  export { isTitleCase, titleCase } from './titles.js'
32
+ export { fitFrame, localLayoutStore, stepFrom } from './layouts.js'
33
+ export type { LayoutStore, SavedLayout } from './layouts.js'
32
34
  export type { LocationEnv, LocationOptions } from './location.js'
33
35
  export type {
34
36
  CascadeOptions,
@@ -0,0 +1,88 @@
1
+ import type { Frame, Size } from './types.js'
2
+
3
+ /*
4
+ * Windows that come back the way they were left. Close a window you had made small and put in a
5
+ * corner, open it tomorrow, and it is small and in the corner again; one you zoomed to fill the desk
6
+ * fills it again.
7
+ *
8
+ * What is remembered is per kind of window, not per id: `query#2` opens where `query` was last left.
9
+ * A remembered frame is a promise about a screen that may have changed, so it is fitted to the
10
+ * current one: moved in until it is on the desk, made smaller if the desk is now smaller, and given
11
+ * up for filling if it would be too small to use. A second window of a kind already open steps down
12
+ * and to the right of it, as a cascade does, rather than opening exactly on top and hiding it.
13
+ */
14
+
15
+ export type SavedLayout = { readonly mode: 'filled' } | { readonly mode: 'floating'; readonly frame: Frame }
16
+
17
+ /** Where layouts are kept. Synchronous: a window opens now, not after a round trip. */
18
+ export interface LayoutStore {
19
+ load(type: string): SavedLayout | undefined
20
+ save(type: string, layout: SavedLayout): void
21
+ }
22
+
23
+ const MIN: Size = { width: 240, height: 160 }
24
+
25
+ /** Keeps layouts in this browser, under `desk.layouts.<key>`. */
26
+ export function localLayoutStore(key: string): LayoutStore {
27
+ const name = `desk.layouts.${key}`
28
+ const read = (): Record<string, SavedLayout> => {
29
+ try {
30
+ const raw = globalThis.localStorage?.getItem(name)
31
+ const parsed: unknown = raw ? JSON.parse(raw) : {}
32
+ return parsed && typeof parsed === 'object' ? (parsed as Record<string, SavedLayout>) : {}
33
+ } catch {
34
+ return {}
35
+ }
36
+ }
37
+ return {
38
+ load: type => valid(read()[type]),
39
+ save(type, layout) {
40
+ try {
41
+ globalThis.localStorage?.setItem(name, JSON.stringify({ ...read(), [type]: layout }))
42
+ } catch {
43
+ // Private windows and full storage: windows still open, just not where they were.
44
+ }
45
+ },
46
+ }
47
+ }
48
+
49
+ const finite = (...values: unknown[]) => values.every(v => typeof v === 'number' && Number.isFinite(v))
50
+
51
+ /** Storage is outside the program: anything that is not a layout is treated as none. */
52
+ function valid(layout: unknown): SavedLayout | undefined {
53
+ const l = layout as SavedLayout | undefined
54
+ if (l?.mode === 'filled') return l
55
+ if (l?.mode === 'floating' && l.frame && finite(l.frame.x, l.frame.y, l.frame.width, l.frame.height)) return l
56
+ return undefined
57
+ }
58
+
59
+ /** A remembered frame on today's desk: on screen, no bigger than the desk, or null if too small to use. */
60
+ export function fitFrame(frame: Frame, stage: Size, min: Size = MIN): Frame | null {
61
+ const width = Math.min(frame.width, stage.width)
62
+ const height = Math.min(frame.height, stage.height)
63
+ if (width < min.width || height < min.height) return null
64
+ return {
65
+ x: Math.max(0, Math.min(frame.x, stage.width - width)),
66
+ y: Math.max(0, Math.min(frame.y, stage.height - height)),
67
+ width,
68
+ height,
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Where another window of a kind goes when some are already free on the desk: a step down and right
74
+ * of the one in front, back to the top-left when that would run off the desk, and never exactly on
75
+ * top of one already there.
76
+ */
77
+ export function stepFrom(frame: Frame, open: readonly Frame[], stage: Size, step: { x: number; y: number }, margin: number): Frame {
78
+ const front = open.at(-1)
79
+ if (!front) return frame
80
+ const taken = (f: Frame) => open.some(o => o.x === f.x && o.y === f.y)
81
+ let next = { ...frame, x: front.x + step.x, y: front.y + step.y }
82
+ for (let tries = 0; tries <= open.length; tries++) {
83
+ if (next.x + next.width > stage.width || next.y + next.height > stage.height) next = { ...next, x: margin, y: margin }
84
+ if (!taken(next)) return next
85
+ next = { ...next, x: next.x + step.x, y: next.y + step.y }
86
+ }
87
+ return next
88
+ }
package/src/core/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { LayoutStore } from './layouts.js'
2
+
1
3
  export type WindowId = string
2
4
 
3
5
  export interface Frame {
@@ -40,6 +42,8 @@ export interface DeskOptions {
40
42
  /** Reports the stage's size, so floating windows open inside it. */
41
43
  readonly stage?: () => Size
42
44
  readonly initial?: DeskState
45
+ /** Remembers how each kind of window was left, and opens it that way again. */
46
+ readonly layouts?: LayoutStore
43
47
  }
44
48
 
45
49
  export interface OpenOptions {