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.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/action-icons.d.ts +18 -0
- package/dist/action-icons.d.ts.map +1 -0
- package/dist/action-icons.js +36 -0
- package/dist/action-icons.js.map +1 -0
- package/dist/chrome-icons.d.ts +3 -0
- package/dist/chrome-icons.d.ts.map +1 -0
- package/dist/chrome-icons.js +32 -0
- package/dist/chrome-icons.js.map +1 -0
- package/dist/corners.d.ts +8 -0
- package/dist/corners.d.ts.map +1 -0
- package/dist/corners.js +205 -0
- package/dist/corners.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lifetime.d.ts +10 -0
- package/dist/lifetime.d.ts.map +1 -0
- package/dist/lifetime.js +44 -0
- package/dist/lifetime.js.map +1 -0
- package/dist/menu.d.ts +10 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +356 -0
- package/dist/menu.js.map +1 -0
- package/dist/panes.d.ts +13 -0
- package/dist/panes.d.ts.map +1 -0
- package/dist/panes.js +58 -0
- package/dist/panes.js.map +1 -0
- package/dist/picker.d.ts +6 -0
- package/dist/picker.d.ts.map +1 -0
- package/dist/picker.js +112 -0
- package/dist/picker.js.map +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +39 -0
- package/dist/registry.js.map +1 -0
- package/dist/renderer.d.ts +3 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +570 -0
- package/dist/renderer.js.map +1 -0
- package/dist/resize.d.ts +7 -0
- package/dist/resize.d.ts.map +1 -0
- package/dist/resize.js +56 -0
- package/dist/resize.js.map +1 -0
- package/dist/shortcuts.d.ts +6 -0
- package/dist/shortcuts.d.ts.map +1 -0
- package/dist/shortcuts.js +64 -0
- package/dist/shortcuts.js.map +1 -0
- package/dist/styles.css +842 -0
- package/dist/tab-bar.d.ts +6 -0
- package/dist/tab-bar.d.ts.map +1 -0
- package/dist/tab-bar.js +336 -0
- package/dist/tab-bar.js.map +1 -0
- package/dist/tab-tooltip.d.ts +4 -0
- package/dist/tab-tooltip.d.ts.map +1 -0
- package/dist/tab-tooltip.js +95 -0
- package/dist/tab-tooltip.js.map +1 -0
- package/dist/tabs.d.ts +15 -0
- package/dist/tabs.d.ts.map +1 -0
- package/dist/tabs.js +126 -0
- package/dist/tabs.js.map +1 -0
- package/dist/theme.d.ts +206 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +166 -0
- package/dist/theme.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/windows.d.ts +23 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +247 -0
- package/dist/windows.js.map +1 -0
- package/package.json +50 -0
- package/src/action-icons.ts +40 -0
- package/src/chrome-icons.ts +30 -0
- package/src/corners.ts +259 -0
- package/src/index.ts +27 -0
- package/src/lifetime.ts +46 -0
- package/src/menu.ts +432 -0
- package/src/panes.ts +76 -0
- package/src/picker.ts +114 -0
- package/src/registry.ts +46 -0
- package/src/renderer.ts +654 -0
- package/src/resize.ts +65 -0
- package/src/shortcuts.ts +90 -0
- package/src/styles.css +842 -0
- package/src/tab-bar.ts +382 -0
- package/src/tab-tooltip.ts +96 -0
- package/src/tabs.ts +129 -0
- package/src/theme.ts +211 -0
- package/src/types.ts +67 -0
- package/src/windows.ts +244 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 niko-dellic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# quilt-vanilla
|
|
2
|
+
|
|
3
|
+
Vanilla DOM pane layouts, tabs, themes, resizing, and same-origin browser popouts. Includes convenient core exports; does not install React. MIT licensed, ESM and TypeScript.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install quilt-vanilla
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Core is installed automatically; React is not required. Use an ESM-capable bundler with CSS import support, such as Vite. For local archives, see [packaging instructions](https://github.com/niko-dellic/quilt/blob/main/docs/packaging.md).
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
Provide a host in your HTML: `<div id="workspace" style="width:100%;height:600px"></div>`.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createLayout, LayoutStore, mountLayout } from 'quilt-vanilla';
|
|
19
|
+
import 'quilt-vanilla/styles.css';
|
|
20
|
+
|
|
21
|
+
const store = new LayoutStore(
|
|
22
|
+
createLayout({
|
|
23
|
+
pane: { id: 'notes', type: 'notes', title: 'Notes' },
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
const data = { text: 'Hello' }; // survives view remounts
|
|
27
|
+
const mounted = mountLayout(document.getElementById('workspace')!, {
|
|
28
|
+
store,
|
|
29
|
+
getPaneState: () => data,
|
|
30
|
+
renderers: {
|
|
31
|
+
notes: ({ element, document: doc, state }) => {
|
|
32
|
+
const model = state as typeof data;
|
|
33
|
+
const input = doc.createElement('textarea');
|
|
34
|
+
input.value = model.text;
|
|
35
|
+
input.oninput = () => {
|
|
36
|
+
model.text = input.value;
|
|
37
|
+
};
|
|
38
|
+
element.append(input);
|
|
39
|
+
return {
|
|
40
|
+
dispose() {
|
|
41
|
+
input.oninput = null;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
// Call when removing the workspace:
|
|
48
|
+
function disposeWorkspace() {
|
|
49
|
+
mounted.dispose();
|
|
50
|
+
store.dispose();
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The host needs an explicit height. Supply a `TabRegistry` through `tabs` to offer new content; registration factories return fresh pane IDs. `themes`, `themeFamilies`, `LayoutTheme`, and `MountedLayout` are public exports. The layout JSON type is `LayoutSnapshot` from this entry point.
|
|
55
|
+
|
|
56
|
+
Call `mounted.popout(id)` from a user action. Companions are same-origin and owned by the main session. Use the supplied document/window inside renderers and release every view-owned resource in `dispose`. Keep app data outside view lifetimes.
|
|
57
|
+
|
|
58
|
+
See [API](https://github.com/niko-dellic/quilt/blob/main/docs/api.md), [lifecycle](https://github.com/niko-dellic/quilt/blob/main/docs/lifecycle.md), and [migration](https://github.com/niko-dellic/quilt/blob/main/docs/migration.md).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LayoutOptions } from './types.js';
|
|
2
|
+
declare const paths: {
|
|
3
|
+
readonly 'add-tab': readonly ["M12 5v14M5 12h14"];
|
|
4
|
+
readonly 'split-right': readonly ["M3 4h18v16H3z", "M12 4v16"];
|
|
5
|
+
readonly 'split-below': readonly ["M3 4h18v16H3z", "M3 12h18"];
|
|
6
|
+
readonly 'tab-orientation': readonly ["M3 4h18v16H3z", "M3 9h18", "M8 9v11"];
|
|
7
|
+
readonly join: readonly ["M3 4h18v16H3z", "m7 9 3 3-3 3m10-6-3 3 3 3"];
|
|
8
|
+
readonly maximize: readonly ["M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5"];
|
|
9
|
+
readonly restore: readonly ["M3 8h5V3m13 5h-5V3M8 21v-5H3m13 5v-5h5"];
|
|
10
|
+
readonly popout: readonly ["M14 3h7v7m0-7L11 13", "M10 3H3v18h18v-7"];
|
|
11
|
+
readonly close: readonly ["m6 6 12 12M6 18 18 6"];
|
|
12
|
+
readonly cancel: readonly ["M9 5 2 12l7 7M2 12h14a5 5 0 0 0 0-10"];
|
|
13
|
+
};
|
|
14
|
+
export type ActionIcon = keyof typeof paths;
|
|
15
|
+
/** Small built-in chrome icons; applications may override the namespaced keys. */
|
|
16
|
+
export declare function actionIcon(doc: Document, name: ActionIcon, options: Pick<LayoutOptions, 'renderIcon'>): Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=action-icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-icons.d.ts","sourceRoot":"","sources":["../src/action-icons.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,QAAA,MAAM,KAAK;;;;;;;;;;;CAWD,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,KAAK,CAAC;AAC5C,kFAAkF;AAClF,wBAAgB,UAAU,CACxB,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,GACzC,OAAO,CAoBT"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const paths = {
|
|
2
|
+
'add-tab': ['M12 5v14M5 12h14'],
|
|
3
|
+
'split-right': ['M3 4h18v16H3z', 'M12 4v16'],
|
|
4
|
+
'split-below': ['M3 4h18v16H3z', 'M3 12h18'],
|
|
5
|
+
'tab-orientation': ['M3 4h18v16H3z', 'M3 9h18', 'M8 9v11'],
|
|
6
|
+
join: ['M3 4h18v16H3z', 'm7 9 3 3-3 3m10-6-3 3 3 3'],
|
|
7
|
+
maximize: ['M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5'],
|
|
8
|
+
restore: ['M3 8h5V3m13 5h-5V3M8 21v-5H3m13 5v-5h5'],
|
|
9
|
+
popout: ['M14 3h7v7m0-7L11 13', 'M10 3H3v18h18v-7'],
|
|
10
|
+
close: ['m6 6 12 12M6 18 18 6'],
|
|
11
|
+
cancel: ['M9 5 2 12l7 7M2 12h14a5 5 0 0 0 0-10'],
|
|
12
|
+
};
|
|
13
|
+
/** Small built-in chrome icons; applications may override the namespaced keys. */
|
|
14
|
+
export function actionIcon(doc, name, options) {
|
|
15
|
+
const custom = options.renderIcon?.(`layouts:${name}`, doc);
|
|
16
|
+
if (custom)
|
|
17
|
+
return custom;
|
|
18
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
19
|
+
for (const [key, value] of Object.entries({
|
|
20
|
+
viewBox: '0 0 24 24',
|
|
21
|
+
fill: 'none',
|
|
22
|
+
stroke: 'currentColor',
|
|
23
|
+
'stroke-width': '1.7',
|
|
24
|
+
'stroke-linecap': 'round',
|
|
25
|
+
'stroke-linejoin': 'round',
|
|
26
|
+
focusable: 'false',
|
|
27
|
+
}))
|
|
28
|
+
svg.setAttribute(key, value);
|
|
29
|
+
for (const d of paths[name]) {
|
|
30
|
+
const path = doc.createElementNS(svg.namespaceURI, 'path');
|
|
31
|
+
path.setAttribute('d', d);
|
|
32
|
+
svg.append(path);
|
|
33
|
+
}
|
|
34
|
+
return svg;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=action-icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-icons.js","sourceRoot":"","sources":["../src/action-icons.ts"],"names":[],"mappings":"AACA,MAAM,KAAK,GAAG;IACZ,SAAS,EAAE,CAAC,kBAAkB,CAAC;IAC/B,aAAa,EAAE,CAAC,eAAe,EAAE,UAAU,CAAC;IAC5C,aAAa,EAAE,CAAC,eAAe,EAAE,UAAU,CAAC;IAC5C,iBAAiB,EAAE,CAAC,eAAe,EAAE,SAAS,EAAE,SAAS,CAAC;IAC1D,IAAI,EAAE,CAAC,eAAe,EAAE,2BAA2B,CAAC;IACpD,QAAQ,EAAE,CAAC,sCAAsC,CAAC;IAClD,OAAO,EAAE,CAAC,wCAAwC,CAAC;IACnD,MAAM,EAAE,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;IACnD,KAAK,EAAE,CAAC,sBAAsB,CAAC;IAC/B,MAAM,EAAE,CAAC,sCAAsC,CAAC;CACxC,CAAC;AAEX,kFAAkF;AAClF,MAAM,UAAU,UAAU,CACxB,GAAa,EACb,IAAgB,EAChB,OAA0C;IAE1C,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,WAAW,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5D,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;IACrE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC;QACxC,OAAO,EAAE,WAAW;QACpB,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,cAAc;QACtB,cAAc,EAAE,KAAK;QACrB,gBAAgB,EAAE,OAAO;QACzB,iBAAiB,EAAE,OAAO;QAC1B,SAAS,EAAE,OAAO;KACnB,CAAC;QACA,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["import type { LayoutOptions } from './types.js';\nconst paths = {\n 'add-tab': ['M12 5v14M5 12h14'],\n 'split-right': ['M3 4h18v16H3z', 'M12 4v16'],\n 'split-below': ['M3 4h18v16H3z', 'M3 12h18'],\n 'tab-orientation': ['M3 4h18v16H3z', 'M3 9h18', 'M8 9v11'],\n join: ['M3 4h18v16H3z', 'm7 9 3 3-3 3m10-6-3 3 3 3'],\n maximize: ['M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5'],\n restore: ['M3 8h5V3m13 5h-5V3M8 21v-5H3m13 5v-5h5'],\n popout: ['M14 3h7v7m0-7L11 13', 'M10 3H3v18h18v-7'],\n close: ['m6 6 12 12M6 18 18 6'],\n cancel: ['M9 5 2 12l7 7M2 12h14a5 5 0 0 0 0-10'],\n} as const;\nexport type ActionIcon = keyof typeof paths;\n/** Small built-in chrome icons; applications may override the namespaced keys. */\nexport function actionIcon(\n doc: Document,\n name: ActionIcon,\n options: Pick<LayoutOptions, 'renderIcon'>,\n): Element {\n const custom = options.renderIcon?.(`layouts:${name}`, doc);\n if (custom) return custom;\n const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');\n for (const [key, value] of Object.entries({\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': '1.7',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n focusable: 'false',\n }))\n svg.setAttribute(key, value);\n for (const d of paths[name]) {\n const path = doc.createElementNS(svg.namespaceURI, 'path');\n path.setAttribute('d', d);\n svg.append(path);\n }\n return svg;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-icons.d.ts","sourceRoot":"","sources":["../src/chrome-icons.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,aAAa,CA4B/E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Shared geometry for renderer-owned controls, independent of application icons. */
|
|
2
|
+
export function chromeIcon(doc, name) {
|
|
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
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
for (const y of [5, 12, 19]) {
|
|
23
|
+
const dot = doc.createElementNS(ns, 'circle');
|
|
24
|
+
dot.setAttribute('cx', '12');
|
|
25
|
+
dot.setAttribute('cy', String(y));
|
|
26
|
+
dot.setAttribute('r', '1');
|
|
27
|
+
svg.append(dot);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return svg;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=chrome-icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-icons.js","sourceRoot":"","sources":["../src/chrome-icons.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAC,GAAa,EAAE,IAAsB;IAC9D,MAAM,EAAE,GAAG,4BAA4B,CAAC;IACxC,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC3C,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACzC,GAAG,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACzC,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAChC,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACjC,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC3C,GAAG,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACzC,GAAG,CAAC,YAAY,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAC5C,GAAG,CAAC,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;IAC7C,GAAG,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACxC,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACvC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;QAChD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;YAC9C,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC7B,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/** Shared geometry for renderer-owned controls, independent of application icons. */\nexport function chromeIcon(doc: Document, name: 'close' | 'more'): SVGSVGElement {\n const ns = 'http://www.w3.org/2000/svg';\n const svg = doc.createElementNS(ns, 'svg');\n svg.classList.add('layouts-chrome-icon');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('width', '16');\n svg.setAttribute('height', '16');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '1.75');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('focusable', 'false');\n if (name === 'close') {\n const path = doc.createElementNS(ns, 'path');\n path.setAttribute('d', 'M6 6 18 18 M18 6 6 18');\n svg.append(path);\n } else {\n for (const y of [5, 12, 19]) {\n const dot = doc.createElementNS(ns, 'circle');\n dot.setAttribute('cx', '12');\n dot.setAttribute('cy', String(y));\n dot.setAttribute('r', '1');\n svg.append(dot);\n }\n }\n return svg;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Group, Pane } from 'quilt-core';
|
|
2
|
+
import type { LayoutOptions } from './types.js';
|
|
3
|
+
import { Scope } from './lifetime.js';
|
|
4
|
+
type Direction = 'left' | 'right' | 'top' | 'bottom';
|
|
5
|
+
/** Commit once on release; cancellation never changes the layout or pane ownership. */
|
|
6
|
+
export declare function bindCorners(host: HTMLElement, id: string, options: LayoutOptions, scope: Scope, split: (pane: Pane | undefined, group: Group, direction: Direction, ratio: number) => void, report: (error: unknown) => void): void;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=corners.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corners.d.ts","sourceRoot":"","sources":["../src/corners.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAM,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1C,KAAK,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AACrD,uFAAuF;AACvF,wBAAgB,WAAW,CACzB,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,EAC1F,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,QAsPjC"}
|
package/dist/corners.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { allocate, bounds, DIVIDER, findNode, joinRange } from 'quilt-core';
|
|
2
|
+
import { el, Scope } from './lifetime.js';
|
|
3
|
+
/** Commit once on release; cancellation never changes the layout or pane ownership. */
|
|
4
|
+
export function bindCorners(host, id, options, scope, split, report) {
|
|
5
|
+
const doc = host.ownerDocument;
|
|
6
|
+
let drag;
|
|
7
|
+
scope.add(() => drag?.dispose());
|
|
8
|
+
const initial = findNode(options.store.getSnapshot().root, id);
|
|
9
|
+
if (initial?.kind !== 'group' || !initial.panes.every((p) => options.store.can(p, 'split')))
|
|
10
|
+
return;
|
|
11
|
+
for (const corner of ['tl', 'tr', 'bl', 'br']) {
|
|
12
|
+
const handle = el(doc, 'button', 'layouts-corner');
|
|
13
|
+
handle.dataset.corner = corner;
|
|
14
|
+
handle.title = 'Drag inward to split; drag across adjacent regions to join. Escape cancels.';
|
|
15
|
+
handle.setAttribute('aria-label', 'Split or join region from ' + corner + ' corner');
|
|
16
|
+
handle.tabIndex = -1; // Equivalent keyboard operations live in the pane menu.
|
|
17
|
+
host.append(handle);
|
|
18
|
+
scope.add(() => handle.remove());
|
|
19
|
+
scope.listen(handle, 'pointerdown', (event) => {
|
|
20
|
+
const start = event;
|
|
21
|
+
if (start.button !== 0)
|
|
22
|
+
return;
|
|
23
|
+
const node = findNode(options.store.getSnapshot().root, id);
|
|
24
|
+
if (node?.kind !== 'group' || !node.panes.every((p) => options.store.can(p, 'split')))
|
|
25
|
+
return;
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
event.stopPropagation();
|
|
28
|
+
drag?.dispose();
|
|
29
|
+
const local = new Scope();
|
|
30
|
+
drag = local;
|
|
31
|
+
local.add(options.store.subscribe(() => local.dispose()));
|
|
32
|
+
handle.setPointerCapture(start.pointerId);
|
|
33
|
+
let target;
|
|
34
|
+
const overlay = el(doc, 'div', 'layouts-corner-overlay');
|
|
35
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
36
|
+
host.closest('.layouts').append(overlay);
|
|
37
|
+
local.add(() => overlay.remove());
|
|
38
|
+
const mark = (rect, kind, label) => {
|
|
39
|
+
const box = el(doc, 'div', 'layouts-corner-preview');
|
|
40
|
+
box.dataset.cornerPreview = kind;
|
|
41
|
+
Object.assign(box.style, {
|
|
42
|
+
left: `${rect.left}px`,
|
|
43
|
+
top: `${rect.top}px`,
|
|
44
|
+
width: `${rect.width}px`,
|
|
45
|
+
height: `${rect.height}px`,
|
|
46
|
+
});
|
|
47
|
+
box.append(el(doc, 'span', 'layouts-corner-label', label));
|
|
48
|
+
overlay.append(box);
|
|
49
|
+
};
|
|
50
|
+
const clear = () => {
|
|
51
|
+
overlay.replaceChildren();
|
|
52
|
+
target = undefined;
|
|
53
|
+
};
|
|
54
|
+
local.add(() => {
|
|
55
|
+
clear();
|
|
56
|
+
try {
|
|
57
|
+
handle.releasePointerCapture(start.pointerId);
|
|
58
|
+
}
|
|
59
|
+
catch { }
|
|
60
|
+
});
|
|
61
|
+
local.listen(handle, 'pointermove', (e) => {
|
|
62
|
+
const move = e;
|
|
63
|
+
clear();
|
|
64
|
+
if (Math.hypot(move.clientX - start.clientX, move.clientY - start.clientY) < 24)
|
|
65
|
+
return;
|
|
66
|
+
const rect = host.getBoundingClientRect();
|
|
67
|
+
const inside = move.clientX > rect.left &&
|
|
68
|
+
move.clientX < rect.right &&
|
|
69
|
+
move.clientY > rect.top &&
|
|
70
|
+
move.clientY < rect.bottom;
|
|
71
|
+
if (inside) {
|
|
72
|
+
const horizontal = Math.abs(move.clientX - start.clientX) > Math.abs(move.clientY - start.clientY);
|
|
73
|
+
const direction = horizontal
|
|
74
|
+
? corner.endsWith('l')
|
|
75
|
+
? 'left'
|
|
76
|
+
: 'right'
|
|
77
|
+
: corner.startsWith('t')
|
|
78
|
+
? 'top'
|
|
79
|
+
: 'bottom';
|
|
80
|
+
target = {
|
|
81
|
+
kind: 'split',
|
|
82
|
+
direction,
|
|
83
|
+
ratio: Math.max(0.1, Math.min(0.9, horizontal
|
|
84
|
+
? (move.clientX - rect.left) / rect.width
|
|
85
|
+
: (move.clientY - rect.top) / rect.height)),
|
|
86
|
+
};
|
|
87
|
+
const before = direction === 'left' || direction === 'top';
|
|
88
|
+
const layout = options.store.getSnapshot();
|
|
89
|
+
const current = findNode(layout.root, id);
|
|
90
|
+
if (!current)
|
|
91
|
+
return;
|
|
92
|
+
const constraints = bounds(current, layout);
|
|
93
|
+
const min = horizontal ? constraints.minWidth : constraints.minHeight;
|
|
94
|
+
const max = horizontal ? constraints.maxWidth : constraints.maxHeight;
|
|
95
|
+
const total = horizontal ? rect.width : rect.height;
|
|
96
|
+
const disabled = current.kind === 'group' &&
|
|
97
|
+
(!current.panes.every((p) => options.store.can(p, 'resize')) || min === max);
|
|
98
|
+
const configuredGap = parseFloat(doc
|
|
99
|
+
.defaultView.getComputedStyle(host.closest('.layouts'))
|
|
100
|
+
.getPropertyValue(disabled
|
|
101
|
+
? '--layouts-disabled-resize-handle-width'
|
|
102
|
+
: '--layouts-resize-handle-width'));
|
|
103
|
+
const gap = Number.isFinite(configuredGap) && configuredGap >= 0
|
|
104
|
+
? configuredGap
|
|
105
|
+
: disabled
|
|
106
|
+
? 0
|
|
107
|
+
: DIVIDER;
|
|
108
|
+
const [first, second] = allocate(total, target.ratio, before ? 0 : min, before ? Infinity : max, before ? min : 0, before ? max : Infinity, gap);
|
|
109
|
+
// Store uses content extents (excluding the divider), matching this preview exactly.
|
|
110
|
+
const newRect = horizontal
|
|
111
|
+
? {
|
|
112
|
+
left: rect.left + (before ? 0 : first + gap),
|
|
113
|
+
top: rect.top,
|
|
114
|
+
width: before ? first : second,
|
|
115
|
+
height: rect.height,
|
|
116
|
+
}
|
|
117
|
+
: {
|
|
118
|
+
left: rect.left,
|
|
119
|
+
top: rect.top + (before ? 0 : first + gap),
|
|
120
|
+
width: rect.width,
|
|
121
|
+
height: before ? first : second,
|
|
122
|
+
};
|
|
123
|
+
mark(newRect, 'split', 'New pane');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const layout = options.store.getSnapshot();
|
|
127
|
+
const elements = Array.from(host.closest('.layouts').querySelectorAll('[data-node-id]')).filter((element) => element.closest('.layouts') === host.closest('.layouts'));
|
|
128
|
+
const boxes = new Map(elements.map((element) => [element.dataset.nodeId, element.getBoundingClientRect()]));
|
|
129
|
+
const hovered = elements.find((element) => {
|
|
130
|
+
const candidate = findNode(layout.root, element.dataset.nodeId);
|
|
131
|
+
const box = boxes.get(element.dataset.nodeId);
|
|
132
|
+
return (candidate?.kind === 'group' &&
|
|
133
|
+
move.clientX >= box.left &&
|
|
134
|
+
move.clientX <= box.right &&
|
|
135
|
+
move.clientY >= box.top &&
|
|
136
|
+
move.clientY <= box.bottom);
|
|
137
|
+
});
|
|
138
|
+
if (!hovered)
|
|
139
|
+
return;
|
|
140
|
+
const receiverId = hovered.dataset.nodeId;
|
|
141
|
+
const range = joinRange(layout.root, id, receiverId);
|
|
142
|
+
if (!range ||
|
|
143
|
+
!range.selected.every((g) => g.panes.every((p) => options.store.can(p, 'join'))))
|
|
144
|
+
return;
|
|
145
|
+
const receiver = findNode(layout.root, receiverId);
|
|
146
|
+
if (receiver?.kind !== 'group')
|
|
147
|
+
return;
|
|
148
|
+
const selectedBoxes = range.selected.map((g) => boxes.get(g.id));
|
|
149
|
+
if (selectedBoxes.some((box) => !box))
|
|
150
|
+
return;
|
|
151
|
+
// Require full-edge alignment in the rendered layout too (constraints can leave slack).
|
|
152
|
+
const horizontal = range.row.axis === 'horizontal';
|
|
153
|
+
if (selectedBoxes.some((box) => horizontal
|
|
154
|
+
? Math.abs(box.top - rect.top) > 1 || Math.abs(box.bottom - rect.bottom) > 1
|
|
155
|
+
: Math.abs(box.left - rect.left) > 1 || Math.abs(box.right - rect.right) > 1))
|
|
156
|
+
return;
|
|
157
|
+
const extents = Object.fromEntries([...boxes].map(([nodeId, box]) => [nodeId, horizontal ? box.width : box.height]));
|
|
158
|
+
target = { kind: 'join', id: receiverId, extents };
|
|
159
|
+
const targetTitle = layout.panes[receiver.active ?? '']?.title ?? 'Empty region';
|
|
160
|
+
const sources = range.selected.filter((g) => g.id !== receiverId);
|
|
161
|
+
const sourceLabel = sources.every((g) => g.panes.length === 0)
|
|
162
|
+
? `${sources.length} empty ${sources.length === 1 ? 'region' : 'regions'}`
|
|
163
|
+
: sources.map((g) => layout.panes[g.active ?? '']?.title ?? 'Empty region').join(', ');
|
|
164
|
+
for (const group of range.selected) {
|
|
165
|
+
const receiving = group.id === receiverId;
|
|
166
|
+
mark(boxes.get(group.id), receiving ? 'join-target' : 'join-source', receiving ? `${targetTitle} absorbs ${sourceLabel}` : `Joins ${targetTitle}`);
|
|
167
|
+
}
|
|
168
|
+
const left = Math.min(...selectedBoxes.map((box) => box.left));
|
|
169
|
+
const top = Math.min(...selectedBoxes.map((box) => box.top));
|
|
170
|
+
mark({
|
|
171
|
+
left,
|
|
172
|
+
top,
|
|
173
|
+
width: Math.max(...selectedBoxes.map((box) => box.right)) - left,
|
|
174
|
+
height: Math.max(...selectedBoxes.map((box) => box.bottom)) - top,
|
|
175
|
+
}, 'join', '');
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
local.listen(handle, 'pointerup', () => {
|
|
179
|
+
const action = target;
|
|
180
|
+
local.dispose();
|
|
181
|
+
if (!action)
|
|
182
|
+
return;
|
|
183
|
+
try {
|
|
184
|
+
if (action.kind === 'join')
|
|
185
|
+
options.store.joinRegions(action.id, id, { source: 'user', extents: action.extents });
|
|
186
|
+
else {
|
|
187
|
+
const layout = options.store.getSnapshot(), group = findNode(layout.root, id);
|
|
188
|
+
if (group?.kind === 'group')
|
|
189
|
+
split(group.active ? layout.panes[group.active] : undefined, group, action.direction, action.ratio);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
report(error);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
local.listen(handle, 'pointercancel', () => local.dispose());
|
|
197
|
+
local.listen(handle, 'lostpointercapture', () => local.dispose());
|
|
198
|
+
local.listen(doc, 'keydown', (e) => {
|
|
199
|
+
if (e.key === 'Escape')
|
|
200
|
+
local.dispose();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=corners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corners.js","sourceRoot":"","sources":["../src/corners.ts"],"names":[],"mappings":"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","sourcesContent":["import { allocate, bounds, DIVIDER, findNode, joinRange } from 'quilt-core';\nimport type { Group, Pane } from 'quilt-core';\nimport type { LayoutOptions } from './types.js';\nimport { el, Scope } from './lifetime.js';\ntype Direction = 'left' | 'right' | 'top' | 'bottom';\n/** Commit once on release; cancellation never changes the layout or pane ownership. */\nexport function bindCorners(\n host: HTMLElement,\n id: string,\n options: LayoutOptions,\n scope: Scope,\n split: (pane: Pane | undefined, group: Group, direction: Direction, ratio: number) => void,\n report: (error: unknown) => void,\n) {\n const doc = host.ownerDocument;\n let drag: Scope | undefined;\n scope.add(() => drag?.dispose());\n const initial = findNode(options.store.getSnapshot().root, id);\n if (initial?.kind !== 'group' || !initial.panes.every((p) => options.store.can(p, 'split')))\n return;\n for (const corner of ['tl', 'tr', 'bl', 'br']) {\n const handle = el(doc, 'button', 'layouts-corner');\n handle.dataset.corner = corner;\n handle.title = 'Drag inward to split; drag across adjacent regions to join. Escape cancels.';\n handle.setAttribute('aria-label', 'Split or join region from ' + corner + ' corner');\n handle.tabIndex = -1; // Equivalent keyboard operations live in the pane menu.\n host.append(handle);\n scope.add(() => handle.remove());\n scope.listen(handle, 'pointerdown', (event) => {\n const start = event as PointerEvent;\n if (start.button !== 0) return;\n const node = findNode(options.store.getSnapshot().root, id);\n if (node?.kind !== 'group' || !node.panes.every((p) => options.store.can(p, 'split'))) return;\n event.preventDefault();\n event.stopPropagation();\n drag?.dispose();\n const local = new Scope();\n drag = local;\n local.add(options.store.subscribe(() => local.dispose()));\n handle.setPointerCapture(start.pointerId);\n let target:\n | { kind: 'split'; direction: Direction; ratio: number }\n | { kind: 'join'; id: string; extents: Record<string, number> }\n | undefined;\n const overlay = el(doc, 'div', 'layouts-corner-overlay');\n overlay.setAttribute('aria-hidden', 'true');\n host.closest('.layouts')!.append(overlay);\n local.add(() => overlay.remove());\n const mark = (\n rect: { left: number; top: number; width: number; height: number },\n kind: string,\n label: string,\n ) => {\n const box = el(doc, 'div', 'layouts-corner-preview');\n box.dataset.cornerPreview = kind;\n Object.assign(box.style, {\n left: `${rect.left}px`,\n top: `${rect.top}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n box.append(el(doc, 'span', 'layouts-corner-label', label));\n overlay.append(box);\n };\n const clear = () => {\n overlay.replaceChildren();\n target = undefined;\n };\n local.add(() => {\n clear();\n try {\n handle.releasePointerCapture(start.pointerId);\n } catch {}\n });\n local.listen(handle, 'pointermove', (e) => {\n const move = e as PointerEvent;\n clear();\n if (Math.hypot(move.clientX - start.clientX, move.clientY - start.clientY) < 24) return;\n const rect = host.getBoundingClientRect();\n const inside =\n move.clientX > rect.left &&\n move.clientX < rect.right &&\n move.clientY > rect.top &&\n move.clientY < rect.bottom;\n if (inside) {\n const horizontal =\n Math.abs(move.clientX - start.clientX) > Math.abs(move.clientY - start.clientY);\n const direction: Direction = horizontal\n ? corner.endsWith('l')\n ? 'left'\n : 'right'\n : corner.startsWith('t')\n ? 'top'\n : 'bottom';\n target = {\n kind: 'split',\n direction,\n ratio: Math.max(\n 0.1,\n Math.min(\n 0.9,\n horizontal\n ? (move.clientX - rect.left) / rect.width\n : (move.clientY - rect.top) / rect.height,\n ),\n ),\n };\n const before = direction === 'left' || direction === 'top';\n const layout = options.store.getSnapshot();\n const current = findNode(layout.root, id);\n if (!current) return;\n const constraints = bounds(current, layout);\n const min = horizontal ? constraints.minWidth : constraints.minHeight;\n const max = horizontal ? constraints.maxWidth : constraints.maxHeight;\n const total = horizontal ? rect.width : rect.height;\n const disabled =\n current.kind === 'group' &&\n (!current.panes.every((p) => options.store.can(p, 'resize')) || min === max);\n const configuredGap = parseFloat(\n doc\n .defaultView!.getComputedStyle(host.closest('.layouts')!)\n .getPropertyValue(\n disabled\n ? '--layouts-disabled-resize-handle-width'\n : '--layouts-resize-handle-width',\n ),\n );\n const gap =\n Number.isFinite(configuredGap) && configuredGap >= 0\n ? configuredGap\n : disabled\n ? 0\n : DIVIDER;\n const [first, second] = allocate(\n total,\n target.ratio,\n before ? 0 : min,\n before ? Infinity : max,\n before ? min : 0,\n before ? max : Infinity,\n gap,\n );\n // Store uses content extents (excluding the divider), matching this preview exactly.\n const newRect = horizontal\n ? {\n left: rect.left + (before ? 0 : first + gap),\n top: rect.top,\n width: before ? first : second,\n height: rect.height,\n }\n : {\n left: rect.left,\n top: rect.top + (before ? 0 : first + gap),\n width: rect.width,\n height: before ? first : second,\n };\n mark(newRect, 'split', 'New pane');\n } else {\n const layout = options.store.getSnapshot();\n const elements = Array.from(\n host.closest('.layouts')!.querySelectorAll<HTMLElement>('[data-node-id]'),\n ).filter((element) => element.closest('.layouts') === host.closest('.layouts'));\n const boxes = new Map(\n elements.map((element) => [element.dataset.nodeId!, element.getBoundingClientRect()]),\n );\n const hovered = elements.find((element) => {\n const candidate = findNode(layout.root, element.dataset.nodeId!);\n const box = boxes.get(element.dataset.nodeId!)!;\n return (\n candidate?.kind === 'group' &&\n move.clientX >= box.left &&\n move.clientX <= box.right &&\n move.clientY >= box.top &&\n move.clientY <= box.bottom\n );\n });\n if (!hovered) return;\n const receiverId = hovered.dataset.nodeId!;\n const range = joinRange(layout.root, id, receiverId);\n if (\n !range ||\n !range.selected.every((g) => g.panes.every((p) => options.store.can(p, 'join')))\n )\n return;\n const receiver = findNode(layout.root, receiverId);\n if (receiver?.kind !== 'group') return;\n const selectedBoxes = range.selected.map((g) => boxes.get(g.id));\n if (selectedBoxes.some((box) => !box)) return;\n // Require full-edge alignment in the rendered layout too (constraints can leave slack).\n const horizontal = range.row.axis === 'horizontal';\n if (\n selectedBoxes.some((box) =>\n horizontal\n ? Math.abs(box!.top - rect.top) > 1 || Math.abs(box!.bottom - rect.bottom) > 1\n : Math.abs(box!.left - rect.left) > 1 || Math.abs(box!.right - rect.right) > 1,\n )\n )\n return;\n const extents = Object.fromEntries(\n [...boxes].map(([nodeId, box]) => [nodeId, horizontal ? box.width : box.height]),\n );\n target = { kind: 'join', id: receiverId, extents };\n const targetTitle = layout.panes[receiver.active ?? '']?.title ?? 'Empty region';\n const sources = range.selected.filter((g) => g.id !== receiverId);\n const sourceLabel = sources.every((g) => g.panes.length === 0)\n ? `${sources.length} empty ${sources.length === 1 ? 'region' : 'regions'}`\n : sources.map((g) => layout.panes[g.active ?? '']?.title ?? 'Empty region').join(', ');\n for (const group of range.selected) {\n const receiving = group.id === receiverId;\n mark(\n boxes.get(group.id)!,\n receiving ? 'join-target' : 'join-source',\n receiving ? `${targetTitle} absorbs ${sourceLabel}` : `Joins ${targetTitle}`,\n );\n }\n const left = Math.min(...selectedBoxes.map((box) => box!.left));\n const top = Math.min(...selectedBoxes.map((box) => box!.top));\n mark(\n {\n left,\n top,\n width: Math.max(...selectedBoxes.map((box) => box!.right)) - left,\n height: Math.max(...selectedBoxes.map((box) => box!.bottom)) - top,\n },\n 'join',\n '',\n );\n }\n });\n local.listen(handle, 'pointerup', () => {\n const action = target;\n local.dispose();\n if (!action) return;\n try {\n if (action.kind === 'join')\n options.store.joinRegions(action.id, id, { source: 'user', extents: action.extents });\n else {\n const layout = options.store.getSnapshot(),\n group = findNode(layout.root, id);\n if (group?.kind === 'group')\n split(\n group.active ? layout.panes[group.active] : undefined,\n group,\n action.direction,\n action.ratio,\n );\n }\n } catch (error) {\n report(error);\n }\n });\n local.listen(handle, 'pointercancel', () => local.dispose());\n local.listen(handle, 'lostpointercapture', () => local.dispose());\n local.listen(doc, 'keydown', (e) => {\n if ((e as KeyboardEvent).key === 'Escape') local.dispose();\n });\n });\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { mountLayout } from './renderer.js';
|
|
2
|
+
export type * from './types.js';
|
|
3
|
+
export { TabRegistry } from './registry.js';
|
|
4
|
+
export type { TabRegistration } from './registry.js';
|
|
5
|
+
export { themes, themeFamilies } from './theme.js';
|
|
6
|
+
export type { LayoutTheme } from './theme.js';
|
|
7
|
+
export { LayoutStore, LayoutError, createLayout, parseLayout, validate } from 'quilt-core';
|
|
8
|
+
export type { AutoCollapse, LayoutStoreOptions, Json, Capability, Axis, Pane, Group, Split, Node, WindowPlacement, Popout, Layout as LayoutSnapshot, Issue, CommandOptions, Change, Bounds, JoinOptions, } from 'quilt-core';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,mBAAmB,YAAY,CAAC;AAEhC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3F,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,IAAI,EACJ,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,KAAK,EACL,IAAI,EACJ,eAAe,EACf,MAAM,EACN,MAAM,IAAI,cAAc,EACxB,KAAK,EACL,cAAc,EACd,MAAM,EACN,MAAM,EACN,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { mountLayout } from './renderer.js';
|
|
2
|
+
export { TabRegistry } from './registry.js';
|
|
3
|
+
export { themes, themeFamilies } from './theme.js';
|
|
4
|
+
export { LayoutStore, LayoutError, createLayout, parseLayout, validate } from 'quilt-core';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { mountLayout } from './renderer.js';\nexport type * from './types.js';\n\nexport { TabRegistry } from './registry.js';\nexport type { TabRegistration } from './registry.js';\nexport { themes, themeFamilies } from './theme.js';\nexport type { LayoutTheme } from './theme.js';\nexport { LayoutStore, LayoutError, createLayout, parseLayout, validate } from 'quilt-core';\nexport type {\n AutoCollapse,\n LayoutStoreOptions,\n Json,\n Capability,\n Axis,\n Pane,\n Group,\n Split,\n Node,\n WindowPlacement,\n Popout,\n Layout as LayoutSnapshot,\n Issue,\n CommandOptions,\n Change,\n Bounds,\n JoinOptions,\n} from 'quilt-core';\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class Scope {
|
|
2
|
+
private tasks;
|
|
3
|
+
private ended;
|
|
4
|
+
add(cleanup: () => void): void;
|
|
5
|
+
listen(target: EventTarget, type: string, listener: EventListener, options?: AddEventListenerOptions): void;
|
|
6
|
+
dispose(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function syncChildren(parent: HTMLElement, children: HTMLElement[]): void;
|
|
9
|
+
export declare function el<K extends keyof HTMLElementTagNameMap>(doc: Document, tag: K, className: string, text?: string): HTMLElementTagNameMap[K];
|
|
10
|
+
//# sourceMappingURL=lifetime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifetime.d.ts","sourceRoot":"","sources":["../src/lifetime.ts"],"names":[],"mappings":"AAAA,qBAAa,KAAK;IAChB,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,KAAK,CAAS;IACtB,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI;IAIvB,MAAM,CACJ,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,uBAAuB;IAKnC,OAAO;CAWR;AACD,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,QAMxE;AACD,wBAAgB,EAAE,CAAC,CAAC,SAAS,MAAM,qBAAqB,EACtD,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,CAAC,EACN,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,qBAAqB,CAAC,CAAC,CAAC,CAK1B"}
|
package/dist/lifetime.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class Scope {
|
|
2
|
+
tasks = [];
|
|
3
|
+
ended = false;
|
|
4
|
+
add(cleanup) {
|
|
5
|
+
if (this.ended)
|
|
6
|
+
cleanup();
|
|
7
|
+
else
|
|
8
|
+
this.tasks.push(cleanup);
|
|
9
|
+
}
|
|
10
|
+
listen(target, type, listener, options) {
|
|
11
|
+
target.addEventListener(type, listener, options);
|
|
12
|
+
this.add(() => target.removeEventListener(type, listener, options));
|
|
13
|
+
}
|
|
14
|
+
dispose() {
|
|
15
|
+
if (this.ended)
|
|
16
|
+
return;
|
|
17
|
+
this.ended = true;
|
|
18
|
+
for (const task of this.tasks.splice(0).reverse()) {
|
|
19
|
+
try {
|
|
20
|
+
task();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* Complete the remaining cleanup even if a consumer fails. */
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function syncChildren(parent, children) {
|
|
29
|
+
children.forEach((child, i) => {
|
|
30
|
+
if (parent.children[i] !== child)
|
|
31
|
+
parent.insertBefore(child, parent.children[i] ?? null);
|
|
32
|
+
});
|
|
33
|
+
for (const child of Array.from(parent.children))
|
|
34
|
+
if (!children.includes(child))
|
|
35
|
+
child.remove();
|
|
36
|
+
}
|
|
37
|
+
export function el(doc, tag, className, text) {
|
|
38
|
+
const node = doc.createElement(tag);
|
|
39
|
+
node.className = className;
|
|
40
|
+
if (text !== undefined)
|
|
41
|
+
node.textContent = text;
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=lifetime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifetime.js","sourceRoot":"","sources":["../src/lifetime.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,KAAK;IACR,KAAK,GAAmB,EAAE,CAAC;IAC3B,KAAK,GAAG,KAAK,CAAC;IACtB,GAAG,CAAC,OAAmB;QACrB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAC;;YACrB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,CACJ,MAAmB,EACnB,IAAY,EACZ,QAAuB,EACvB,OAAiC;QAEjC,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,OAAO;QACL,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAClD,IAAI,CAAC;gBACH,IAAI,EAAE,CAAC;YACT,CAAC;YAAC,MAAM,CAAC;gBACP,8DAA8D;YAChE,CAAC;QACH,CAAC;IACH,CAAC;CACF;AACD,MAAM,UAAU,YAAY,CAAC,MAAmB,EAAE,QAAuB;IACvE,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK;YAAE,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAoB,CAAC;YAAE,KAAK,CAAC,MAAM,EAAE,CAAC;AACjE,CAAC;AACD,MAAM,UAAU,EAAE,CAChB,GAAa,EACb,GAAM,EACN,SAAiB,EACjB,IAAa;IAEb,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC3B,IAAI,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAChD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["export class Scope {\n private tasks: (() => void)[] = [];\n private ended = false;\n add(cleanup: () => void) {\n if (this.ended) cleanup();\n else this.tasks.push(cleanup);\n }\n listen(\n target: EventTarget,\n type: string,\n listener: EventListener,\n options?: AddEventListenerOptions,\n ) {\n target.addEventListener(type, listener, options);\n this.add(() => target.removeEventListener(type, listener, options));\n }\n dispose() {\n if (this.ended) return;\n this.ended = true;\n for (const task of this.tasks.splice(0).reverse()) {\n try {\n task();\n } catch {\n /* Complete the remaining cleanup even if a consumer fails. */\n }\n }\n }\n}\nexport function syncChildren(parent: HTMLElement, children: HTMLElement[]) {\n children.forEach((child, i) => {\n if (parent.children[i] !== child) parent.insertBefore(child, parent.children[i] ?? null);\n });\n for (const child of Array.from(parent.children))\n if (!children.includes(child as HTMLElement)) child.remove();\n}\nexport function el<K extends keyof HTMLElementTagNameMap>(\n doc: Document,\n tag: K,\n className: string,\n text?: string,\n): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n node.className = className;\n if (text !== undefined) node.textContent = text;\n return node;\n}\n"]}
|
package/dist/menu.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Group, Pane } from 'quilt-core';
|
|
2
|
+
import type { LayoutOptions } from './types.js';
|
|
3
|
+
import type { Windows } from './windows.js';
|
|
4
|
+
export declare function createPaneMenu(root: HTMLElement, options: LayoutOptions, windows: Pick<Windows, 'pending' | 'open'>, refresh: () => void, report: (e: unknown) => void): {
|
|
5
|
+
open: (anchor: HTMLElement, pane: Pane | undefined, group: Group, direction?: "left" | "right" | "top" | "bottom", ratio?: number, groupActions?: boolean, addTab?: boolean) => void;
|
|
6
|
+
addTab(anchor: HTMLElement, group: Group): void;
|
|
7
|
+
openEmpty(anchor: HTMLElement, group: Group): void;
|
|
8
|
+
dispose(): void;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../src/menu.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,EAC1C,OAAO,EAAE,MAAM,IAAI,EACnB,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI;mBAsBlB,WAAW,QACb,IAAI,GAAG,SAAS,SACf,KAAK,cACA,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ;mBAkXhC,WAAW,SAAS,KAAK;sBAYtB,WAAW,SAAS,KAAK;;EAW9C"}
|