quilt-vanilla 0.3.0 → 0.4.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 (45) hide show
  1. package/dist/actions.d.ts +14 -0
  2. package/dist/actions.d.ts.map +1 -0
  3. package/dist/actions.js +115 -0
  4. package/dist/actions.js.map +1 -0
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/menu.d.ts +9 -3
  10. package/dist/menu.d.ts.map +1 -1
  11. package/dist/menu.js +110 -37
  12. package/dist/menu.js.map +1 -1
  13. package/dist/renderer.d.ts.map +1 -1
  14. package/dist/renderer.js +35 -8
  15. package/dist/renderer.js.map +1 -1
  16. package/dist/shortcut-registration.d.ts +9 -0
  17. package/dist/shortcut-registration.d.ts.map +1 -0
  18. package/dist/shortcut-registration.js +95 -0
  19. package/dist/shortcut-registration.js.map +1 -0
  20. package/dist/shortcuts.d.ts +7 -6
  21. package/dist/shortcuts.d.ts.map +1 -1
  22. package/dist/shortcuts.js +28 -50
  23. package/dist/shortcuts.js.map +1 -1
  24. package/dist/styles.css +25 -9
  25. package/dist/tabs.d.ts.map +1 -1
  26. package/dist/tabs.js +10 -12
  27. package/dist/tabs.js.map +1 -1
  28. package/dist/types.d.ts +21 -5
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/types.js.map +1 -1
  31. package/dist/workspace-api.d.ts +8 -2
  32. package/dist/workspace-api.d.ts.map +1 -1
  33. package/dist/workspace-api.js +14 -0
  34. package/dist/workspace-api.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/actions.ts +125 -0
  37. package/src/index.ts +6 -0
  38. package/src/menu.ts +123 -43
  39. package/src/renderer.ts +41 -7
  40. package/src/shortcut-registration.ts +106 -0
  41. package/src/shortcuts.ts +38 -63
  42. package/src/styles.css +25 -9
  43. package/src/tabs.ts +9 -11
  44. package/src/types.ts +43 -5
  45. package/src/workspace-api.ts +23 -1
