quilt-vanilla 0.1.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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/action-icons.d.ts +18 -0
  4. package/dist/action-icons.d.ts.map +1 -0
  5. package/dist/action-icons.js +36 -0
  6. package/dist/action-icons.js.map +1 -0
  7. package/dist/chrome-icons.d.ts +3 -0
  8. package/dist/chrome-icons.d.ts.map +1 -0
  9. package/dist/chrome-icons.js +32 -0
  10. package/dist/chrome-icons.js.map +1 -0
  11. package/dist/corners.d.ts +8 -0
  12. package/dist/corners.d.ts.map +1 -0
  13. package/dist/corners.js +205 -0
  14. package/dist/corners.js.map +1 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +5 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/lifetime.d.ts +10 -0
  20. package/dist/lifetime.d.ts.map +1 -0
  21. package/dist/lifetime.js +44 -0
  22. package/dist/lifetime.js.map +1 -0
  23. package/dist/menu.d.ts +10 -0
  24. package/dist/menu.d.ts.map +1 -0
  25. package/dist/menu.js +356 -0
  26. package/dist/menu.js.map +1 -0
  27. package/dist/panes.d.ts +13 -0
  28. package/dist/panes.d.ts.map +1 -0
  29. package/dist/panes.js +58 -0
  30. package/dist/panes.js.map +1 -0
  31. package/dist/picker.d.ts +6 -0
  32. package/dist/picker.d.ts.map +1 -0
  33. package/dist/picker.js +112 -0
  34. package/dist/picker.js.map +1 -0
  35. package/dist/registry.d.ts +25 -0
  36. package/dist/registry.d.ts.map +1 -0
  37. package/dist/registry.js +39 -0
  38. package/dist/registry.js.map +1 -0
  39. package/dist/renderer.d.ts +3 -0
  40. package/dist/renderer.d.ts.map +1 -0
  41. package/dist/renderer.js +570 -0
  42. package/dist/renderer.js.map +1 -0
  43. package/dist/resize.d.ts +7 -0
  44. package/dist/resize.d.ts.map +1 -0
  45. package/dist/resize.js +56 -0
  46. package/dist/resize.js.map +1 -0
  47. package/dist/shortcuts.d.ts +6 -0
  48. package/dist/shortcuts.d.ts.map +1 -0
  49. package/dist/shortcuts.js +64 -0
  50. package/dist/shortcuts.js.map +1 -0
  51. package/dist/styles.css +842 -0
  52. package/dist/tab-bar.d.ts +6 -0
  53. package/dist/tab-bar.d.ts.map +1 -0
  54. package/dist/tab-bar.js +336 -0
  55. package/dist/tab-bar.js.map +1 -0
  56. package/dist/tab-tooltip.d.ts +4 -0
  57. package/dist/tab-tooltip.d.ts.map +1 -0
  58. package/dist/tab-tooltip.js +95 -0
  59. package/dist/tab-tooltip.js.map +1 -0
  60. package/dist/tabs.d.ts +15 -0
  61. package/dist/tabs.d.ts.map +1 -0
  62. package/dist/tabs.js +126 -0
  63. package/dist/tabs.js.map +1 -0
  64. package/dist/theme.d.ts +206 -0
  65. package/dist/theme.d.ts.map +1 -0
  66. package/dist/theme.js +166 -0
  67. package/dist/theme.js.map +1 -0
  68. package/dist/types.d.ts +66 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +2 -0
  71. package/dist/types.js.map +1 -0
  72. package/dist/windows.d.ts +23 -0
  73. package/dist/windows.d.ts.map +1 -0
  74. package/dist/windows.js +247 -0
  75. package/dist/windows.js.map +1 -0
  76. package/package.json +50 -0
  77. package/src/action-icons.ts +40 -0
  78. package/src/chrome-icons.ts +30 -0
  79. package/src/corners.ts +259 -0
  80. package/src/index.ts +27 -0
  81. package/src/lifetime.ts +46 -0
  82. package/src/menu.ts +432 -0
  83. package/src/panes.ts +76 -0
  84. package/src/picker.ts +114 -0
  85. package/src/registry.ts +46 -0
  86. package/src/renderer.ts +654 -0
  87. package/src/resize.ts +65 -0
  88. package/src/shortcuts.ts +90 -0
  89. package/src/styles.css +842 -0
  90. package/src/tab-bar.ts +382 -0
  91. package/src/tab-tooltip.ts +96 -0
  92. package/src/tabs.ts +129 -0
  93. package/src/theme.ts +211 -0
  94. package/src/types.ts +67 -0
  95. package/src/windows.ts +244 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAQ,eAAe,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD,uFAAuF;AACvF,qBAAa,OAAO;IAMhB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,SAAS;IARnB,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,QAAQ,CAAC,OAAO,+BAAsC;gBAE5C,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EAC3B,SAAS,EAAE,WAAW;IAWhC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,SAAS;IAiCjB,OAAO,CAAC,MAAM;IAoCd,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,GAAE,eAAoB,GAAG,OAAO;IAgF1D,UAAU,CAAC,EAAE,EAAE,MAAM;IAQrB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,KAAK;IAUb,OAAO;CAoBR"}
