quilt-vanilla 0.2.3 → 0.3.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 (50) hide show
  1. package/README.md +36 -40
  2. package/dist/chrome-size.d.ts +7 -0
  3. package/dist/chrome-size.d.ts.map +1 -0
  4. package/dist/chrome-size.js +86 -0
  5. package/dist/chrome-size.js.map +1 -0
  6. package/dist/close.d.ts.map +1 -1
  7. package/dist/close.js +3 -1
  8. package/dist/close.js.map +1 -1
  9. package/dist/corners.d.ts +2 -1
  10. package/dist/corners.d.ts.map +1 -1
  11. package/dist/corners.js +2 -2
  12. package/dist/corners.js.map +1 -1
  13. package/dist/index.d.ts +5 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/panes.d.ts.map +1 -1
  18. package/dist/panes.js +84 -52
  19. package/dist/panes.js.map +1 -1
  20. package/dist/registry.d.ts +1 -0
  21. package/dist/registry.d.ts.map +1 -1
  22. package/dist/registry.js +4 -3
  23. package/dist/registry.js.map +1 -1
  24. package/dist/renderer.d.ts.map +1 -1
  25. package/dist/renderer.js +94 -29
  26. package/dist/renderer.js.map +1 -1
  27. package/dist/resize.d.ts +2 -1
  28. package/dist/resize.d.ts.map +1 -1
  29. package/dist/resize.js +2 -2
  30. package/dist/resize.js.map +1 -1
  31. package/dist/styles.css +49 -0
  32. package/dist/types.d.ts +29 -8
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/types.js.map +1 -1
  35. package/dist/workspace-api.d.ts +180 -0
  36. package/dist/workspace-api.d.ts.map +1 -0
  37. package/dist/workspace-api.js +395 -0
  38. package/dist/workspace-api.js.map +1 -0
  39. package/package.json +2 -2
  40. package/src/chrome-size.ts +99 -0
  41. package/src/close.ts +6 -1
  42. package/src/corners.ts +2 -1
  43. package/src/index.ts +25 -4
  44. package/src/panes.ts +82 -55
  45. package/src/registry.ts +6 -4
  46. package/src/renderer.ts +141 -48
  47. package/src/resize.ts +2 -1
  48. package/src/styles.css +49 -0
  49. package/src/types.ts +24 -9
  50. package/src/workspace-api.ts +561 -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":"AAuBA,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,39 @@ 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 { LayoutStore, createLayout, allocate, bounds as modelBounds, DIVIDER, findNode, groups, paneIds, } from 'quilt-core';
13
14
  import { Scope, el, syncChildren } from './lifetime.js';
14
15
  import { mountPane } from './panes.js';
15
16
  import { Windows } from './windows.js';
16
17
  import { createPaneMenu } from './menu.js';
17
18
  let nextMount = 0;
18
19
  export function mountLayout(host, input) {
19
- // State typing is a boundary contract; the renderer only forwards the value.
20
- return mountLayoutInternal(host, input);
20
+ if ('store' in input)
21
+ throw new Error('Quilt owns its store; pass initialLayout or initialWorkspace');
22
+ if (input.initialLayout !== undefined && input.initialWorkspace !== undefined)
23
+ throw new Error('initialLayout and initialWorkspace are mutually exclusive');
24
+ if (input.registry && (input.renderers || input.tabs))
25
+ throw new Error('registry cannot be combined with renderers or tabs');
26
+ const preset = input.initialWorkspace === undefined ? undefined : parseWorkspace(input.initialWorkspace);
27
+ const theme = preset?.theme ?? input.theme ?? {};
28
+ const tabBar = preset?.tabBar ?? input.tabBar ?? {};
29
+ validateTheme(theme);
30
+ validateTabBar(tabBar);
31
+ const store = new LayoutStore(dockLayout(preset?.layout ?? input.initialLayout ?? createLayout()), {
32
+ autoCollapse: preset?.autoCollapse ?? 'disabled',
33
+ });
34
+ // The initialization scope rolls back even if setup fails before returning a handle.
35
+ const scope = new Scope();
36
+ scope.add(() => store.dispose());
37
+ try {
38
+ return mountLayoutInternal(host, { ...input, store, theme, tabBar }, scope);
39
+ }
40
+ catch (error) {
41
+ scope.dispose();
42
+ throw error;
43
+ }
21
44
  }