@@ -0,0 +1,14 @@
1
+ import type { Group, Pane } from 'quilt-core';
2
+ import type { ActionContext, ResolvedLayoutOptions, WorkspaceAction } from './types.js';
3
+ export declare function createActions(options: ResolvedLayoutOptions, dependencies: {
4
+ context(): ActionContext;
5
+ disposed(): boolean;
6
+ create(action: WorkspaceAction, group: Group, pane: Pane | undefined): void;
7
+ close(id: string, kind?: 'pane' | 'group'): Promise<boolean>;
8
+ popout(id: string): Promise<boolean>;
9
+ report(error: unknown): void;
10
+ }): {
11
+ canExecuteAction: (action: WorkspaceAction, context?: ActionContext) => boolean;
12
+ executeAction: (action: WorkspaceAction, context?: ActionContext) => Promise<boolean>;
13
+ };
14
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGxF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,qBAAqB,EAC9B,YAAY,EAAE;IACZ,OAAO,IAAI,aAAa,CAAC;IACzB,QAAQ,IAAI,OAAO,CAAC;IACpB,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;IAC5E,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;+BAsBiC,eAAe,YAAY,aAAa,KAAG,OAAO;4BAgC/C,eAAe,YAAY,aAAa,KAAG,OAAO,CAAC,OAAO,CAAC;EAwDjG"}
@@ -0,0 +1,115 @@
1
+ import { findNode, findParent, groups, paneIds } from 'quilt-core';
2
+ import { actionOrder } from './shortcut-registration.js';
3
+ export function createActions(options, dependencies) {
4
+ function resolve(context) {
5
+ const layout = options.store.getSnapshot();
6
+ const target = context?.groupId !== undefined || context?.paneId !== undefined
7
+ ? context
8
+ : dependencies.context();
9
+ const id = target.groupId ??
10
+ (target.paneId === undefined
11
+ ? undefined
12
+ : groups(layout.root).find((group) => group.panes.includes(target.paneId))?.id);
13
+ const node = id === undefined ? undefined : findNode(layout.root, id);
14
+ const group = node?.kind === 'group' ? node : undefined;
15
+ const paneId = target.paneId ?? group?.active;
16
+ const pane = paneId ? layout.panes[paneId] : undefined;
17
+ const valid = !(target.groupId !== undefined && !group) &&
18
+ !(target.paneId !== undefined && (!pane || !group?.panes.includes(target.paneId)));
19
+ return { layout, group, pane, valid };
20
+ }
21
+ function canExecuteAction(action, context) {
22
+ if (dependencies.disposed() || !actionOrder.includes(action))
23
+ return false;
24
+ const { layout, group, pane, valid } = resolve(context);
25
+ if (!valid)
26
+ return false;
27
+ if (action === 'restoreClosedTab')
28
+ return options.store.canRestoreClosedTab();
29
+ if (!group)
30
+ return false;
31
+ const allowed = (cap) => group.panes.every((id) => options.store.can(id, cap));
32
+ switch (action) {
33
+ case 'addTab':
34
+ return !!options.tabs?.list().length && allowed('move');
35
+ case 'splitLeft':
36
+ case 'splitRight':
37
+ case 'splitUp':
38
+ case 'splitDown':
39
+ return (!group.panes.length || !!options.tabs?.list().length) && allowed('split');
40
+ case 'closeActiveTab':
41
+ return !!pane && options.store.can(pane.id, 'close');
42
+ case 'closePane':
43
+ return !!group.panes.length && allowed('close');
44
+ case 'closeEmptyPane':
45
+ return !group.panes.length && group.id !== layout.root.id;
46
+ case 'popout':
47
+ return options.popouts !== false && !!pane && options.store.can(pane.id, 'popout');
48
+ case 'joinSiblingRegion': {
49
+ const parent = findParent(layout.root, group.id);
50
+ return !!parent && paneIds(parent).every((id) => options.store.can(id, 'join'));
51
+ }
52
+ default:
53
+ return true;
54
+ }
55
+ }
56
+ async function executeAction(action, context) {
57
+ try {
58
+ if (!canExecuteAction(action, context))
59
+ return false;
60
+ const { layout, group, pane } = resolve(context);
61
+ switch (action) {
62
+ case 'restoreClosedTab':
63
+ options.store.restoreClosedTab();
64
+ break;
65
+ case 'maximize':
66
+ options.store.maximize(layout.maximized === group.id ? null : group.id);
67
+ break;
68
+ case 'addTab':
69
+ case 'splitLeft':
70
+ case 'splitRight':
71
+ case 'splitUp':
72
+ case 'splitDown':
73
+ dependencies.create(action, group, pane);
74
+ break;
75
+ case 'closeActiveTab':
76
+ return await dependencies.close(pane.id);
77
+ case 'closePane':
78
+ return await dependencies.close(group.id, 'group');
79
+ case 'closeEmptyPane':
80
+ options.store.removeEmptyGroup(group.id);
81
+ break;
82
+ case 'popout':
83
+ return await dependencies.popout(pane.id);
84
+ case 'joinSiblingRegion':
85
+ options.store.join(group.id, { source: 'user' });
86
+ break;
87
+ case 'tabOrientationDefault':
88
+ options.store.setTabPlacement(group.id, undefined);
89
+ break;
90
+ case 'tabOrientationHorizontal':
91
+ options.store.setTabPlacement(group.id, 'top');
92
+ break;
93
+ case 'tabOrientationVertical':
94
+ options.store.setTabPlacement(group.id, 'left');
95
+ break;
96
+ case 'tabDisplayDefault':
97
+ options.store.setTabDisplay(group.id, undefined);
98
+ break;
99
+ case 'tabDisplayAutomatic':
100
+ options.store.setTabDisplay(group.id, 'automatic');
101
+ break;
102
+ case 'tabDisplayCompact':
103
+ options.store.setTabDisplay(group.id, 'compact');
104
+ break;
105
+ }
106
+ return true;
107
+ }
108
+ catch (error) {
109
+ dependencies.report(error);
110
+ return false;
111
+ }
112
+ }
113
+ return { canExecuteAction, executeAction };
114
+ }
115
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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","sourcesContent":["import { findNode, findParent, groups, paneIds } from 'quilt-core';\nimport type { Group, Pane } from 'quilt-core';\nimport type { ActionContext, ResolvedLayoutOptions, WorkspaceAction } from './types.js';\nimport { actionOrder } from './shortcut-registration.js';\n\nexport function createActions(\n options: ResolvedLayoutOptions,\n dependencies: {\n context(): ActionContext;\n disposed(): boolean;\n create(action: WorkspaceAction, group: Group, pane: Pane | undefined): void;\n close(id: string, kind?: 'pane' | 'group'): Promise<boolean>;\n popout(id: string): Promise<boolean>;\n report(error: unknown): void;\n },\n) {\n function resolve(context?: ActionContext) {\n const layout = options.store.getSnapshot();\n const target =\n context?.groupId !== undefined || context?.paneId !== undefined\n ? context\n : dependencies.context();\n const id =\n target.groupId ??\n (target.paneId === undefined\n ? undefined\n : groups(layout.root).find((group) => group.panes.includes(target.paneId!))?.id);\n const node = id === undefined ? undefined : findNode(layout.root, id);\n const group = node?.kind === 'group' ? node : undefined;\n const paneId = target.paneId ?? group?.active;\n const pane = paneId ? layout.panes[paneId] : undefined;\n const valid =\n !(target.groupId !== undefined && !group) &&\n !(target.paneId !== undefined && (!pane || !group?.panes.includes(target.paneId)));\n return { layout, group, pane, valid };\n }\n function canExecuteAction(action: WorkspaceAction, context?: ActionContext): boolean {\n if (dependencies.disposed() || !actionOrder.includes(action)) return false;\n const { layout, group, pane, valid } = resolve(context);\n if (!valid) return false;\n if (action === 'restoreClosedTab') return options.store.canRestoreClosedTab();\n if (!group) return false;\n const allowed = (cap: 'split' | 'move' | 'close') =>\n group.panes.every((id) => options.store.can(id, cap));\n switch (action) {\n case 'addTab':\n return !!options.tabs?.list().length && allowed('move');\n case 'splitLeft':\n case 'splitRight':\n case 'splitUp':\n case 'splitDown':\n return (!group.panes.length || !!options.tabs?.list().length) && allowed('split');\n case 'closeActiveTab':\n return !!pane && options.store.can(pane.id, 'close');\n case 'closePane':\n return !!group.panes.length && allowed('close');\n case 'closeEmptyPane':\n return !group.panes.length && group.id !== layout.root.id;\n case 'popout':\n return options.popouts !== false && !!pane && options.store.can(pane.id, 'popout');\n case 'joinSiblingRegion': {\n const parent = findParent(layout.root, group.id);\n return !!parent && paneIds(parent).every((id) => options.store.can(id, 'join'));\n }\n default:\n return true;\n }\n }\n async function executeAction(action: WorkspaceAction, context?: ActionContext): Promise<boolean> {\n try {\n if (!canExecuteAction(action, context)) return false;\n const { layout, group, pane } = resolve(context);\n switch (action) {\n case 'restoreClosedTab':\n options.store.restoreClosedTab();\n break;\n case 'maximize':\n options.store.maximize(layout.maximized === group!.id ? null : group!.id);\n break;\n case 'addTab':\n case 'splitLeft':\n case 'splitRight':\n case 'splitUp':\n case 'splitDown':\n dependencies.create(action, group!, pane);\n break;\n case 'closeActiveTab':\n return await dependencies.close(pane!.id);\n case 'closePane':\n return await dependencies.close(group!.id, 'group');\n case 'closeEmptyPane':\n options.store.removeEmptyGroup(group!.id);\n break;\n case 'popout':\n return await dependencies.popout(pane!.id);\n case 'joinSiblingRegion':\n options.store.join(group!.id, { source: 'user' });\n break;\n case 'tabOrientationDefault':\n options.store.setTabPlacement(group!.id, undefined);\n break;\n case 'tabOrientationHorizontal':\n options.store.setTabPlacement(group!.id, 'top');\n break;\n case 'tabOrientationVertical':\n options.store.setTabPlacement(group!.id, 'left');\n break;\n case 'tabDisplayDefault':\n options.store.setTabDisplay(group!.id, undefined);\n break;\n case 'tabDisplayAutomatic':\n options.store.setTabDisplay(group!.id, 'automatic');\n break;\n case 'tabDisplayCompact':\n options.store.setTabDisplay(group!.id, 'compact');\n break;\n }\n return true;\n } catch (error) {\n dependencies.report(error);\n return false;\n }\n }\n return { canExecuteAction, executeAction };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Workspace } from './workspace-api.js';
2
2
  export type { WorkspaceHandle, WorkspaceOptions, WorkspaceOptionUpdates, WorkspaceEvents, WorkspaceChange, PaneHandle, PaneType, PaneTypes, AddPaneOptions, CloseOptions, } from './workspace-api.js';