@@ -0,0 +1,247 @@
1
+ import { themeProperties } from './theme.js';
2
+ import { el, Scope } from './lifetime.js';
3
+ import { mountPane } from './panes.js';
4
+ /** The opener owns all state; child documents never load an independent app bundle. */
5
+ export class Windows {
6
+ doc;
7
+ options;
8
+ error;
9
+ themeRoot;
10
+ companions = new Map();
11
+ scope = new Scope();
12
+ stopped = false;
13
+ pending = new Map();
14
+ constructor(doc, options, error, themeRoot) {
15
+ this.doc = doc;
16
+ this.options = options;
17
+ this.error = error;
18
+ this.themeRoot = themeRoot;
19
+ const interval = setInterval(() => this.check(), 300);
20
+ this.scope.add(() => clearInterval(interval));
21
+ this.scope.listen(doc.defaultView, 'pagehide', () => this.dispose());
22
+ for (const p of options.store.getSnapshot().popouts)
23
+ this.pending.set(p.paneId, p.placement ?? {});
24
+ // Loading data never opens a browser window. Restore missing windows into the main layout.
25
+ this.restoreMissing();
26
+ this.scope.add(options.store.subscribe(() => this.reconcile()));
27
+ }
28
+ restoreMissing() {
29
+ for (const entry of [...this.options.store.getSnapshot().popouts]) {
30
+ if (!this.companions.has(entry.paneId)) {
31
+ this.pending.set(entry.paneId, entry.placement ?? {});
32
+ this.options.store.returnPane(entry.paneId);
33
+ }
34
+ }
35
+ }
36
+ reconcile() {
37
+ if (this.stopped)
38
+ return;
39
+ for (const [id, c] of this.companions)
40
+ if (!this.options.store.getSnapshot().popouts.some((p) => p.paneId === id))
41
+ this.closeCompanion(id, c);
42
+ for (const [id, c] of this.companions) {
43
+ const pane = this.options.store.getSnapshot().panes[id];
44
+ if (!pane || JSON.stringify(pane) === c.mounted.signature)
45
+ continue;
46
+ try {
47
+ if (pane.type !== c.mounted.pane.type ||
48
+ JSON.stringify(pane.params) !== JSON.stringify(c.mounted.pane.params)) {
49
+ const replacement = mountPane(c.window.document, pane, 'popout', this.options);
50
+ c.mounted.element.replaceWith(replacement.element);
51
+ c.mounted.dispose();
52
+ c.mounted = replacement;
53
+ }
54
+ else {
55
+ c.mounted.view.update?.(pane);
56
+ c.mounted.pane = pane;
57
+ c.mounted.signature = JSON.stringify(pane);
58
+ }
59
+ c.window.document.title = pane.title + ' — layouts';
60
+ const title = c.window.document.querySelector('.layouts-companion-bar strong');
61
+ if (title)
62
+ title.textContent = pane.title;
63
+ }
64
+ catch (error) {
65
+ this.error(error);
66
+ }
67
+ }
68
+ this.restoreMissing();
69
+ for (const id of this.pending.keys())
70
+ if (!this.options.store.getSnapshot().panes[id])
71
+ this.pending.delete(id);
72
+ }
73
+ styles(target, pane, scope) {
74
+ const source = this.doc, dest = target.document;
75
+ dest.title = pane.title + ' — layouts';
76
+ const base = dest.createElement('base');
77
+ base.href = source.baseURI;
78
+ dest.head.append(base);
79
+ const holder = dest.createElement('meta');
80
+ holder.dataset.layoutsStyles = '';
81
+ dest.head.append(holder);
82
+ let copied = [];
83
+ const copy = () => {
84
+ copied.forEach((n) => n.remove());
85
+ copied = Array.from(source.head.querySelectorAll('link[rel="stylesheet"], style')).map((n) => n.cloneNode(true));
86
+ copied.forEach((n) => dest.head.append(n));
87
+ dest.documentElement.className = source.documentElement.className;
88
+ dest.body.className = source.body.className;
89
+ for (const original of [source.documentElement, source.body]) {
90
+ const next = original === source.body ? dest.body : dest.documentElement;
91
+ for (const attribute of Array.from(next.attributes))
92
+ if (attribute.name === 'style' || attribute.name.startsWith('data-'))
93
+ next.removeAttribute(attribute.name);
94
+ for (const attribute of Array.from(original.attributes))
95
+ if (attribute.name === 'style' || attribute.name.startsWith('data-'))
96
+ next.setAttribute(attribute.name, attribute.value);
97
+ }
98
+ };
99
+ copy();
100
+ const observer = new MutationObserver(copy);
101
+ observer.observe(source.head, { childList: true, subtree: true, characterData: true });
102
+ observer.observe(source.body, { attributes: true });
103
+ observer.observe(source.documentElement, { attributes: true });
104
+ scope.add(() => observer.disconnect());
105
+ }
106
+ open(id, placement = {}) {
107
+ if (this.stopped)
108
+ return false;
109
+ const existing = this.companions.get(id);
110
+ if (existing) {
111
+ existing.window.focus();
112
+ return true;
113
+ }
114
+ const pane = this.options.store.getSnapshot().panes[id];
115
+ if (!pane || !this.options.store.can(id, 'popout'))
116
+ return false;
117
+ let child = null, mounted;
118
+ const scope = new Scope();
119
+ try {
120
+ const requested = { width: 640, height: 480, ...placement };
121
+ const features = [
122
+ 'popup=yes',
123
+ ...Object.entries(requested).map(([key, value]) => `${key}=${Math.round(value)}`),
124
+ ].join(',');
125
+ child = this.options.openWindow
126
+ ? this.options.openWindow(pane, requested)
127
+ : this.doc.defaultView.open('about:blank', '', features);
128
+ if (!child)
129
+ throw new Error('The browser blocked the popout. Allow popups or try again from the pane menu.');
130
+ const dest = child.document;
131
+ dest.body.replaceChildren();
132
+ this.styles(child, pane, scope);
133
+ const shell = el(dest, 'div', 'layouts layouts-companion');
134
+ const bar = el(dest, 'header', 'layouts-companion-bar');
135
+ const button = el(dest, 'button', 'layouts-button', 'Return to layout');
136
+ button.type = 'button';
137
+ bar.append(el(dest, 'strong', '', pane.title), button);
138
+ shell.append(bar);
139
+ dest.body.append(shell);
140
+ const syncTheme = () => {
141
+ const computed = this.doc.defaultView.getComputedStyle(this.themeRoot);
142
+ for (const property of Object.values(themeProperties)) {
143
+ const value = computed.getPropertyValue(property);
144
+ if (value.trim())
145
+ shell.style.setProperty(property, value);
146
+ else
147
+ shell.style.removeProperty(property);
148
+ }
149
+ };
150
+ syncTheme();
151
+ const themeObserver = new MutationObserver(syncTheme);
152
+ for (let node = this.themeRoot; node; node = node.parentElement)
153
+ themeObserver.observe(node, { attributes: true });
154
+ themeObserver.observe(this.doc.head, { childList: true, subtree: true, characterData: true });
155
+ scope.add(() => themeObserver.disconnect());
156
+ this.options.prepareWindow?.(child, pane);
157
+ mounted = mountPane(dest, pane, 'popout', this.options);
158
+ shell.append(mounted.element);
159
+ const c = { window: child, mounted, scope, suppressReturn: false };
160
+ this.companions.set(id, c);
161
+ scope.listen(button, 'click', () => this.returnPane(id));
162
+ // Leave detection supplements closed-window polling; neither is a persistence guarantee.
163
+ scope.listen(child, 'pagehide', () => {
164
+ if (!c.suppressReturn && !this.stopped)
165
+ queueMicrotask(() => this.returnPane(id));
166
+ });
167
+ this.options.store.popout(id, requested, { source: 'user' });
168
+ this.pending.delete(id);
169
+ child.focus();
170
+ return true;
171
+ }
172
+ catch (error) {
173
+ this.companions.delete(id);
174
+ scope.dispose();
175
+ try {
176
+ mounted?.dispose();
177
+ }
178
+ catch {
179
+ /* Finish rollback. */
180
+ }
181
+ try {
182
+ child?.close();
183
+ }
184
+ catch {
185
+ /* Browser may already have closed it. */
186
+ }
187
+ this.error(error);
188
+ return false;
189
+ }
190
+ }
191
+ returnPane(id) {
192
+ if (this.stopped)
193
+ return;
194
+ try {
195
+ this.options.store.returnPane(id);
196
+ }
197
+ catch (error) {
198
+ this.error(error);
199
+ }
200
+ }
201
+ closeCompanion(id, c) {
202
+ c.suppressReturn = true;
203
+ this.companions.delete(id);
204
+ c.scope.dispose();
205
+ try {
206
+ c.mounted.dispose();
207
+ }
208
+ catch (error) {
209
+ this.error(error);
210
+ }
211
+ try {
212
+ c.window.close();
213
+ }
214
+ catch {
215
+ /* A user may have navigated the companion away. */
216
+ }
217
+ }
218
+ check() {
219
+ if (this.stopped)
220
+ return;
221
+ for (const [id, c] of this.companions) {
222
+ try {
223
+ if (c.window.closed || c.window.document.defaultView === null)
224
+ this.returnPane(id);
225
+ }
226
+ catch {
227
+ this.returnPane(id);
228
+ }
229
+ }
230
+ }
231
+ dispose() {
232
+ if (this.stopped)
233
+ return;
234
+ this.stopped = true;
235
+ this.scope.dispose();
236
+ for (const [id, c] of this.companions) {
237
+ try {
238
+ c.window.document.body.replaceChildren(el(c.window.document, 'p', 'layouts-placeholder', 'Disconnected: the main layout session has ended.'));
239
+ }
240
+ catch {
241
+ /* Inaccessible or already closed. */
242
+ }
243
+ this.closeCompanion(id, c);
244
+ }
245
+ }
246
+ }
247
+ //# sourceMappingURL=windows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows.js","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"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","sourcesContent":["import { themeProperties } from './theme.js';\nimport type { Pane, WindowPlacement } from 'quilt-core';\nimport type { LayoutOptions } from './types.js';\nimport { el, Scope } from './lifetime.js';\nimport { mountPane } from './panes.js';\nimport type { MountedPane } from './panes.js';\ninterface Companion {\n window: Window;\n mounted: MountedPane;\n scope: Scope;\n suppressReturn: boolean;\n}\n/** The opener owns all state; child documents never load an independent app bundle. */\nexport class Windows {\n private companions = new Map<string, Companion>();\n private scope = new Scope();\n private stopped = false;\n readonly pending = new Map<string, WindowPlacement>();\n constructor(\n private doc: Document,\n private options: LayoutOptions,\n private error: (e: unknown) => void,\n private themeRoot: HTMLElement,\n ) {\n const interval = setInterval(() => this.check(), 300);\n this.scope.add(() => clearInterval(interval));\n this.scope.listen(doc.defaultView!, 'pagehide', () => this.dispose());\n for (const p of options.store.getSnapshot().popouts)\n this.pending.set(p.paneId, p.placement ?? {});\n // Loading data never opens a browser window. Restore missing windows into the main layout.\n this.restoreMissing();\n this.scope.add(options.store.subscribe(() => this.reconcile()));\n }\n private restoreMissing() {\n for (const entry of [...this.options.store.getSnapshot().popouts]) {\n if (!this.companions.has(entry.paneId)) {\n this.pending.set(entry.paneId, entry.placement ?? {});\n this.options.store.returnPane(entry.paneId);\n }\n }\n }\n private reconcile() {\n if (this.stopped) return;\n for (const [id, c] of this.companions)\n if (!this.options.store.getSnapshot().popouts.some((p) => p.paneId === id))\n this.closeCompanion(id, c);\n for (const [id, c] of this.companions) {\n const pane = this.options.store.getSnapshot().panes[id];\n if (!pane || JSON.stringify(pane) === c.mounted.signature) continue;\n try {\n if (\n pane.type !== c.mounted.pane.type ||\n JSON.stringify(pane.params) !== JSON.stringify(c.mounted.pane.params)\n ) {\n const replacement = mountPane(c.window.document, pane, 'popout', this.options);\n c.mounted.element.replaceWith(replacement.element);\n c.mounted.dispose();\n c.mounted = replacement;\n } else {\n c.mounted.view.update?.(pane);\n c.mounted.pane = pane;\n c.mounted.signature = JSON.stringify(pane);\n }\n c.window.document.title = pane.title + ' — layouts';\n const title = c.window.document.querySelector('.layouts-companion-bar strong');\n if (title) title.textContent = pane.title;\n } catch (error) {\n this.error(error);\n }\n }\n this.restoreMissing();\n for (const id of this.pending.keys())\n if (!this.options.store.getSnapshot().panes[id]) this.pending.delete(id);\n }\n private styles(target: Window, pane: Pane, scope: Scope) {\n const source = this.doc,\n dest = target.document;\n dest.title = pane.title + ' — layouts';\n const base = dest.createElement('base');\n base.href = source.baseURI;\n dest.head.append(base);\n const holder = dest.createElement('meta');\n holder.dataset.layoutsStyles = '';\n dest.head.append(holder);\n let copied: Element[] = [];\n const copy = () => {\n copied.forEach((n) => n.remove());\n copied = Array.from(source.head.querySelectorAll('link[rel=\"stylesheet\"], style')).map(\n (n) => n.cloneNode(true) as Element,\n );\n copied.forEach((n) => dest.head.append(n));\n dest.documentElement.className = source.documentElement.className;\n dest.body.className = source.body.className;\n for (const original of [source.documentElement, source.body]) {\n const next = original === source.body ? dest.body : dest.documentElement;\n for (const attribute of Array.from(next.attributes))\n if (attribute.name === 'style' || attribute.name.startsWith('data-'))\n next.removeAttribute(attribute.name);\n for (const attribute of Array.from(original.attributes))\n if (attribute.name === 'style' || attribute.name.startsWith('data-'))\n next.setAttribute(attribute.name, attribute.value);\n }\n };\n copy();\n const observer = new MutationObserver(copy);\n observer.observe(source.head, { childList: true, subtree: true, characterData: true });\n observer.observe(source.body, { attributes: true });\n observer.observe(source.documentElement, { attributes: true });\n scope.add(() => observer.disconnect());\n }\n open(id: string, placement: WindowPlacement = {}): boolean {\n if (this.stopped) return false;\n const existing = this.companions.get(id);\n if (existing) {\n existing.window.focus();\n return true;\n }\n const pane = this.options.store.getSnapshot().panes[id];\n if (!pane || !this.options.store.can(id, 'popout')) return false;\n let child: Window | null = null,\n mounted: MountedPane | undefined;\n const scope = new Scope();\n try {\n const requested = { width: 640, height: 480, ...placement };\n const features = [\n 'popup=yes',\n ...Object.entries(requested).map(([key, value]) => `${key}=${Math.round(value)}`),\n ].join(',');\n child = this.options.openWindow\n ? this.options.openWindow(pane, requested)\n : this.doc.defaultView!.open('about:blank', '', features);\n if (!child)\n throw new Error(\n 'The browser blocked the popout. Allow popups or try again from the pane menu.',\n );\n const dest = child.document;\n dest.body.replaceChildren();\n this.styles(child, pane, scope);\n const shell = el(dest, 'div', 'layouts layouts-companion');\n const bar = el(dest, 'header', 'layouts-companion-bar');\n const button = el(dest, 'button', 'layouts-button', 'Return to layout');\n button.type = 'button';\n bar.append(el(dest, 'strong', '', pane.title), button);\n shell.append(bar);\n dest.body.append(shell);\n const syncTheme = () => {\n const computed = this.doc.defaultView!.getComputedStyle(this.themeRoot);\n for (const property of Object.values(themeProperties)) {\n const value = computed.getPropertyValue(property);\n if (value.trim()) shell.style.setProperty(property, value);\n else shell.style.removeProperty(property);\n }\n };\n syncTheme();\n const themeObserver = new MutationObserver(syncTheme);\n for (let node: HTMLElement | null = this.themeRoot; node; node = node.parentElement)\n themeObserver.observe(node, { attributes: true });\n themeObserver.observe(this.doc.head, { childList: true, subtree: true, characterData: true });\n scope.add(() => themeObserver.disconnect());\n this.options.prepareWindow?.(child, pane);\n mounted = mountPane(dest, pane, 'popout', this.options);\n shell.append(mounted.element);\n const c: Companion = { window: child, mounted, scope, suppressReturn: false };\n this.companions.set(id, c);\n scope.listen(button, 'click', () => this.returnPane(id));\n // Leave detection supplements closed-window polling; neither is a persistence guarantee.\n scope.listen(child, 'pagehide', () => {\n if (!c.suppressReturn && !this.stopped) queueMicrotask(() => this.returnPane(id));\n });\n this.options.store.popout(id, requested, { source: 'user' });\n this.pending.delete(id);\n child.focus();\n return true;\n } catch (error) {\n this.companions.delete(id);\n scope.dispose();\n try {\n mounted?.dispose();\n } catch {\n /* Finish rollback. */\n }\n try {\n child?.close();\n } catch {\n /* Browser may already have closed it. */\n }\n this.error(error);\n return false;\n }\n }\n returnPane(id: string) {\n if (this.stopped) return;\n try {\n this.options.store.returnPane(id);\n } catch (error) {\n this.error(error);\n }\n }\n private closeCompanion(id: string, c: Companion) {\n c.suppressReturn = true;\n this.companions.delete(id);\n c.scope.dispose();\n try {\n c.mounted.dispose();\n } catch (error) {\n this.error(error);\n }\n try {\n c.window.close();\n } catch {\n /* A user may have navigated the companion away. */\n }\n }\n private check() {\n if (this.stopped) return;\n for (const [id, c] of this.companions) {\n try {\n if (c.window.closed || c.window.document.defaultView === null) this.returnPane(id);\n } catch {\n this.returnPane(id);\n }\n }\n }\n dispose() {\n if (this.stopped) return;\n this.stopped = true;\n this.scope.dispose();\n for (const [id, c] of this.companions) {\n try {\n c.window.document.body.replaceChildren(\n el(\n c.window.document,\n 'p',\n 'layouts-placeholder',\n 'Disconnected: the main layout session has ended.',\n ),\n );\n } catch {\n /* Inaccessible or already closed. */\n }\n this.closeCompanion(id, c);\n }\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "quilt-vanilla",
3
+ "version": "0.1.0",
4
+ "description": "Vanilla DOM split panes, tabs, themes, and same-origin window popouts",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/niko-dellic/quilt.git",
10
+ "directory": "packages/dom"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "src",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./styles.css": "./dist/styles.css"
24
+ },
25
+ "scripts": {
26
+ "build": "node ../../scripts/build-package.mjs dom",
27
+ "prepack": "node ../../scripts/build-package.mjs dom --dependencies"
28
+ },
29
+ "dependencies": {
30
+ "quilt-core": "0.1.0"
31
+ },
32
+ "sideEffects": [
33
+ "**/*.css"
34
+ ],
35
+ "homepage": "https://github.com/niko-dellic/quilt/tree/main/packages/dom#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/niko-dellic/quilt/issues"
38
+ },
39
+ "keywords": [
40
+ "layout",
41
+ "split-pane",
42
+ "tabs",
43
+ "workspace",
44
+ "typescript"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org/"
49
+ }
50
+ }
@@ -0,0 +1,40 @@
1
+ import type { LayoutOptions } from './types.js';
2
+ const paths = {
3
+ 'add-tab': ['M12 5v14M5 12h14'],
4
+ 'split-right': ['M3 4h18v16H3z', 'M12 4v16'],
5
+ 'split-below': ['M3 4h18v16H3z', 'M3 12h18'],
6
+ 'tab-orientation': ['M3 4h18v16H3z', 'M3 9h18', 'M8 9v11'],
7
+ join: ['M3 4h18v16H3z', 'm7 9 3 3-3 3m10-6-3 3 3 3'],
8
+ maximize: ['M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5'],
9
+ restore: ['M3 8h5V3m13 5h-5V3M8 21v-5H3m13 5v-5h5'],
10
+ popout: ['M14 3h7v7m0-7L11 13', 'M10 3H3v18h18v-7'],
11
+ close: ['m6 6 12 12M6 18 18 6'],
12
+ cancel: ['M9 5 2 12l7 7M2 12h14a5 5 0 0 0 0-10'],
13
+ } as const;
14
+ export type ActionIcon = keyof typeof paths;
15
+ /** Small built-in chrome icons; applications may override the namespaced keys. */
16
+ export function actionIcon(
17
+ doc: Document,
18
+ name: ActionIcon,
19
+ options: Pick<LayoutOptions, 'renderIcon'>,
20
+ ): Element {
21
+ const custom = options.renderIcon?.(`layouts:${name}`, doc);
22
+ if (custom) return custom;
23
+ const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
24
+ for (const [key, value] of Object.entries({
25
+ viewBox: '0 0 24 24',
26
+ fill: 'none',
27
+ stroke: 'currentColor',
28
+ 'stroke-width': '1.7',
29
+ 'stroke-linecap': 'round',
30
+ 'stroke-linejoin': 'round',
31
+ focusable: 'false',
32
+ }))
33
+ svg.setAttribute(key, value);
34
+ for (const d of paths[name]) {
35
+ const path = doc.createElementNS(svg.namespaceURI, 'path');
36
+ path.setAttribute('d', d);
37
+ svg.append(path);
38
+ }
39
+ return svg;
40
+ }
@@ -0,0 +1,30 @@
1
+ /** Shared geometry for renderer-owned controls, independent of application icons. */
2
+ export function chromeIcon(doc: Document, name: 'close' | 'more'): SVGSVGElement {
3
+ const ns = 'http://www.w3.org/2000/svg';
4
+ const svg = doc.createElementNS(ns, 'svg');
5
+ svg.classList.add('layouts-chrome-icon');
6
+ svg.setAttribute('viewBox', '0 0 24 24');
7
+ svg.setAttribute('width', '16');
8
+ svg.setAttribute('height', '16');
9
+ svg.setAttribute('fill', 'none');
10
+ svg.setAttribute('stroke', 'currentColor');
11
+ svg.setAttribute('stroke-width', '1.75');
12
+ svg.setAttribute('stroke-linecap', 'round');
13
+ svg.setAttribute('stroke-linejoin', 'round');
14
+ svg.setAttribute('aria-hidden', 'true');
15
+ svg.setAttribute('focusable', 'false');
16
+ if (name === 'close') {
17
+ const path = doc.createElementNS(ns, 'path');
18
+ path.setAttribute('d', 'M6 6 18 18 M18 6 6 18');
19
+ svg.append(path);
20
+ } else {
21
+ for (const y of [5, 12, 19]) {
22
+ const dot = doc.createElementNS(ns, 'circle');
23
+ dot.setAttribute('cx', '12');
24
+ dot.setAttribute('cy', String(y));
25
+ dot.setAttribute('r', '1');
26
+ svg.append(dot);
27
+ }
28
+ }
29
+ return svg;
30
+ }