quilt-vanilla 0.2.4 → 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 (74) hide show
  1. package/README.md +35 -39
  2. package/dist/actions.d.ts +14 -0
  3. package/dist/actions.d.ts.map +1 -0
  4. package/dist/actions.js +115 -0
  5. package/dist/actions.js.map +1 -0
  6. package/dist/chrome-size.d.ts +7 -0
  7. package/dist/chrome-size.d.ts.map +1 -0
  8. package/dist/chrome-size.js +86 -0
  9. package/dist/chrome-size.js.map +1 -0
  10. package/dist/close.d.ts.map +1 -1
  11. package/dist/close.js +3 -1
  12. package/dist/close.js.map +1 -1
  13. package/dist/corners.d.ts +2 -1
  14. package/dist/corners.d.ts.map +1 -1
  15. package/dist/corners.js +2 -2
  16. package/dist/corners.js.map +1 -1
  17. package/dist/index.d.ts +6 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/menu.d.ts +9 -3
  22. package/dist/menu.d.ts.map +1 -1
  23. package/dist/menu.js +110 -37
  24. package/dist/menu.js.map +1 -1
  25. package/dist/panes.d.ts.map +1 -1
  26. package/dist/panes.js +84 -52
  27. package/dist/panes.js.map +1 -1
  28. package/dist/registry.d.ts +1 -0
  29. package/dist/registry.d.ts.map +1 -1
  30. package/dist/registry.js +4 -3
  31. package/dist/registry.js.map +1 -1
  32. package/dist/renderer.d.ts.map +1 -1
  33. package/dist/renderer.js +128 -36
  34. package/dist/renderer.js.map +1 -1
  35. package/dist/resize.d.ts +2 -1
  36. package/dist/resize.d.ts.map +1 -1
  37. package/dist/resize.js +2 -2
  38. package/dist/resize.js.map +1 -1
  39. package/dist/shortcut-registration.d.ts +9 -0
  40. package/dist/shortcut-registration.d.ts.map +1 -0
  41. package/dist/shortcut-registration.js +95 -0
  42. package/dist/shortcut-registration.js.map +1 -0
  43. package/dist/shortcuts.d.ts +7 -6
  44. package/dist/shortcuts.d.ts.map +1 -1
  45. package/dist/shortcuts.js +28 -50
  46. package/dist/shortcuts.js.map +1 -1
  47. package/dist/styles.css +74 -9
  48. package/dist/tabs.d.ts.map +1 -1
  49. package/dist/tabs.js +10 -12
  50. package/dist/tabs.js.map +1 -1
  51. package/dist/types.d.ts +50 -13
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/types.js.map +1 -1
  54. package/dist/workspace-api.d.ts +186 -0
  55. package/dist/workspace-api.d.ts.map +1 -0
  56. package/dist/workspace-api.js +409 -0
  57. package/dist/workspace-api.js.map +1 -0
  58. package/package.json +2 -2
  59. package/src/actions.ts +125 -0
  60. package/src/chrome-size.ts +99 -0
  61. package/src/close.ts +6 -1
  62. package/src/corners.ts +2 -1
  63. package/src/index.ts +31 -4
  64. package/src/menu.ts +123 -43
  65. package/src/panes.ts +82 -55
  66. package/src/registry.ts +6 -4
  67. package/src/renderer.ts +181 -54
  68. package/src/resize.ts +2 -1
  69. package/src/shortcut-registration.ts +106 -0
  70. package/src/shortcuts.ts +38 -63
  71. package/src/styles.css +74 -9
  72. package/src/tabs.ts +9 -11
  73. package/src/types.ts +67 -14
  74. package/src/workspace-api.ts +583 -0