3
- export type { PaneContext, PaneView, PaneRenderer, TabBarStyle, TabBarOptions, InitialConfiguration, WorkspaceSettings, KeyBinding, CloseRequest, } from './types.js';
3
+ export type { PaneContext, PaneView, PaneRenderer, TabBarStyle, TabBarOptions, InitialConfiguration, WorkspaceSettings, KeyBinding, WorkspaceAction, ActionContext, RegisteredShortcut, ShortcutRegistration, CloseRequest, } from './types.js';
4
4
  export { TabRegistry } from './registry.js';
5
5
  export type { TabRegistration } from './registry.js';
6
6
  export { themes, themeFamilies } from './theme.js';
@@ -14,4 +14,5 @@ export type { WorkspacePreset } from './workspace.js';
14
14
  export { themeProperties } from './theme.js';
15
15
  export { defaultMessages } from './messages.js';
16
16
  export type { Messages } from './messages.js';
17
+ export { formatShortcut } from './shortcut-registration.js';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,UAAU,EACV,QAAQ,EACR,SAAS,EACT,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,KAAK,EACL,IAAI,EACJ,eAAe,EACf,MAAM,EACN,MAAM,IAAI,cAAc,EACxB,KAAK,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,UAAU,EACV,QAAQ,EACR,SAAS,EACT,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,KAAK,EACL,IAAI,EACJ,eAAe,EACf,MAAM,EACN,MAAM,IAAI,cAAc,EACxB,KAAK,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -6,4 +6,5 @@ export { PaneRegistry } from './registry.js';
6
6
  export { parseWorkspace, dockLayout } from './workspace.js';