22
- function mountLayoutInternal(host, input) {
45
+ function mountLayoutInternal(host, input, scope) {
23
46
  const options = { ...input };
24
47
  const configure = () => {
25
48
  if (options.registry) {
@@ -37,7 +60,7 @@ function mountLayoutInternal(host, input) {
37
60
  const doc = host.ownerDocument, win = doc.defaultView;
38
61
  if (!win)
39
62
  throw new Error('Layout host must belong to a live document');
40
- const scope = new Scope(), regions = new Map(), panes = new Map();
63
+ const regions = new Map(), panes = new Map();
41
64
  const root = el(doc, 'div', 'layouts');
42
65
  applyTheme(root, options.theme ?? {});
43
66
  root.setAttribute('aria-label', message(options, 'Pane workspace'));
@@ -46,6 +69,7 @@ function mountLayoutInternal(host, input) {
46
69
  status.setAttribute('role', 'status');
47
70
  root.append(stage, status);
48
71
  host.append(root);
72
+ scope.add(() => root.remove());
49
73
  let disposed = false, rendering = false, again = false, dragScope;
50
74
  let dragId;
51
75
  const error = (e) => {
@@ -65,9 +89,22 @@ function mountLayoutInternal(host, input) {
65
89
  error(e);
66
90
  }
67
91
  };
68
- const requestClose = closeRequests(root, options, scope, error);
92
+ const closeScope = new Scope();
93
+ scope.add(() => closeScope.dispose());
94
+ const requestClose = closeRequests(root, options, closeScope, error);
69
95
  const windows = new Windows(doc, options, error, root);
96
+ scope.add(() => windows.dispose());
97
+ scope.add(() => {
98
+ for (const pane of panes.values())
99
+ pane.dispose();
100
+ panes.clear();
101
+ for (const region of regions.values())
102
+ region.scope.dispose();
103
+ regions.clear();
104
+ });
70
105
  const menu = createPaneMenu(root, options, windows, render, error, requestClose);
106
+ scope.add(() => menu.dispose());
107
+ scope.add(() => dragScope?.dispose());
71
108
  bindShortcuts(root, options, scope, act, (group) => {
72
109
  const region = regions.get(group.id);
73
110
  if (region)
@@ -101,7 +138,7 @@ function mountLayoutInternal(host, input) {
101
138
  r.header = el(doc, 'header', 'layouts-header');
102
139
  r.body = el(doc, 'div', 'layouts-body');
103
140
  r.element.append(r.header, r.body);
104
- r.updateTabBar = bindTabBar(r.element, r.header, root, r.scope, () => {
141
+ r.tabStyle = () => {
105
142
  const current = findNode(options.store.getSnapshot().root, node.id);
106
143
  return {
107
144
  ...tabBar,
@@ -113,8 +150,9 @@ function mountLayoutInternal(host, input) {
113
150
  ? { placement: current.tabPlacement }
114
151
  : {}),
115
152
  };
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);
153
+ };
154
+ r.updateTabBar = bindTabBar(r.element, r.header, root, r.scope, r.tabStyle, () => options.messages ?? {});
155
+ bindCorners(r.element, node.id, options, r.scope, (pane, group, direction, ratio) => menu.open(r.header, pane, group, direction, ratio), error, bounds);
118
156
  r.element.setAttribute('aria-label', message(options, 'Pane region'));
119
157
  r.scope.listen(r.element, 'dragover', (event) => {
120
158
  if (!dragId)
@@ -169,7 +207,7 @@ function mountLayoutInternal(host, input) {
169
207
  const resize = isolatedResize(geometryLayout(), node.id, (child, axis) => {
170
208
  const rect = regions.get(child.id).element.getBoundingClientRect();
171
209
  return axis === 'horizontal' ? rect.width : rect.height;
172
- });
210
+ }, bounds);
173
211
  r.divider.setPointerCapture(e.pointerId);
174
212
  root.classList.add('layouts-resizing');
175
213
  drag.add(() => {
@@ -212,7 +250,7 @@ function mountLayoutInternal(host, input) {
212
250
  const resize = isolatedResize(geometryLayout(), node.id, (child, axis) => {
213
251
  const rect = regions.get(child.id).element.getBoundingClientRect();
214
252
  return axis === 'horizontal' ? rect.width : rect.height;
215
- });
253
+ }, bounds);
216
254
  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
255
  }
218
256
  });
@@ -445,9 +483,30 @@ function mountLayoutInternal(host, input) {
445
483
  return node.axis === 'horizontal' ? b.minWidth < b.maxWidth : b.minHeight < b.maxHeight;
446
484
  });
447
485
  }
486
+ const chromeSizes = new Map();
487
+ function bounds(node, layout) {
488
+ return modelBounds(node, layout, (group, constraints) => {
489
+ const chrome = chromeSizes.get(group.id);
490
+ if (!chrome)
491
+ return constraints;
492
+ const minWidth = Math.max(constraints.minWidth, chrome.minWidth);
493
+ const minHeight = Math.max(constraints.minHeight, chrome.minHeight);
494
+ return {
495
+ minWidth,
496
+ minHeight,
497
+ maxWidth: Math.max(constraints.maxWidth, minWidth),
498
+ maxHeight: Math.max(constraints.maxHeight, minHeight),
499
+ };
500
+ });
501
+ }
448
502
  // Renderer-only gaps participate in bounds/allocation without modifying Layout JSON.
449
503
  function geometryLayout() {
450
504
  const layout = options.store.getSnapshot();
505
+ chromeSizes.clear();
506
+ for (const [id, region] of regions) {
507
+ if (region.header && region.tabStyle && region.element.isConnected)
508
+ chromeSizes.set(id, chromeMinimum(region.element, region.header, region.tabStyle()));
509
+ }
451
510
  const pixels = (property, fallback) => themePixels(root, property, fallback);
452
511
  const width = pixels('--layouts-resize-handle-width', DIVIDER);
453
512
  const disabledWidth = pixels('--layouts-disabled-resize-handle-width', 0);
@@ -537,9 +596,23 @@ function mountLayoutInternal(host, input) {
537
596
  }
538
597
  render();
539
598
  }));
540
- const observer = new ResizeObserver(measure);
599
+ // Geometry can change the stage's scrollbar gutter. Defer those writes out
600
+ // of ResizeObserver delivery so WebKit does not detect a nested resize loop.
601
+ let measureFrame = 0;
602
+ const observer = new ResizeObserver(() => {
603
+ if (!measureFrame && !disposed)
604
+ measureFrame = doc.defaultView.requestAnimationFrame(() => {
605
+ measureFrame = 0;
606
+ if (!disposed)
607
+ measure();
608
+ });
609
+ });
541
610
  observer.observe(stage);
542
- scope.add(() => observer.disconnect());
611
+ scope.add(() => {
612
+ observer.disconnect();
613
+ if (measureFrame)
614
+ doc.defaultView.cancelAnimationFrame(measureFrame);
615
+ });
543
616
  scope.listen(doc, 'dragend', () => {
544
617
  dragId = undefined;
545
618
  for (const r of regions.values())
@@ -587,13 +660,21 @@ function mountLayoutInternal(host, input) {
587
660
  themeObserver.observe(ancestor, { attributes: true });
588
661
  themeObserver.observe(doc.head, { childList: true, subtree: true, characterData: true });
589
662
  scope.add(() => themeObserver.disconnect());
663
+ // Cancel asynchronous actions before tearing down views and the owned store.
664
+ scope.add(() => {
665
+ closeScope.dispose();
666
+ windows.dispose();
667
+ });
590
668
  render();
591
669
  return {
670
+ store: options.store,
592
671
  requestClose,
593
672
  refreshTheme,
594
673
  updateOptions(next) {
595
674
  if (disposed)
596
675
  return;
676
+ if ('store' in next || 'initialLayout' in next || 'initialWorkspace' in next)
677
+ throw new Error('Initial configuration cannot be updated; use loadWorkspace or store commands');
597
678
  const candidate = { ...options, ...next };
598
679
  if (candidate.registry && (next.renderers || next.tabs))
599
680
  throw new Error('registry cannot be combined with renderers or tabs');
@@ -691,23 +772,7 @@ function mountLayoutInternal(host, input) {
691
772
  if (disposed)
692
773
  return;
693
774
  disposed = true;
694
- menu.dispose();
695
- dragScope?.dispose();
696
775
  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
776
  },
712
777
  };
713
778
  }