package/src/styles.css CHANGED
@@ -162,10 +162,13 @@
162
162
  .layouts-menu {
163
163
  position: fixed;
164
164
  margin: 0;
165
- padding: 5px;
166
165
  width: 240px;
167
166
  max-height: min(520px, 70vh);
168
167
  overflow: auto;
168
+ }
169
+ .layouts-menu,
170
+ .layouts-submenu-content {
171
+ padding: var(--layouts-spacing, 5px);
169
172
  background: var(--layouts-header, #242a34);
170
173
  color: var(--layouts-text, #e5e9f0);
171
174
  border: 1px solid var(--layouts-line, #39414e);
@@ -374,9 +377,6 @@
374
377
  .layouts-submenu-content {
375
378
  display: flex;
376
379
  flex-direction: column;
377
- padding: 4px;
378
- border: 1px solid var(--layouts-line);
379
- background: var(--layouts-panel);
380
380
  }
381
381
  .layouts-corner {
382
382
  position: absolute;
@@ -437,8 +437,6 @@
437
437
  box-sizing: border-box;
438
438
  overflow: auto;
439
439
  width: 200px;
440
- box-shadow: 0 8px 24px #0005;
441
- border-radius: var(--layouts-radius, 5px);
442
440
  }
443
441
 
444
442
  .layouts-corner-overlay {
@@ -482,9 +480,6 @@
482
480
  .layouts-picker button {
483
481
  min-height: var(--layouts-control-height, 28px);
484
482
  }
485
- .layouts-menu {
486
- padding: var(--layouts-spacing, 5px);
487
- }
488
483
 
489
484
  /* Tapered chrome overlays the pane; only its painted footprint intercepts input. */
490
485
  .layouts-group[data-tab-bar='tapered'] > .layouts-header {
@@ -831,3 +826,73 @@
831
826
  flex: 0 0 calc(var(--layouts-icon-size, 16px) + 16px);
832
827
  min-width: calc(var(--layouts-icon-size, 16px) + 16px);
833
828
  }
829
+
830
+ /* Chrome remains usable at the renderer's minimum region size, even with larger icons. */
831
+ .layouts-group[data-tab-placement='top'] > .layouts-header {
832
+ min-height: calc(var(--layouts-icon-size, 16px) + 9px);
833
+ }
834
+ .layouts-group[data-tab-placement='top'][data-tab-display='automatic'] .layouts-tab-item {
835
+ min-width: max(32px, calc(var(--layouts-icon-size, 16px) + 8px));
836
+ }
837
+ .layouts-group[data-tab-placement='top'][data-tab-display='automatic']
838
+ .layouts-tab-item[data-active='true']:has(> .layouts-tab-close) {
839
+ min-width: max(
840
+ 56px,
841
+ calc(var(--layouts-icon-size, 16px) + 11px + max(22px, var(--layouts-icon-size, 16px)))
842
+ );
843
+ }
844
+ .layouts-group[data-tab-placement='left'] > .layouts-header {
845
+ min-width: max(32px, calc(var(--layouts-icon-size, 16px) + 10px));
846
+ }
847
+ .layouts-group[data-tab-placement='left'] .layouts-tab-item,
848
+ .layouts-group[data-tab-placement='left'] > .layouts-header > .layouts-button {
849
+ flex-basis: max(30px, calc(var(--layouts-icon-size, 16px) + 8px));
850
+ height: max(30px, calc(var(--layouts-icon-size, 16px) + 8px));
851
+ }
852
+ .layouts-group[data-tab-placement='left'][data-tab-display='automatic']
853
+ .layouts-tab-item[data-active='true']:has(> .layouts-tab-close) {
854
+ flex-basis: calc(
855
+ max(30px, var(--layouts-icon-size, 16px) + 8px) + max(22px, var(--layouts-icon-size, 16px))
856
+ );
857
+ height: calc(
858
+ max(30px, var(--layouts-icon-size, 16px) + 8px) + max(22px, var(--layouts-icon-size, 16px))
859
+ );
860
+ }
861
+ .layouts-group[data-tab-placement='left'][data-tab-display='automatic']
862
+ .layouts-tab-item[data-active='true']:has(> .layouts-tab-close)
863
+ > .layouts-tab {
864
+ flex-basis: max(30px, calc(var(--layouts-icon-size, 16px) + 8px));
865
+ }
866
+
867
+ .layouts-tab-item .layouts-tab-close {
868
+ flex-basis: max(22px, var(--layouts-icon-size, 16px));
869
+ width: max(22px, var(--layouts-icon-size, 16px));
870
+ max-height: max(24px, var(--layouts-icon-size, 16px));
871
+ }
872
+ .layouts-group[data-tab-placement='left'][data-tab-display='automatic']
873
+ .layouts-tab-item[data-active='true']
874
+ > .layouts-tab-close {
875
+ flex-basis: max(22px, var(--layouts-icon-size, 16px));
876
+ height: max(22px, var(--layouts-icon-size, 16px));
877
+ }
878
+
879
+ .layouts-shortcut {
880
+ display: inline-flex;
881
+ align-items: center;
882
+ justify-content: center;
883
+ margin-left: auto;
884
+ min-width: 1.8em;
885
+ min-height: 1.8em;
886
+ padding: 0.15em 0.45em;
887
+ flex-shrink: 0;
888
+ border: 1px solid color-mix(in srgb, var(--layouts-text, #e5e9f0) 22%, transparent);
889
+ border-radius: 0.5em;
890
+ background: color-mix(in srgb, var(--layouts-text, #e5e9f0) 4%, transparent);
891
+ font: inherit;
892
+ font-size: 0.9em;
893
+ font-weight: 500;
894
+ line-height: 1.2;
895
+ color: var(--layouts-text, #e5e9f0);
896
+ white-space: nowrap;
897
+ pointer-events: none;
898
+ }
package/src/tabs.ts CHANGED
@@ -114,17 +114,15 @@ export function fillTabs(tabs: HTMLElement, group: Group, panes: Pane[], deps: T
114
114
  x.dataset.focusId = `close-${pane.id}`;
115
115
  x.onclick = close;
116
116
  item.append(x);
117
- if (shortcutEnabled(options, 'middleClickClose')) {
118
- item.onmousedown = (e) => {
119
- if (e.button === 1) e.preventDefault();
120
- };
121
- item.onauxclick = (e) => {
122
- if (e.button === 1) {
123
- e.preventDefault();
124
- close();
125
- }
126
- };
127
- }
117
+ item.onmousedown = (e) => {
118
+ if (e.button === 1 && shortcutEnabled(options, 'middleClickClose')) e.preventDefault();
119
+ };
120
+ item.onauxclick = (e) => {
121
+ if (e.button === 1 && shortcutEnabled(options, 'middleClickClose')) {
122
+ e.preventDefault();
123
+ close();
124
+ }
125
+ };
128
126
  }
129
127
  tabs.append(item);
130
128
  });
package/src/types.ts CHANGED
@@ -3,9 +3,13 @@ import type { WorkspacePreset } from './workspace.js';
3
3
  import type { Messages } from './messages.js';
4
4
  import type { TabRegistry } from './registry.js';
5
5
  import type { LayoutTheme } from './theme.js';
6
- import type { LayoutStore, Pane, WindowPlacement } from 'quilt-core';
6
+ import type { LayoutStore, Layout, Pane, WindowPlacement } from 'quilt-core';
7
7
  export interface PaneContext<State = unknown> {
8
8
  element: HTMLElement;
9
+ /** Aborted when this view is torn down, including a failed mount. */
10
+ signal: AbortSignal;
11
+ /** Register view-owned cleanup; callbacks run once in reverse order. */
12
+ onCleanup(callback: () => void): void;
9
13
  reportError(error: unknown): void;
10
14
  document: Document;
11
15
  window: Window;
@@ -21,7 +25,7 @@ export interface PaneView {
21
25
  resize?(width: number, height: number): void;
22
26
  update?(pane: Pane): void;
23
27
  }
24
- export type PaneRenderer<State = unknown> = (context: PaneContext<State>) => PaneView;
28
+ export type PaneRenderer<State = unknown> = (context: PaneContext<State>) => PaneView | void;
25
29
  export interface TabBarStyle {
26
30
  /** Left placement uses an icon-only rail; mode and shape apply in either placement. */
27
31
  placement?: 'top' | 'left';
@@ -40,13 +44,10 @@ export interface TabBarOptions extends TabBarStyle {
40
44
  /** Partial overrides keyed by stable group ID; not serialized in Layout JSON. */
41
45
  regions?: Record<string, TabBarStyle>;
42
46
  }
43
- export interface ResolvedLayoutOptions<State = unknown> {
47
+ /** Live appearance, interactions, and application integration callbacks. */
48
+ export interface WorkspaceSettings<State = unknown> {
44
49
  tabBar?: TabBarOptions;
45
- store: LayoutStore;
46
- tabs?: TabRegistry;
47
50
  theme?: LayoutTheme;
48
- renderers?: Record<string, PaneRenderer<State>>;
49
- registry?: PaneRegistry<PaneRenderer<State>>;
50
51
  messages?: Messages;
51
52
  popouts?: boolean;
52
53
  confirmClose?: (request: CloseRequest) => boolean | Promise<boolean>;
@@ -55,12 +56,11 @@ export interface ResolvedLayoutOptions<State = unknown> {
55
56
  /** Opt-in conveniences; omitted and false disable all. */
56
57
  shortcuts?:
57
58
  | boolean
58
- | {
59
- maximize?: boolean | KeyBinding | readonly KeyBinding[];
59
+ | (Partial<Record<WorkspaceAction, ShortcutRegistration>> & {
60
60
  middleClickClose?: boolean;
61
- addTab?: boolean | KeyBinding | readonly KeyBinding[];
62
- restoreClosedTab?: boolean | KeyBinding | readonly KeyBinding[];
63
- };
61
+ });
62
+ /** Customize presentation only; matching and accessibility encoding use the binding. */
63
+ formatShortcut?: (binding: Readonly<KeyBinding>) => string;
64
64
  getPaneState?: (paneId: string) => State;
65
65
  onError?: (error: unknown) => void;
66
66
  /** Copy additional app styles/assets into a same-origin companion document. */
@@ -68,12 +68,25 @@ export interface ResolvedLayoutOptions<State = unknown> {
68
68
  /** Override window creation for applications with a managed same-origin host. Must return synchronously. */
69
69
  openWindow?: (pane: Pane, placement: WindowPlacement) => Window | null;
70
70
  }
71
+ /** Internal renderer configuration; mounted workspaces create this model themselves. */
72
+ export interface ResolvedLayoutOptions<State = unknown> extends WorkspaceSettings<State> {
73
+ store: LayoutStore;
74
+ tabs?: TabRegistry;
75
+ renderers?: Record<string, PaneRenderer<State>>;
76
+ registry?: PaneRegistry<PaneRenderer<State>>;
77
+ }
78
+ /** Initial configuration is consumed once. Use workspace commands for later changes. */
79
+ export type InitialConfiguration =
80
+ | { initialLayout?: Layout; initialWorkspace?: never }
81
+ | { initialLayout?: never; initialWorkspace: WorkspacePreset };
82
+ /** Commands and subscriptions for a workspace-owned store. */
83
+ export type WorkspaceStore = Omit<LayoutStore, 'dispose'>;
71
84
  /** Choose a unified registry OR the low-level renderer and creation maps. */
72
85
  export type LayoutOptions<State = unknown> = Omit<
73
86
  ResolvedLayoutOptions<State>,
74
- 'registry' | 'renderers' | 'tabs' | 'getPaneState'
87
+ 'store' | 'registry' | 'renderers' | 'tabs' | 'getPaneState'
75
88
  > &
76
- (unknown extends State
89
+ InitialConfiguration & { store?: never } & (unknown extends State
77
90
  ? { getPaneState?: (paneId: string) => State }
78
91
  : { getPaneState: (paneId: string) => State }) &
79
92
  (
@@ -85,6 +98,42 @@ export type LayoutOptionUpdates<State = unknown> = {
85
98
  [Key in keyof Omit<ResolvedLayoutOptions<State>, 'store'>]?:
86
99
  ResolvedLayoutOptions<State>[Key] | undefined;
87
100
  };
101
+ export type WorkspaceAction =
102
+ | 'maximize'
103
+ | 'addTab'
104
+ | 'restoreClosedTab'
105
+ | 'closeActiveTab'
106
+ | 'closePane'
107
+ | 'closeEmptyPane'
108
+ | 'popout'
109
+ | 'joinSiblingRegion'
110
+ | 'splitLeft'
111
+ | 'splitRight'
112
+ | 'splitUp'
113
+ | 'splitDown'
114
+ | 'tabOrientationDefault'
115
+ | 'tabOrientationHorizontal'
116
+ | 'tabOrientationVertical'
117
+ | 'tabDisplayDefault'
118
+ | 'tabDisplayAutomatic'
119
+ | 'tabDisplayCompact';
120
+ export interface ActionContext {
121
+ groupId?: string;
122
+ paneId?: string;
123
+ }
124
+ export type ShortcutRegistration =
125
+ | boolean
126
+ | KeyBinding
127
+ | readonly KeyBinding[]
128
+ | {
129
+ bindings: true | KeyBinding | readonly KeyBinding[];
130
+ handling?: 'quilt' | 'external';
131
+ };
132
+ export interface RegisteredShortcut {
133
+ readonly action: WorkspaceAction;
134
+ readonly bindings: readonly Readonly<KeyBinding>[];
135
+ readonly handling: 'quilt' | 'external';
136
+ }
88
137
  export interface KeyBinding {
89
138
  key: string;
90
139
  ctrl?: boolean;
@@ -99,6 +148,10 @@ export interface CloseRequest {
99
148
  signal: AbortSignal;
100
149
  }
101
150
  export interface MountedLayout<State = unknown> {
151
+ readonly store: WorkspaceStore;
152
+ canExecuteAction(action: WorkspaceAction, context?: ActionContext): boolean;
153
+ executeAction(action: WorkspaceAction, context?: ActionContext): Promise<boolean>;
154
+ getShortcuts(): readonly RegisteredShortcut[];
102
155
  updateOptions(options: LayoutOptionUpdates<State>): void;
103
156
  exportWorkspace(): WorkspacePreset;
104
157
  loadWorkspace(input: unknown): void;