7
7
  export { themeProperties } from './theme.js';
8
8
  export { defaultMessages } from './messages.js';
9
+ export { formatShortcut } from './shortcut-registration.js';
9
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAyB/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmB9E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export { Workspace } from './workspace-api.js';\nexport type {\n WorkspaceHandle,\n WorkspaceOptions,\n WorkspaceOptionUpdates,\n WorkspaceEvents,\n WorkspaceChange,\n PaneHandle,\n PaneType,\n PaneTypes,\n AddPaneOptions,\n CloseOptions,\n} from './workspace-api.js';\nexport type {\n PaneContext,\n PaneView,\n PaneRenderer,\n TabBarStyle,\n TabBarOptions,\n InitialConfiguration,\n WorkspaceSettings,\n KeyBinding,\n CloseRequest,\n} from './types.js';\n\nexport { TabRegistry } from './registry.js';\nexport type { TabRegistration } from './registry.js';\nexport { themes, themeFamilies } from './theme.js';\nexport type { LayoutTheme } from './theme.js';\nexport { LayoutError, createLayout, parseLayout, validate } from 'quilt-core';\nexport type {\n AutoCollapse,\n Json,\n Capability,\n Axis,\n Pane,\n Group,\n Split,\n Node,\n WindowPlacement,\n Popout,\n Layout as LayoutSnapshot,\n Issue,\n CommandOptions,\n Change,\n Bounds,\n JoinOptions,\n} from 'quilt-core';\nexport { PaneRegistry } from './registry.js';\nexport type { PaneRegistration } from './registry.js';\nexport { parseWorkspace, dockLayout } from './workspace.js';\nexport type { WorkspacePreset } from './workspace.js';\nexport { themeProperties } from './theme.js';\nexport { defaultMessages } from './messages.js';\nexport type { Messages } from './messages.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA6B/C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmB9E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["export { Workspace } from './workspace-api.js';\nexport type {\n WorkspaceHandle,\n WorkspaceOptions,\n WorkspaceOptionUpdates,\n WorkspaceEvents,\n WorkspaceChange,\n PaneHandle,\n PaneType,\n PaneTypes,\n AddPaneOptions,\n CloseOptions,\n} from './workspace-api.js';\nexport type {\n PaneContext,\n PaneView,\n PaneRenderer,\n TabBarStyle,\n TabBarOptions,\n InitialConfiguration,\n WorkspaceSettings,\n KeyBinding,\n WorkspaceAction,\n ActionContext,\n RegisteredShortcut,\n ShortcutRegistration,\n CloseRequest,\n} from './types.js';\n\nexport { TabRegistry } from './registry.js';\nexport type { TabRegistration } from './registry.js';\nexport { themes, themeFamilies } from './theme.js';\nexport type { LayoutTheme } from './theme.js';\nexport { LayoutError, createLayout, parseLayout, validate } from 'quilt-core';\nexport type {\n AutoCollapse,\n Json,\n Capability,\n Axis,\n Pane,\n Group,\n Split,\n Node,\n WindowPlacement,\n Popout,\n Layout as LayoutSnapshot,\n Issue,\n CommandOptions,\n Change,\n Bounds,\n JoinOptions,\n} from 'quilt-core';\nexport { PaneRegistry } from './registry.js';\nexport type { PaneRegistration } from './registry.js';\nexport { parseWorkspace, dockLayout } from './workspace.js';\nexport type { WorkspacePreset } from './workspace.js';\nexport { themeProperties } from './theme.js';\nexport { defaultMessages } from './messages.js';\nexport type { Messages } from './messages.js';\n\nexport { formatShortcut } from './shortcut-registration.js';\n"]}
package/dist/menu.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import type { Group, Pane } from 'quilt-core';
2
- import type { ResolvedLayoutOptions } from './types.js';
3
- import type { Windows } from './windows.js';
4
- export declare function createPaneMenu(root: HTMLElement, options: ResolvedLayoutOptions, windows: Pick<Windows, 'open'>, refresh: () => void, report: (e: unknown) => void, requestClose: (id: string, kind?: 'pane' | 'group') => Promise<boolean>): {
2
+ import type { ResolvedLayoutOptions, ActionContext } from './types.js';
3
+ import type { createActions } from './actions.js';
4
+ export declare function createPaneMenu(root: HTMLElement, options: ResolvedLayoutOptions, refresh: () => void, report: (e: unknown) => void, actions: ReturnType<typeof createActions>): {
5
+ getShortcutTarget: () => {
6
+ dialog: HTMLDialogElement;
7
+ context: ActionContext;
8
+ close(): void;
9
+ } | undefined;
10
+ refreshShortcuts: () => void;
5
11
  open: (anchor: HTMLElement, pane: Pane | undefined, group: Group, direction?: 'left' | 'right' | 'top' | 'bottom', ratio?: number, groupActions?: boolean, addTab?: boolean) => void;
6
12
  addTab(anchor: HTMLElement, group: Group): void;
7
13
  openEmpty(anchor: HTMLElement, group: Group): void;
@@ -1 +1 @@
1
- {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAC9B,OAAO,EAAE,MAAM,IAAI,EACnB,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EAC5B,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC;mBAsB7D,WAAW,QACb,IAAI,GAAG,SAAS,SACf,KAAK,cACA,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ;mBAybhC,WAAW,SAAS,KAAK;sBAYtB,WAAW,SAAS,KAAK;;EAW9C"}
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,qBAAqB,EAAmB,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,MAAM,IAAI,EACnB,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EAC5B,OAAO,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC;;gBAM7B,iBAAiB;iBAAW,aAAa;iBAAW,IAAI;;;mBAkB1D,WAAW,QACb,IAAI,GAAG,SAAS,SACf,KAAK,cACA,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ;mBAugBhC,WAAW,SAAS,KAAK;sBAYtB,WAAW,SAAS,KAAK;;EAW9C"}
package/dist/menu.js CHANGED
@@ -2,10 +2,13 @@ import { message } from './messages.js';
2
2
  import { actionIcon } from './action-icons.js';
3
3
  import { fillTabPicker } from './picker.js';
4
4
  import { findNode, findParent, groups, paneIds } from 'quilt-core';
5
+ import { registeredShortcuts, formatShortcut, ariaShortcut } from './shortcut-registration.js';
5
6
  import { el, Scope } from './lifetime.js';
6
- export function createPaneMenu(root, options, windows, refresh, report, requestClose) {
7
+ export function createPaneMenu(root, options, refresh, report, actions) {
7
8
  const doc = root.ownerDocument, win = doc.defaultView;
8
9
  let current;
10
+ let shortcutTarget;
11
+ let refreshShortcuts = () => { };
9
12
  const persistentPickers = new Map();
10
13
  const act = (fn) => {
11
14
  try {
@@ -15,11 +18,10 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
15
18
  report(error);
16
19
  }
17
20
  };
18
- function button(text, title, action) {
21
+ function button(text, label, action) {
19
22
  const b = el(doc, 'button', 'layouts-button', text);
20
23
  b.type = 'button';
21
- b.title = title;
22
- b.setAttribute('aria-label', title);
24
+ b.setAttribute('aria-label', label);
23
25
  b.onclick = () => act(action);
24
26
  return b;
25
27
  }
@@ -38,17 +40,35 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
38
40
  ? message(options, '{title} actions', { title: pane.title })
39
41
  : message(options, 'Empty pane actions'));
40
42
  local.add(() => dialog.remove());
41
- const add = (icon, label, enabled, fn) => {
43
+ const context = {
44
+ groupId: group.id,
45
+ ...(pane && group.panes.includes(pane.id) ? { paneId: pane.id } : {}),
46
+ };
47
+ const hints = [];
48
+ const bindAction = (element, action) => {
49
+ hints.push({ element, action });
50
+ element.disabled = !actions.canExecuteAction(action, context);
51
+ element.onclick = () => {
52
+ local.dispose();
53
+ void actions.executeAction(action, context);
54
+ };
55
+ };
56
+ const add = (icon, label, enabled, fn, target = dialog) => {
42
57
  const b = button(label, label, () => {
43
58
  local.dispose();
44
- act(fn);
59
+ if (typeof fn === 'function')
60
+ act(fn);
45
61
  });
46
62
  const glyph = el(doc, 'span', 'layouts-tab-icon');
47
63
  glyph.setAttribute('aria-hidden', 'true');
48
64
  act(() => glyph.append(actionIcon(doc, icon, options)));
49
65
  b.replaceChildren(glyph, el(doc, 'span', '', label.replace(/^\+ /, '')));
50
66
  b.disabled = !enabled;
51
- dialog.append(b);
67
+ if (typeof fn === 'string')
68
+ bindAction(b, fn);
69
+ if (target !== dialog)
70
+ b.setAttribute('role', 'menuitem');
71
+ target.append(b);
52
72
  return b;
53
73
  };
54
74
  const allowed = (cap) => group.panes.every((id) => options.store.can(id, cap));
@@ -151,7 +171,7 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
151
171
  create(direction === 'left' || direction === 'right' ? 'horizontal' : 'vertical', direction === 'left' || direction === 'top');
152
172
  return;
153
173
  }
154
- add('add-tab', message(options, '+ Add tab'), available && allowed('move'), () => create());
174
+ add('add-tab', message(options, '+ Add tab'), available && allowed('move'), 'addTab');
155
175
  const canCreate = !group.panes.length || available;
156
176
  const flyouts = [];
157
177
  let positionMenus = () => { };
@@ -170,7 +190,14 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
170
190
  flyout.setAttribute('role', 'menu');
171
191
  flyout.setAttribute('aria-label', label);
172
192
  flyouts.push({ trigger, flyout });
193
+ let closeTimer;
194
+ const cancelClose = () => {
195
+ win.clearTimeout(closeTimer);
196
+ closeTimer = undefined;
197
+ };
198
+ local.add(cancelClose);
173
199
  const setOpen = (open) => {
200
+ cancelClose();
174
201
  if (open) {
175
202
  for (const other of flyouts) {
176
203
  other.flyout.hidden = true;
@@ -186,7 +213,11 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
186
213
  if (!trigger.disabled)
187
214
  setOpen(true);
188
215
  });
189
- local.listen(container, 'pointerleave', () => setOpen(false));
216
+ // Allow the pointer to cross the dialog padding or move diagonally into the flyout.
217
+ local.listen(container, 'pointerleave', () => {
218
+ cancelClose();
219
+ closeTimer = win.setTimeout(() => setOpen(false), 300);
220
+ });
190
221
  local.listen(trigger, 'keydown', (event) => {
191
222
  if (event.key === 'ArrowRight') {
192
223
  event.preventDefault();
@@ -206,16 +237,14 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
206
237
  return flyout;
207
238
  }
208
239
  const flyout = submenu(message(options, 'Split'), 'split-right', canCreate && allowed('split'));
209
- for (const [label, axis, before] of [
210
- [message(options, 'Split left'), 'horizontal', true],
211
- [message(options, 'Split right'), 'horizontal', false],
212
- [message(options, 'Split up'), 'vertical', true],
213
- [message(options, 'Split down'), 'vertical', false],
240
+ for (const [label, axis, action] of [
241
+ [message(options, 'Split left'), 'horizontal', 'splitLeft'],
242
+ [message(options, 'Split right'), 'horizontal', 'splitRight'],
243
+ [message(options, 'Split up'), 'vertical', 'splitUp'],
244
+ [message(options, 'Split down'), 'vertical', 'splitDown'],
214
245
  ]) {
215
- const option = button(label, label, () => {
216
- local.dispose();
217
- create(axis, before);
218
- });
246
+ const option = button(label, label, () => { });
247
+ bindAction(option, action);
219
248
  const icon = el(doc, 'span', 'layouts-tab-icon');
220
249
  icon.setAttribute('aria-hidden', 'true');
221
250
  icon.append(actionIcon(doc, axis === 'horizontal' ? 'split-right' : 'split-below', options));
@@ -224,7 +253,7 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
224
253
  flyout.append(option);
225
254
  }
226
255
  const joinParent = findParent(options.store.getSnapshot().root, group.id);
227
- const join = add('join', message(options, 'Join sibling region'), Boolean(joinParent) && paneIds(joinParent).every((id) => options.store.can(id, 'join')), () => options.store.join(group.id, { source: 'user' }));
256
+ const join = add('join', message(options, 'Join sibling region'), Boolean(joinParent) && paneIds(joinParent).every((id) => options.store.can(id, 'join')), 'joinSiblingRegion');
228
257
  let preview;
229
258
  const clearPreview = () => {
230
259
  preview?.dispose();
@@ -284,12 +313,9 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
284
313
  local.listen(join, 'blur', clearPreview);
285
314
  add(options.store.getSnapshot().maximized === group.id ? 'restore' : 'maximize', options.store.getSnapshot().maximized === group.id
286
315
  ? message(options, 'Restore region')
287
- : message(options, 'Maximize region'), true, () => options.store.maximize(options.store.getSnapshot().maximized === group.id ? null : group.id));
316
+ : message(options, 'Maximize region'), true, 'maximize');
288
317
  if (group.panes.length && pane && options.popouts !== false) {
289
- add('popout', message(options, 'Open in window'), options.store.can(pane.id, 'popout'), () => {
290
- void windows.open(pane.id);
291
- refresh();
292
- });
318
+ add('popout', message(options, 'Open in window'), options.store.can(pane.id, 'popout'), 'popout');
293
319
  }
294
320
  const orientation = submenu(message(options, 'Tab orientation'), 'tab-orientation');
295
321
  for (const [value, label] of [
@@ -298,10 +324,12 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
298
324
  ['left', message(options, 'Vertical')],
299
325
  ]) {
300
326
  const selected = group.tabPlacement === value;
301
- const option = button(label, label, () => {
302
- options.store.setTabPlacement(group.id, value);
303
- local.dispose();
304
- });
327
+ const option = button(label, label, () => { });
328
+ bindAction(option, value === 'top'
329
+ ? 'tabOrientationHorizontal'
330
+ : value === 'left'
331
+ ? 'tabOrientationVertical'
332
+ : 'tabOrientationDefault');
305
333
  option.setAttribute('role', 'menuitemradio');
306
334
  option.setAttribute('aria-checked', String(selected));
307
335
  const mark = el(doc, 'span', 'layouts-tab-icon', selected ? '✓' : '');
@@ -316,10 +344,12 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
316
344
  ['compact', message(options, 'Compact')],
317
345
  ]) {
318
346
  const selected = group.tabDisplay === value;
319
- const option = button(label, label, () => {
320
- options.store.setTabDisplay(group.id, value);
321
- local.dispose();
322
- });
347
+ const option = button(label, label, () => { });
348
+ bindAction(option, value === 'automatic'
349
+ ? 'tabDisplayAutomatic'
350
+ : value === 'compact'
351
+ ? 'tabDisplayCompact'
352
+ : 'tabDisplayDefault');
323
353
  option.setAttribute('role', 'menuitemradio');
324
354
  option.setAttribute('aria-checked', String(selected));
325
355
  const mark = el(doc, 'span', 'layouts-tab-icon', selected ? '✓' : '');
@@ -328,13 +358,14 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
328
358
  display.append(option);
329
359
  }
330
360
  if (group.panes.length && pane) {
331
- add('close', message(options, 'Close active tab'), options.store.can(pane.id, 'close'), () => void requestClose(pane.id));
332
- add('close', message(options, 'Close pane'), group.panes.every((id) => options.store.can(id, 'close')), () => void requestClose(group.id, 'group'));
361
+ const close = submenu(message(options, 'Close'), 'close');
362
+ add('close', message(options, 'Close active tab'), options.store.can(pane.id, 'close'), 'closeActiveTab', close);
363
+ add('close', message(options, 'Close pane'), group.panes.every((id) => options.store.can(id, 'close')), 'closePane', close);
333
364
  }
334
365
  else {
335
- add('close', message(options, 'Close empty pane'), options.store.getSnapshot().root.id !== group.id, () => options.store.removeEmptyGroup(group.id));
366
+ add('close', message(options, 'Close empty pane'), options.store.getSnapshot().root.id !== group.id, 'closeEmptyPane');
336
367
  }
337
- add('restore', message(options, 'Restore closed tab'), options.store.canRestoreClosedTab(), () => options.store.restoreClosedTab());
368
+ add('restore', message(options, 'Restore closed tab'), options.store.canRestoreClosedTab(), 'restoreClosedTab');
338
369
  add('cancel', message(options, 'Cancel'), true, () => { });
339
370
  root.append(dialog);
340
371
  // Measure after opening: theme density, labels, and available actions change the size.
@@ -380,8 +411,48 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
380
411
  local.dispose();
381
412
  }
382
413
  });
414
+ const updateHints = () => {
415
+ const registrations = registeredShortcuts(options);
416
+ for (const { element, action } of hints) {
417
+ element.querySelector('.layouts-shortcut')?.remove();
418
+ element.removeAttribute('aria-keyshortcuts');
419
+ element.disabled = !actions.canExecuteAction(action, context);
420
+ const label = element.getAttribute('aria-label') ?? '';
421
+ const registration = registrations.find((entry) => entry.action === action);
422
+ element.removeAttribute('title');
423
+ if (!registration)
424
+ continue;
425
+ const labels = registration.bindings.map((binding) => {
426
+ try {
427
+ return options.formatShortcut?.(binding) ?? formatShortcut(binding);
428
+ }
429
+ catch (error) {
430
+ report(error);
431
+ return formatShortcut(binding);
432
+ }
433
+ });
434
+ const badge = el(doc, 'kbd', 'layouts-shortcut', labels[0]);
435
+ badge.setAttribute('aria-hidden', 'true');
436
+ element.append(badge);
437
+ element.title = `${label} (${labels.join(' / ')})`;
438
+ if (!element.disabled)
439
+ element.setAttribute('aria-keyshortcuts', registration.bindings.map(ariaShortcut).join(' '));
440
+ }
441
+ positionMenus();
442
+ };
443
+ refreshShortcuts = updateHints;
444
+ local.add(() => {
445
+ if (refreshShortcuts === updateHints)
446
+ refreshShortcuts = () => { };
447
+ });
448
+ const target = { dialog, context, close: () => local.dispose() };
449
+ shortcutTarget = target;
450
+ local.add(() => {
451
+ if (shortcutTarget === target)
452
+ shortcutTarget = undefined;
453
+ });
383
454
  dialog.showModal();
384
- positionMenus();
455
+ updateHints();
385
456
  const observer = new win.ResizeObserver(positionMenus);
386
457
  observer.observe(dialog);
387
458
  observer.observe(anchor);
@@ -397,6 +468,8 @@ export function createPaneMenu(root, options, windows, refresh, report, requestC
397
468
  dialog.focus({ preventScroll: true });
398
469
  }
399
470
  return {
471
+ getShortcutTarget: () => shortcutTarget,
472
+ refreshShortcuts: () => refreshShortcuts(),
400
473
  open,
401
474
  addTab(anchor, group) {
402
475
  const layout = options.store.getSnapshot();