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/dist/panes.js CHANGED
@@ -9,66 +9,98 @@ export function mountPane(doc, pane, location, options) {
9
9
  const renderer = Object.hasOwn(options.renderers ?? {}, pane.type)
10
10
  ? options.renderers?.[pane.type]
11
11
  : undefined;
12
- let view;
12
+ let view = { dispose() { } };
13
+ let observer;
14
+ let disposed = false;
15
+ const controller = new win.AbortController();
16
+ const cleanups = [];
17
+ const reportCleanup = (error) => {
18
+ try {
19
+ options.onError?.(error);
20
+ }
21
+ catch {
22
+ /* Finish teardown even if reporting fails. */
23
+ }
24
+ };
25
+ const run = (callback) => {
26
+ try {
27
+ callback();
28
+ }
29
+ catch (error) {
30
+ reportCleanup(error);
31
+ }
32
+ };
33
+ const dispose = () => {
34
+ if (disposed)
35
+ return;
36
+ disposed = true;
37
+ run(() => controller.abort());
38
+ run(() => observer?.disconnect());
39
+ run(() => view.dispose());
40
+ for (const cleanup of cleanups.splice(0).reverse())
41
+ run(cleanup);
42
+ element.remove();
43
+ };
13
44
  let result;
14
45
  const reportError = (error) => {
15
46
  if (result)
16
47
  result.failed = true;
17
48
  options.onError?.(error);
18
49
  };
19
- if (renderer)
20
- view = renderer({
21
- element,
22
- reportError,
23
- document: doc,
24
- window: win,
25
- pane,
26
- state: options.getPaneState?.(pane.id),
27
- location,
28
- });
29
- else {
30
- element.append(el(doc, 'div', 'layouts-placeholder', message(options, 'Unknown pane type: {type}. Register a renderer to display this pane.', {
31
- type: pane.type,
32
- })));
33
- view = { dispose() { } };
34
- }
35
- const observer = new ResizeObserver((entries) => {
36
- const rect = entries[0]?.contentRect;
37
- if (rect) {
38
- try {
39
- view.resize?.(rect.width, rect.height);
40
- }
41
- catch (error) {
42
- options.onError?.(error);
43
- }
50
+ try {
51
+ if (renderer)
52
+ view = renderer({
53
+ signal: controller.signal,
54
+ onCleanup(callback) {
55
+ if (disposed)
56
+ run(callback);
57
+ else
58
+ cleanups.push(callback);
59
+ },
60
+ element,
61
+ reportError,
62
+ document: doc,
63
+ window: win,
64
+ pane,
65
+ state: options.getPaneState?.(pane.id),
66
+ location,
67
+ }) ?? { dispose() { } };
68
+ else {
69
+ element.append(el(doc, 'div', 'layouts-placeholder', message(options, 'Unknown pane type: {type}. Register a renderer to display this pane.', {
70
+ type: pane.type,
71
+ })));
72
+ view = { dispose() { } };
44
73
  }
45
- });
46
- observer.observe(element);
47
- let disposed = false;
48
- result = {
49
- element,
50
- view,
51
- pane,
52
- signature: JSON.stringify(pane),
53
- renderer,
54
- dispose() {
55
- if (disposed)
56
- return;
57
- disposed = true;
58
- observer.disconnect();
59
- try {
60
- view.dispose();
61
- }
62
- finally {
63
- element.remove();
74
+ observer = new ResizeObserver((entries) => {
75
+ const rect = entries[0]?.contentRect;
76
+ if (rect) {
77
+ try {
78
+ view.resize?.(rect.width, rect.height);
79
+ }
80
+ catch (error) {
81
+ options.onError?.(error);
82
+ }
64
83
  }
65
- },
66
- };
67
- if (location === 'main')
68
- void view.ready?.catch((error) => {
69
- if (!disposed)
70
- reportError(error);
71
84
  });
72
- return result;
85
+ observer.observe(element);
86
+ result = {
87
+ element,
88
+ view,
89
+ pane,
90
+ signature: JSON.stringify(pane),
91
+ renderer,
92
+ dispose,
93
+ };
94
+ if (location === 'main')
95
+ void view.ready?.catch((error) => {
96
+ if (!disposed)
97
+ reportError(error);
98
+ });
99
+ return result;
100
+ }
101
+ catch (error) {
102
+ dispose();
103
+ throw error;
104
+ }
73
105
  }
74
106
  //# sourceMappingURL=panes.js.map
package/dist/panes.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"panes.js","sourceRoot":"","sources":["../src/panes.ts"],"names":[],"mappings":"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","sourcesContent":["import { message } from './messages.js';\nimport type { Pane } from 'quilt-core';\nimport type { ResolvedLayoutOptions, PaneView, PaneRenderer } from './types.js';\nimport { el } from './lifetime.js';\nexport interface MountedPane {\n element: HTMLElement;\n view: PaneView;\n pane: Pane;\n signature: string;\n renderer: PaneRenderer | undefined;\n failed?: boolean;\n dispose(): void;\n}\nexport function mountPane(\n doc: Document,\n pane: Pane,\n location: 'main' | 'popout',\n options: ResolvedLayoutOptions,\n): MountedPane {\n const win = doc.defaultView;\n if (!win) throw new Error('Pane needs a live document');\n const element = el(doc, 'div', 'layouts-pane');\n element.dataset.paneId = pane.id;\n const renderer = Object.hasOwn(options.renderers ?? {}, pane.type)\n ? options.renderers?.[pane.type]\n : undefined;\n let view: PaneView;\n let result: MountedPane | undefined;\n const reportError = (error: unknown) => {\n if (result) result.failed = true;\n options.onError?.(error);\n };\n if (renderer)\n view = renderer({\n element,\n reportError,\n document: doc,\n window: win,\n pane,\n state: options.getPaneState?.(pane.id),\n location,\n });\n else {\n element.append(\n el(\n doc,\n 'div',\n 'layouts-placeholder',\n message(options, 'Unknown pane type: {type}. Register a renderer to display this pane.', {\n type: pane.type,\n }),\n ),\n );\n view = { dispose() {} };\n }\n const observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (rect) {\n try {\n view.resize?.(rect.width, rect.height);\n } catch (error) {\n options.onError?.(error);\n }\n }\n });\n observer.observe(element);\n let disposed = false;\n result = {\n element,\n view,\n pane,\n signature: JSON.stringify(pane),\n renderer,\n dispose() {\n if (disposed) return;\n disposed = true;\n observer.disconnect();\n try {\n view.dispose();\n } finally {\n element.remove();\n }\n },\n };\n if (location === 'main')\n void view.ready?.catch((error) => {\n if (!disposed) reportError(error);\n });\n return result;\n}\n"]}
1
+ {"version":3,"file":"panes.js","sourceRoot":"","sources":["../src/panes.ts"],"names":[],"mappings":"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","sourcesContent":["import { message } from './messages.js';\nimport type { Pane } from 'quilt-core';\nimport type { ResolvedLayoutOptions, PaneView, PaneRenderer } from './types.js';\nimport { el } from './lifetime.js';\nexport interface MountedPane {\n element: HTMLElement;\n view: PaneView;\n pane: Pane;\n signature: string;\n renderer: PaneRenderer | undefined;\n failed?: boolean;\n dispose(): void;\n}\nexport function mountPane(\n doc: Document,\n pane: Pane,\n location: 'main' | 'popout',\n options: ResolvedLayoutOptions,\n): MountedPane {\n const win = doc.defaultView;\n if (!win) throw new Error('Pane needs a live document');\n const element = el(doc, 'div', 'layouts-pane');\n element.dataset.paneId = pane.id;\n const renderer = Object.hasOwn(options.renderers ?? {}, pane.type)\n ? options.renderers?.[pane.type]\n : undefined;\n let view: PaneView = { dispose() {} };\n let observer: ResizeObserver | undefined;\n let disposed = false;\n const controller = new (win as Window & typeof globalThis).AbortController();\n const cleanups: (() => void)[] = [];\n const reportCleanup = (error: unknown) => {\n try {\n options.onError?.(error);\n } catch {\n /* Finish teardown even if reporting fails. */\n }\n };\n const run = (callback: () => void) => {\n try {\n callback();\n } catch (error) {\n reportCleanup(error);\n }\n };\n const dispose = () => {\n if (disposed) return;\n disposed = true;\n run(() => controller.abort());\n run(() => observer?.disconnect());\n run(() => view.dispose());\n for (const cleanup of cleanups.splice(0).reverse()) run(cleanup);\n element.remove();\n };\n let result: MountedPane | undefined;\n const reportError = (error: unknown) => {\n if (result) result.failed = true;\n options.onError?.(error);\n };\n try {\n if (renderer)\n view = renderer({\n signal: controller.signal,\n onCleanup(callback) {\n if (disposed) run(callback);\n else cleanups.push(callback);\n },\n element,\n reportError,\n document: doc,\n window: win,\n pane,\n state: options.getPaneState?.(pane.id),\n location,\n }) ?? { dispose() {} };\n else {\n element.append(\n el(\n doc,\n 'div',\n 'layouts-placeholder',\n message(options, 'Unknown pane type: {type}. Register a renderer to display this pane.', {\n type: pane.type,\n }),\n ),\n );\n view = { dispose() {} };\n }\n observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (rect) {\n try {\n view.resize?.(rect.width, rect.height);\n } catch (error) {\n options.onError?.(error);\n }\n }\n });\n observer.observe(element);\n result = {\n element,\n view,\n pane,\n signature: JSON.stringify(pane),\n renderer,\n dispose,\n };\n if (location === 'main')\n void view.ready?.catch((error) => {\n if (!disposed) reportError(error);\n });\n return result;\n } catch (error) {\n dispose();\n throw error;\n }\n}\n"]}
@@ -22,6 +22,7 @@ export declare class TabRegistry {
22
22
  subscribe(listener: () => void): () => void;
23
23
  private emit;
24
24
  }
25
+ export declare function createPaneId(): string;
25
26
  export interface PaneRegistration<T> {
26
27
  type: string;
27
28
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B;2EACuE;IACvE,MAAM,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,IAAI,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,SAAS,CAAC;CACtF;AACD,mFAAmF;AACnF,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,SAAS,CAAyB;IAC1C,YAAY,OAAO,GAAE,SAAS,eAAe,EAAO,EAEnD;IACD,IAAI,IAAI,SAAS,eAAe,EAAE,CAEjC;IACD,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,IAAI,CAc3C;IACD,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK1C;IACD,OAAO,CAAC,IAAI;CAGb;AAID,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,CAAC,CAAC;IACR,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3F;AACD,2EAA2E;AAC3E,qBAAa,YAAY,CAAC,CAAC;IACzB,OAAO,CAAC,OAAO,CAAoD;IACnE,OAAO,CAAC,SAAS,CAAyB;IAC1C,QAAQ,CAAC,IAAI,cAAqB;IAClC,YAAY,OAAO,GAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAO,EAEvD;IACD,IAAI,IAAI,SAAS,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAE/C;IACD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAoC/C;IACD,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK1C;IACD,OAAO,CAAC,IAAI;CAGb"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B;2EACuE;IACvE,MAAM,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,IAAI,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,SAAS,CAAC;CACtF;AACD,mFAAmF;AACnF,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,SAAS,CAAyB;IAC1C,YAAY,OAAO,GAAE,SAAS,eAAe,EAAO,EAEnD;IACD,IAAI,IAAI,SAAS,eAAe,EAAE,CAEjC;IACD,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,IAAI,CAc3C;IACD,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK1C;IACD,OAAO,CAAC,IAAI;CAGb;AAGD,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,CAAC,CAAC;IACR,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3F;AACD,2EAA2E;AAC3E,qBAAa,YAAY,CAAC,CAAC;IACzB,OAAO,CAAC,OAAO,CAAoD;IACnE,OAAO,CAAC,SAAS,CAAyB;IAC1C,QAAQ,CAAC,IAAI,cAAqB;IAClC,YAAY,OAAO,GAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAO,EAEvD;IACD,IAAI,IAAI,SAAS,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAE/C;IACD,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAiC/C;IACD,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAK1C;IACD,OAAO,CAAC,IAAI;CAGb"}
package/dist/registry.js CHANGED
@@ -37,6 +37,9 @@ export class TabRegistry {
37
37
  }
38
38
  }
39
39
  let instanceSequence = 0;
40
+ export function createPaneId() {
41
+ return (globalThis.crypto?.randomUUID?.() ?? `quilt-${Date.now().toString(36)}-${++instanceSequence}`);
42
+ }
40
43
  /** Framework-neutral registrations; the adapter supplies the view type. */
41
44
  export class PaneRegistry {
42
45
  entries = new Map();
@@ -71,9 +74,7 @@ export class PaneRegistry {
71
74
  title: stored.title,
72
75
  ...(stored.icon !== undefined ? { icon: stored.icon } : {}),
73
76
  ...supplied,
74
- id: supplied.id ??
75
- globalThis.crypto?.randomUUID?.() ??
76
- `quilt-${Date.now().toString(36)}-${++instanceSequence}`,
77
+ id: supplied.id ?? createPaneId(),
77
78
  };
78
79
  },
79
80
  });
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Pane } from 'quilt-core';\nexport interface TabRegistration {\n id: string;\n title: string;\n description?: string;\n icon?: string;\n keywords?: readonly string[];\n /** Source is undefined when creating content in a completely empty workspace.\n * Return a fresh pane with a unique id; undefined cancels creation. */\n create: (context: { source: Pane | undefined; groupId: string }) => Pane | undefined;\n}\n/** Available content types, independent of open pane instances and layout JSON. */\nexport class TabRegistry {\n private entries = new Map<string, TabRegistration>();\n private listeners = new Set<() => void>();\n constructor(entries: readonly TabRegistration[] = []) {\n for (const entry of entries) this.register(entry);\n }\n list(): readonly TabRegistration[] {\n return [...this.entries.values()];\n }\n register(entry: TabRegistration): () => void {\n if (!entry.id || this.entries.has(entry.id))\n throw new Error(`Duplicate or empty tab registration: ${entry.id}`);\n const stored = Object.freeze({\n ...entry,\n ...(entry.keywords ? { keywords: Object.freeze([...entry.keywords]) } : {}),\n });\n this.entries.set(entry.id, stored);\n this.emit();\n return () => {\n if (this.entries.get(entry.id) !== stored) return;\n this.entries.delete(entry.id);\n this.emit();\n };\n }\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n private emit() {\n for (const listener of this.listeners) listener();\n }\n}\n\nlet instanceSequence = 0;\n\nexport interface PaneRegistration<T> {\n type: string;\n title: string;\n view: T;\n description?: string;\n icon?: string;\n keywords?: readonly string[];\n confirmClose?: boolean;\n create?: (context: Parameters<TabRegistration['create']>[0]) => Partial<Pane> | undefined;\n}\n/** Framework-neutral registrations; the adapter supplies the view type. */\nexport class PaneRegistry<T> {\n private entries = new Map<string, Readonly<PaneRegistration<T>>>();\n private listeners = new Set<() => void>();\n readonly tabs = new TabRegistry();\n constructor(entries: readonly PaneRegistration<T>[] = []) {\n entries.forEach((entry) => this.register(entry));\n }\n list(): readonly Readonly<PaneRegistration<T>>[] {\n return [...this.entries.values()];\n }\n register(entry: PaneRegistration<T>): () => void {\n if (!entry.type || this.entries.has(entry.type))\n throw new Error(`Duplicate or empty pane type: ${entry.type}`);\n const stored = Object.freeze({\n ...entry,\n ...(entry.keywords ? { keywords: Object.freeze([...entry.keywords]) } : {}),\n });\n this.entries.set(entry.type, stored);\n const removeTab = this.tabs.register({\n id: entry.type,\n title: entry.title,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.icon !== undefined ? { icon: entry.icon } : {}),\n ...(entry.keywords !== undefined ? { keywords: entry.keywords } : {}),\n create: (context) => {\n const supplied = stored.create ? stored.create(context) : {};\n if (supplied === undefined) return undefined;\n return {\n type: stored.type,\n title: stored.title,\n ...(stored.icon !== undefined ? { icon: stored.icon } : {}),\n ...supplied,\n id:\n supplied.id ??\n globalThis.crypto?.randomUUID?.() ??\n `quilt-${Date.now().toString(36)}-${++instanceSequence}`,\n };\n },\n });\n this.emit();\n return () => {\n if (this.entries.get(entry.type) !== stored) return;\n this.entries.delete(entry.type);\n removeTab();\n this.emit();\n };\n }\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n private emit() {\n for (const listener of [...this.listeners]) listener();\n }\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Pane } from 'quilt-core';\nexport interface TabRegistration {\n id: string;\n title: string;\n description?: string;\n icon?: string;\n keywords?: readonly string[];\n /** Source is undefined when creating content in a completely empty workspace.\n * Return a fresh pane with a unique id; undefined cancels creation. */\n create: (context: { source: Pane | undefined; groupId: string }) => Pane | undefined;\n}\n/** Available content types, independent of open pane instances and layout JSON. */\nexport class TabRegistry {\n private entries = new Map<string, TabRegistration>();\n private listeners = new Set<() => void>();\n constructor(entries: readonly TabRegistration[] = []) {\n for (const entry of entries) this.register(entry);\n }\n list(): readonly TabRegistration[] {\n return [...this.entries.values()];\n }\n register(entry: TabRegistration): () => void {\n if (!entry.id || this.entries.has(entry.id))\n throw new Error(`Duplicate or empty tab registration: ${entry.id}`);\n const stored = Object.freeze({\n ...entry,\n ...(entry.keywords ? { keywords: Object.freeze([...entry.keywords]) } : {}),\n });\n this.entries.set(entry.id, stored);\n this.emit();\n return () => {\n if (this.entries.get(entry.id) !== stored) return;\n this.entries.delete(entry.id);\n this.emit();\n };\n }\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n private emit() {\n for (const listener of this.listeners) listener();\n }\n}\n\nlet instanceSequence = 0;\nexport function createPaneId(): string {\n return (\n globalThis.crypto?.randomUUID?.() ?? `quilt-${Date.now().toString(36)}-${++instanceSequence}`\n );\n}\n\nexport interface PaneRegistration<T> {\n type: string;\n title: string;\n view: T;\n description?: string;\n icon?: string;\n keywords?: readonly string[];\n confirmClose?: boolean;\n create?: (context: Parameters<TabRegistration['create']>[0]) => Partial<Pane> | undefined;\n}\n/** Framework-neutral registrations; the adapter supplies the view type. */\nexport class PaneRegistry<T> {\n private entries = new Map<string, Readonly<PaneRegistration<T>>>();\n private listeners = new Set<() => void>();\n readonly tabs = new TabRegistry();\n constructor(entries: readonly PaneRegistration<T>[] = []) {\n entries.forEach((entry) => this.register(entry));\n }\n list(): readonly Readonly<PaneRegistration<T>>[] {\n return [...this.entries.values()];\n }\n register(entry: PaneRegistration<T>): () => void {\n if (!entry.type || this.entries.has(entry.type))\n throw new Error(`Duplicate or empty pane type: ${entry.type}`);\n const stored = Object.freeze({\n ...entry,\n ...(entry.keywords ? { keywords: Object.freeze([...entry.keywords]) } : {}),\n });\n this.entries.set(entry.type, stored);\n const removeTab = this.tabs.register({\n id: entry.type,\n title: entry.title,\n ...(entry.description !== undefined ? { description: entry.description } : {}),\n ...(entry.icon !== undefined ? { icon: entry.icon } : {}),\n ...(entry.keywords !== undefined ? { keywords: entry.keywords } : {}),\n create: (context) => {\n const supplied = stored.create ? stored.create(context) : {};\n if (supplied === undefined) return undefined;\n return {\n type: stored.type,\n title: stored.title,\n ...(stored.icon !== undefined ? { icon: stored.icon } : {}),\n ...supplied,\n id: supplied.id ?? createPaneId(),\n };\n },\n });\n this.emit();\n return () => {\n if (this.entries.get(entry.type) !== stored) return;\n this.entries.delete(entry.type);\n removeTab();\n this.emit();\n };\n }\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n private emit() {\n for (const listener of [...this.listeners]) listener();\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAyB,aAAa,EAAE,MAAM,YAAY,CAAC;AAiBtF,wBAAgB,WAAW,CAAC,KAAK,GAAG,OAAO,EACzC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,aAAa,CAAC,KAAK,CAAC,CAMtB"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,aAAa,EAAyB,aAAa,EAAe,MAAM,YAAY,CAAC;AAkBnG,wBAAgB,WAAW,CAAC,KAAK,GAAG,OAAO,EACzC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,aAAa,CAAC,KAAK,CAAC,CAgCtB"}
package/dist/renderer.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { chromeMinimum } from './chrome-size.js';
1
2
  import { closeRequests } from './close.js';
2
3
  import { dockLayout, parseWorkspace } from './workspace.js';
3
4
  import { validateTheme, themePixels } from './theme.js';
@@ -9,17 +10,41 @@ import { bindCorners } from './corners.js';
9
10
  import { applyTheme } from './theme.js';
10
11
  import { fillTabs } from './tabs.js';
11
12
  import { bindShortcuts } from './shortcuts.js';
12
- import { allocate, bounds, DIVIDER, findNode, groups, paneIds } from 'quilt-core';
13
+ import { createActions } from './actions.js';
14
+ import { registeredShortcuts } from './shortcut-registration.js';
15
+ import { LayoutStore, createLayout, allocate, bounds as modelBounds, DIVIDER, findNode, groups, paneIds, } from 'quilt-core';
13
16
  import { Scope, el, syncChildren } from './lifetime.js';
14
17
  import { mountPane } from './panes.js';
15
18
  import { Windows } from './windows.js';
16
19
  import { createPaneMenu } from './menu.js';
17
20
  let nextMount = 0;
18
21
  export function mountLayout(host, input) {
19
- // State typing is a boundary contract; the renderer only forwards the value.
20
- return mountLayoutInternal(host, input);
22
+ if ('store' in input)
23
+ throw new Error('Quilt owns its store; pass initialLayout or initialWorkspace');
24
+ if (input.initialLayout !== undefined && input.initialWorkspace !== undefined)
25
+ throw new Error('initialLayout and initialWorkspace are mutually exclusive');
26
+ if (input.registry && (input.renderers || input.tabs))
27
+ throw new Error('registry cannot be combined with renderers or tabs');
28
+ const preset = input.initialWorkspace === undefined ? undefined : parseWorkspace(input.initialWorkspace);
29
+ const theme = preset?.theme ?? input.theme ?? {};
30
+ const tabBar = preset?.tabBar ?? input.tabBar ?? {};
31
+ validateTheme(theme);
32
+ validateTabBar(tabBar);
33
+ const store = new LayoutStore(dockLayout(preset?.layout ?? input.initialLayout ?? createLayout()), {
34
+ autoCollapse: preset?.autoCollapse ?? 'disabled',
35
+ });
36
+ // The initialization scope rolls back even if setup fails before returning a handle.
37
+ const scope = new Scope();
38
+ scope.add(() => store.dispose());
39
+ try {
40
+ return mountLayoutInternal(host, { ...input, store, theme, tabBar }, scope);
41
+ }
42
+ catch (error) {
43
+ scope.dispose();
44
+ throw error;
45
+ }
21
46
  }
22
- function mountLayoutInternal(host, input) {
47
+ function mountLayoutInternal(host, input, scope) {
23
48
  const options = { ...input };
24
49
  const configure = () => {
25
50
  if (options.registry) {
@@ -37,7 +62,7 @@ function mountLayoutInternal(host, input) {
37
62
  const doc = host.ownerDocument, win = doc.defaultView;
38
63
  if (!win)
39
64
  throw new Error('Layout host must belong to a live document');
40
- const scope = new Scope(), regions = new Map(), panes = new Map();
65
+ const regions = new Map(), panes = new Map();
41
66
  const root = el(doc, 'div', 'layouts');
42
67
  applyTheme(root, options.theme ?? {});
43
68
  root.setAttribute('aria-label', message(options, 'Pane workspace'));
@@ -46,6 +71,7 @@ function mountLayoutInternal(host, input) {
46
71
  status.setAttribute('role', 'status');
47
72
  root.append(stage, status);
48
73
  host.append(root);
74
+ scope.add(() => root.remove());
49
75
  let disposed = false, rendering = false, again = false, dragScope;
50
76
  let dragId;
51
77
  const error = (e) => {
@@ -65,14 +91,49 @@ function mountLayoutInternal(host, input) {
65
91
  error(e);
66
92
  }
67
93
  };
68
- const requestClose = closeRequests(root, options, scope, error);
94
+ const closeScope = new Scope();
95
+ scope.add(() => closeScope.dispose());
96
+ const requestClose = closeRequests(root, options, closeScope, error);
69
97
  const windows = new Windows(doc, options, error, root);
70
- const menu = createPaneMenu(root, options, windows, render, error, requestClose);
71
- bindShortcuts(root, options, scope, act, (group) => {
72
- const region = regions.get(group.id);
73
- if (region)
74
- menu.addTab(region.header ?? region.element, group);
98
+ scope.add(() => windows.dispose());
99
+ scope.add(() => {
100
+ for (const pane of panes.values())
101
+ pane.dispose();
102
+ panes.clear();
103
+ for (const region of regions.values())
104
+ region.scope.dispose();
105
+ regions.clear();
75
106
  });
107
+ const actions = createActions(options, {
108
+ context: () => shortcutContext(),
109
+ disposed: () => disposed,
110
+ close: requestClose,
111
+ popout: (id) => windows.open(id),
112
+ report: error,
113
+ create(action, group, pane) {
114
+ const region = regions.get(group.id);
115
+ if (!region)
116
+ throw new Error('Region is not mounted');
117
+ const anchor = region.header ?? region.element;
118
+ if (action === 'addTab')
119
+ menu.addTab(anchor, group);
120
+ else {
121
+ const direction = action === 'splitLeft'
122
+ ? 'left'
123
+ : action === 'splitRight'
124
+ ? 'right'
125
+ : action === 'splitUp'
126
+ ? 'top'
127
+ : 'bottom';
128
+ menu.open(anchor, pane, group, direction);
129
+ }
130
+ },
131
+ });
132
+ const menu = createPaneMenu(root, options, render, error, actions);
133
+ scope.add(() => menu.dispose());
134
+ scope.add(() => dragScope?.dispose());
135
+ const shortcutContext = bindShortcuts(root, options, scope, actions.canExecuteAction, actions.executeAction, menu.getShortcutTarget);
136
+ registeredShortcuts(options);
76
137
  const renderOptions = { ...options, onError: error };
77
138
  function button(text, title, action) {
78
139
  const b = el(doc, 'button', 'layouts-button', text);
@@ -101,7 +162,7 @@ function mountLayoutInternal(host, input) {
101
162
  r.header = el(doc, 'header', 'layouts-header');
102
163
  r.body = el(doc, 'div', 'layouts-body');
103
164
  r.element.append(r.header, r.body);
104
- r.updateTabBar = bindTabBar(r.element, r.header, root, r.scope, () => {
165
+ r.tabStyle = () => {
105
166
  const current = findNode(options.store.getSnapshot().root, node.id);
106
167
  return {
107
168
  ...tabBar,
@@ -113,8 +174,9 @@ function mountLayoutInternal(host, input) {
113
174
  ? { placement: current.tabPlacement }
114
175
  : {}),
115
176
  };
116
- }, () => options.messages ?? {});
117
- bindCorners(r.element, node.id, options, r.scope, (pane, group, direction, ratio) => menu.open(r.header, pane, group, direction, ratio), error);
177
+ };
178
+ r.updateTabBar = bindTabBar(r.element, r.header, root, r.scope, r.tabStyle, () => options.messages ?? {});
179
+ bindCorners(r.element, node.id, options, r.scope, (pane, group, direction, ratio) => menu.open(r.header, pane, group, direction, ratio), error, bounds);
118
180
  r.element.setAttribute('aria-label', message(options, 'Pane region'));
119
181
  r.scope.listen(r.element, 'dragover', (event) => {
120
182
  if (!dragId)
@@ -169,7 +231,7 @@ function mountLayoutInternal(host, input) {
169
231
  const resize = isolatedResize(geometryLayout(), node.id, (child, axis) => {
170
232
  const rect = regions.get(child.id).element.getBoundingClientRect();
171
233
  return axis === 'horizontal' ? rect.width : rect.height;
172
- });
234
+ }, bounds);
173
235
  r.divider.setPointerCapture(e.pointerId);
174
236
  root.classList.add('layouts-resizing');
175
237
  drag.add(() => {
@@ -212,7 +274,7 @@ function mountLayoutInternal(host, input) {
212
274
  const resize = isolatedResize(geometryLayout(), node.id, (child, axis) => {
213
275
  const rect = regions.get(child.id).element.getBoundingClientRect();
214
276
  return axis === 'horizontal' ? rect.width : rect.height;
215
- });
277
+ }, bounds);
216
278
  act(() => options.store.resizeMany(resize.ratios(Math.max(0.001, Math.min(0.999, n.ratio + (e.key === keys[0] ? -1 : 1) * (e.shiftKey ? 0.1 : 0.02)))), { source: 'user' }));
217
279
  }
218
280
  });
@@ -445,9 +507,30 @@ function mountLayoutInternal(host, input) {
445
507
  return node.axis === 'horizontal' ? b.minWidth < b.maxWidth : b.minHeight < b.maxHeight;
446
508
  });
447
509
  }
510
+ const chromeSizes = new Map();
511
+ function bounds(node, layout) {
512
+ return modelBounds(node, layout, (group, constraints) => {
513
+ const chrome = chromeSizes.get(group.id);
514
+ if (!chrome)
515
+ return constraints;
516
+ const minWidth = Math.max(constraints.minWidth, chrome.minWidth);
517
+ const minHeight = Math.max(constraints.minHeight, chrome.minHeight);
518
+ return {
519
+ minWidth,
520
+ minHeight,
521
+ maxWidth: Math.max(constraints.maxWidth, minWidth),
522
+ maxHeight: Math.max(constraints.maxHeight, minHeight),
523
+ };
524
+ });
525
+ }
448
526
  // Renderer-only gaps participate in bounds/allocation without modifying Layout JSON.
449
527
  function geometryLayout() {
450
528
  const layout = options.store.getSnapshot();
529
+ chromeSizes.clear();
530
+ for (const [id, region] of regions) {
531
+ if (region.header && region.tabStyle && region.element.isConnected)
532
+ chromeSizes.set(id, chromeMinimum(region.element, region.header, region.tabStyle()));
533
+ }
451
534
  const pixels = (property, fallback) => themePixels(root, property, fallback);
452
535
  const width = pixels('--layouts-resize-handle-width', DIVIDER);
453
536
  const disabledWidth = pixels('--layouts-disabled-resize-handle-width', 0);
@@ -537,9 +620,23 @@ function mountLayoutInternal(host, input) {
537
620
  }
538
621
  render();
539
622
  }));
540
- const observer = new ResizeObserver(measure);
623
+ // Geometry can change the stage's scrollbar gutter. Defer those writes out
624
+ // of ResizeObserver delivery so WebKit does not detect a nested resize loop.
625
+ let measureFrame = 0;
626
+ const observer = new ResizeObserver(() => {
627
+ if (!measureFrame && !disposed)
628
+ measureFrame = doc.defaultView.requestAnimationFrame(() => {
629
+ measureFrame = 0;
630
+ if (!disposed)
631
+ measure();
632
+ });
633
+ });
541
634
  observer.observe(stage);
542
- scope.add(() => observer.disconnect());
635
+ scope.add(() => {
636
+ observer.disconnect();
637
+ if (measureFrame)
638
+ doc.defaultView.cancelAnimationFrame(measureFrame);
639
+ });
543
640
  scope.listen(doc, 'dragend', () => {
544
641
  dragId = undefined;
545
642
  for (const r of regions.values())
@@ -587,16 +684,27 @@ function mountLayoutInternal(host, input) {
587
684
  themeObserver.observe(ancestor, { attributes: true });
588
685
  themeObserver.observe(doc.head, { childList: true, subtree: true, characterData: true });
589
686
  scope.add(() => themeObserver.disconnect());
687
+ // Cancel asynchronous actions before tearing down views and the owned store.
688
+ scope.add(() => {
689
+ closeScope.dispose();
690
+ windows.dispose();
691
+ });
590
692
  render();
591
693
  return {
694
+ store: options.store,
695
+ ...actions,
696
+ getShortcuts: () => registeredShortcuts(options),
592
697
  requestClose,
593
698
  refreshTheme,
594
699
  updateOptions(next) {
595
700
  if (disposed)
596
701
  return;
702
+ if ('store' in next || 'initialLayout' in next || 'initialWorkspace' in next)
703
+ throw new Error('Initial configuration cannot be updated; use loadWorkspace or store commands');
597
704
  const candidate = { ...options, ...next };
598
705
  if (candidate.registry && (next.renderers || next.tabs))
599
706
  throw new Error('registry cannot be combined with renderers or tabs');
707
+ registeredShortcuts(candidate);
600
708
  validateTheme(candidate.theme ?? {});
601
709
  validateTabBar(candidate.tabBar ?? {});
602
710
  const sameRegistrations = candidate.registry === options.registry ||
@@ -618,8 +726,7 @@ function mountLayoutInternal(host, input) {
618
726
  (!candidate.registry && candidate.tabs !== options.tabs) ||
619
727
  candidate.renderIcon !== options.renderIcon ||
620
728
  candidate.popouts !== options.popouts ||
621
- JSON.stringify(candidate.messages) !== JSON.stringify(options.messages) ||
622
- JSON.stringify(candidate.shortcuts) !== JSON.stringify(options.shortcuts);
729
+ JSON.stringify(candidate.messages) !== JSON.stringify(options.messages);
623
730
  // Clear maps derived from the old registry when returning to low-level registration.
624
731
  if ('registry' in next && !next.registry && options.registry) {
625
732
  Object.assign(options, { renderers: undefined, tabs: undefined });
@@ -631,6 +738,7 @@ function mountLayoutInternal(host, input) {
631
738
  tabBar = structuredClone(options.tabBar ?? {});
632
739
  bindRegistry();
633
740
  refreshOptions(repaintChrome);
741
+ menu.refreshShortcuts();
634
742
  refreshTheme();
635
743
  },
636
744
  exportWorkspace() {
@@ -691,23 +799,7 @@ function mountLayoutInternal(host, input) {
691
799
  if (disposed)
692
800
  return;
693
801
  disposed = true;
694
- menu.dispose();
695
- dragScope?.dispose();
696
802
  scope.dispose();
697
- windows.dispose();
698
- for (const p of panes.values()) {
699
- try {
700
- p.dispose();
701
- }
702
- catch (e) {
703
- error(e);
704
- }
705
- }
706
- for (const r of regions.values())
707
- r.scope.dispose();
708
- panes.clear();
709
- regions.clear();
710
- root.remove();
711
803
  },
712
804
  };
713
805
  }