@n-uf/placement 26.11.7 → 26.11.9
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/CHANGELOG.md +38 -0
- package/README.md +81 -15
- package/dist/{chunk-NNFNMIW4.js → chunk-PWQWD2CS.js} +159 -27
- package/dist/chunk-PWQWD2CS.js.map +1 -0
- package/dist/chunk-XUV6MJ2P.js +138 -0
- package/dist/chunk-XUV6MJ2P.js.map +1 -0
- package/dist/desk.d.ts +42 -0
- package/dist/desk.js +83 -0
- package/dist/desk.js.map +1 -0
- package/dist/panels.d.ts +7 -3
- package/dist/panels.js +9 -118
- package/dist/panels.js.map +1 -1
- package/dist/{targets-BEqoVKbF.d.ts → targets-DwOTs4UC.d.ts} +62 -11
- package/dist/targets.d.ts +1 -1
- package/dist/targets.js +1 -1
- package/package.json +6 -2
- package/dist/chunk-NNFNMIW4.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,44 @@
|
|
|
3
3
|
Versions are calendar versions, `YY.M.R` (year, month of the release, counter within the month), shared with
|
|
4
4
|
`@n-uf/motion`: both packages carry the same version, and a release bumps both.
|
|
5
5
|
|
|
6
|
+
## 26.11.9
|
|
7
|
+
|
|
8
|
+
- `@n-uf/placement/panels`:
|
|
9
|
+
- Tabs: `Column.show` (`"stack"`, the default, or `"tabs"`) and `Column.active`. A tabbed column draws a strip
|
|
10
|
+
`options.tabs` tall (new chrome size, default 28; `PlacedColumn.strip`); its active panel takes the rest and the
|
|
11
|
+
others are `hidden`, each with its `tab` rect in the strip. It folds as a whole: its active panel folded shows only
|
|
12
|
+
the strip, every panel folded goes to the rail (or rides). `showTabs(state, side, index, on)`, `activate(state, id)`
|
|
13
|
+
(a use). A drop `into` a tabbed column adds a tab at the end and shows it, and so does `open` on a panel in one; a
|
|
14
|
+
panel that leaves takes its `active` mark with it. With no `active` (or one that isn't there), the column shows its
|
|
15
|
+
first panel that isn't closed.
|
|
16
|
+
- Height give-way, opt-in: `PanelDef.height` (`{ min, natural }`). In a stack column where an open section has one,
|
|
17
|
+
sections get their natural heights and the last open one the rest (never under its min); sections without one
|
|
18
|
+
share what is left, never under a bar's height. The others' natural heights give way toward their mins first;
|
|
19
|
+
still short of room, open sections fold to bars in give-way policy order (kept last, never the last open one, never
|
|
20
|
+
one whose min is no more than a bar), note `section-folded`. It never writes `folded`. Columns without `height`
|
|
21
|
+
defs are unchanged.
|
|
22
|
+
- The stored form carries a column's `show` and `active` (optional v1 fields); an `active` that isn't in its column
|
|
23
|
+
is dropped as `"column.active:<id>"`, an unknown `show` as `"column.show"`.
|
|
24
|
+
- New entry `@n-uf/placement/desk`: presets as data on `./panels` (`docks`, `dockTear`, `navigator`; `PRESETS`,
|
|
25
|
+
`presetLayout`, `presetTargets`, `presetDrop`, `presetInput`). It imports only `./panels`' public index (a guard test
|
|
26
|
+
holds it to that) and carries no titles, words or toolbar wiring. `./dock` is unchanged.
|
|
27
|
+
|
|
28
|
+
## 26.11.8
|
|
29
|
+
|
|
30
|
+
- `@n-uf/placement/panels`:
|
|
31
|
+
- Folded floats: a float folded by hand lays out as a title bar (`kind` `"bar"`, `folded` `"hand"`) at its own x, y
|
|
32
|
+
and width, `options.bar` tall, clamped as a bar, pinned or not. It is still a float to targets and drags; a drop
|
|
33
|
+
moves it and keeps its full size, and unfolding brings the full rect back (the stored rect never changes). A
|
|
34
|
+
folded docked panel dropped on the float target stays folded: it lands as a title bar at its natural float size.
|
|
35
|
+
- `options.fold`: `"rail"` (default, as before) or `"ride"`. Riding, a column folded by hand on a side that still
|
|
36
|
+
has an open column becomes title bars stacked on top of the side's first open column (`rides`: its index), as
|
|
37
|
+
wide as it; the open columns lie as if the folded one were gone, and its stored place and width are untouched.
|
|
38
|
+
When every column on the side is folded, the side shows its rail as before. Give-way's folds always go to the rail.
|
|
39
|
+
- `PlacedPanel` gains `floating` (open or folded float) and `rides`; `PlacedColumn` gains `rides`.
|
|
40
|
+
- `pin(…, shown)` takes only the position of `shown` and keeps the stored size, so pinning a folded or clamped
|
|
41
|
+
float keeps its full size.
|
|
42
|
+
- A column too short for its bars gives its open panels a height of 0, never a negative one.
|
|
43
|
+
|
|
6
44
|
## 26.11.7
|
|
7
45
|
|
|
8
46
|
- New entry `@n-uf/placement/panels`: tool panels with string ids, as an app's side columns work. `./dock` is
|
package/README.md
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
> [!CAUTION]
|
|
4
4
|
> ## Not yet stable
|
|
5
5
|
>
|
|
6
|
-
> **@n-uf/placement is young and still changing. Its API (the desk constructions, the toolbar model and the drop targets) is not final.**
|
|
6
|
+
> **@n-uf/placement is young and still changing. Its API (the desk constructions, the panel columns, the toolbar model and the drop targets) is not final.**
|
|
7
7
|
>
|
|
8
8
|
> Version numbers are dates (YY.M.R), not semantic versions, so any release may break things. Pin an exact version and read the [CHANGELOG](https://github.com/n-uf/motion/blob/main/packages/placement/CHANGELOG.md) before you upgrade.
|
|
9
9
|
|
|
10
10
|
Site and live demos: [motion.n-uf.com](https://motion.n-uf.com)
|
|
11
11
|
|
|
12
|
-
Where things go. State in, rects and drop targets out: a desk of docks, rails and palettes,
|
|
13
|
-
and zones, and the drop targets for each. A tiling entry (a split
|
|
14
|
-
planned. Pure functions over plain data: no DOM,
|
|
15
|
-
|
|
12
|
+
Where things go. State in, rects and drop targets out: a desk of docks, rails and palettes, panels in columns per
|
|
13
|
+
side that give way by age, toolbar groups in hosts and zones, and the drop targets for each. A tiling entry (a split
|
|
14
|
+
tree with ratios, groups and workspaces) is planned. Pure functions over plain data: no DOM, no framework, no
|
|
15
|
+
motion, no dependencies.
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
npm i @n-uf/placement
|
|
@@ -34,9 +34,11 @@ without importing each other: a target list from `@n-uf/placement/targets` is ex
|
|
|
34
34
|
- **`/targets`**: drop targets (`{ id, priority, hit, slot?, label?, free? }`) for the desk's constructions,
|
|
35
35
|
toolbar hosts (each with its `slot`, and `allows` to leave out places a group may not take) and pane zones; and
|
|
36
36
|
`keyboardStops`, one ordered list of places for the arrow keys and a "Move to" menu.
|
|
37
|
-
- **`/panels`**: tool panels with string ids: full-height columns per side, each with its own width
|
|
38
|
-
title bar or a rail; give-way by age when the pages run out of room
|
|
39
|
-
their drop targets; and a
|
|
37
|
+
- **`/panels`**: tool panels with string ids: full-height columns per side, each with its own width, shown as a
|
|
38
|
+
stack of sections or as tabs; folding to a title bar or a rail; give-way by age when the pages run out of room (and,
|
|
39
|
+
opt-in, when a column runs out of height); resizable floats, some pinned to the view; their drop targets; and a
|
|
40
|
+
stored form.
|
|
41
|
+
- **`/desk`**: desk presets (`docks`, `dockTear`, `navigator`) as data on `/panels`: options and a start state.
|
|
40
42
|
|
|
41
43
|
Rendering stays in the app: it draws the rects, measures its groups, and feeds pointer samples to a drag intent.
|
|
42
44
|
|
|
@@ -160,9 +162,11 @@ console.log(stops.map((s) => (s.home ? "home" : s.id))); // [ 'g:top:start:0', '
|
|
|
160
162
|
`@n-uf/placement/panels` lays out tool panels the way an app's side columns work: each side holds full-height
|
|
161
163
|
columns from the outer edge inward, each column one or more panels and its own width (clamped to its panels'
|
|
162
164
|
`min` and `max`). A panel folded by hand is a title bar; a column whose panels are all folded becomes segments of
|
|
163
|
-
its side's rail
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
its side's rail, or with `fold: "ride"` bars on the side's first open column. Panels may float instead, in page space
|
|
166
|
+
(they move with the page's offset and scale) or pinned to the view; a folded float is a title bar where it floats, and
|
|
167
|
+
a folded panel torn off stays folded. The output is geometry and structure only: each panel's rect, layer, `kind`
|
|
168
|
+
(`column`, `bar`, `segment`, `float`; a folded float is a `bar`) and its folded, kept, pinned, floating and rides
|
|
169
|
+
flags, the columns, the rails, the pages' rect and coded notes. Chrome sizes
|
|
166
170
|
(`bar`, `rail`, `segment`) are options.
|
|
167
171
|
|
|
168
172
|
```ts
|
|
@@ -196,11 +200,38 @@ even at its minimum size keeps its title bar and `floatVisible` (default 48) px
|
|
|
196
200
|
size is screen px and never scales. A pinned float is clamped where it is; an unpinned one has its page-space position
|
|
197
201
|
clamped so that its screen rect at pan 0 fits the view, so it still scrolls with the page. The clamp runs at layout
|
|
198
202
|
time and the state keeps the stored rect, so a larger view brings a float back where it was. `"free"` never moves a
|
|
199
|
-
float.
|
|
203
|
+
float. A float folded by hand (`toggleFold`) lays out as a title bar: `kind` `"bar"`, `folded` `"hand"`, at its own x,
|
|
204
|
+
y and width and `options.bar` tall, clamped as a bar. It is still a float (`floating: true`) to its targets and drags,
|
|
205
|
+
pinned or not, and unfolding brings its full rect back: the stored rect never changes.
|
|
206
|
+
|
|
207
|
+
**Folded columns** (`options.fold`, default `"rail"`): a column folded by hand goes to its side's rail. With `"ride"`,
|
|
208
|
+
it rides instead while its side still has an open column: its panels become title bars (`kind` `"bar"`, `rides`: the
|
|
209
|
+
host's index) stacked in column order on top of the side's first open column (outer edge first), as wide as it, and
|
|
210
|
+
the host's open panels share the rest of the height. The open columns lie as if the folded one were gone (the pages
|
|
211
|
+
gain its room), and its stored place and width are untouched, so unfolding puts it back as its own column. Only when
|
|
212
|
+
every column on a side is folded does the side show its rail. Give-way's folds (`column-folded`, `kept-folded`) go to
|
|
213
|
+
the rail in both modes.
|
|
214
|
+
|
|
215
|
+
**Tabs**: a column with `show: "tabs"` shows one panel at a time, its `active` one (default, or when `active` isn't
|
|
216
|
+
one of them: the first panel in the column that isn't closed), under a strip
|
|
217
|
+
`options.tabs` tall (default 28). The strip is `PlacedColumn.strip`, and each panel's `tab` is its header's rect in it
|
|
218
|
+
(panel order, equal widths); the others are `hidden`. A tabbed column folds as a whole: with its active panel in
|
|
219
|
+
`folded` it shows only its strip (the active panel is a `"bar"` there); with every panel folded it goes to the rail,
|
|
220
|
+
or rides, as a stack column does. `showTabs(state, side, index, on)` switches a column; `activate(state, id)` shows a
|
|
221
|
+
tab and counts as a use. A drop `into` a tabbed column adds a tab at the end and shows it, and `open` on a panel in a
|
|
222
|
+
tabbed column shows its tab too.
|
|
223
|
+
|
|
224
|
+
**Section heights** (opt-in): a `PanelDef.height` (`{ min, natural }`) gives a section of a stack column its natural
|
|
225
|
+
height; the column's last open section takes the rest, never under its min, and sections without one share what is
|
|
226
|
+
left equally, never under a bar's height (`options.bar`). The others' natural heights give way toward their mins
|
|
227
|
+
first. When the open sections' mins and the bars still don't fit, open sections fold to bars by the give-way policy
|
|
228
|
+
(kept last, never the last open one, and never one whose min is no more than a bar, since folding it gains nothing),
|
|
229
|
+
each with a `section-folded` note; that never writes `folded`, so a taller stage unfolds them. Columns without
|
|
230
|
+
`height` defs lay out as before.
|
|
200
231
|
|
|
201
232
|
**Changes** are pure: `open`, `close`, `focus`, `toggleFold`, `keep`, `dock(state, id, side, index, { into?, width? })`,
|
|
202
233
|
`float`, `pin(state, id, on, view?, shown?)` (pass the layout's rect as `shown` so a clamped float pins where it
|
|
203
|
-
shows), `resizeColumn`, `resizeFloat`.
|
|
234
|
+
shows), `resizeColumn`, `resizeFloat`, `showTabs`, `activate`.
|
|
204
235
|
|
|
205
236
|
**Drop targets**: `panelTargets({ defs, state, id, stage, options?, allows? })` gives `column:<side>:<i>` (a new
|
|
206
237
|
column as wide as the panel's own width), `into:<side>:<i>` (stack into a column), `rail:<side>` (dock folded) and
|
|
@@ -231,18 +262,53 @@ console.log(stops.map((s) => (s.home ? "home" : s.id))); // [ 'rail:left', 'colu
|
|
|
231
262
|
parsed data back and never throws. Anything but an object of version 1 is `{ ok: false, error }`; inside one, unknown
|
|
232
263
|
or repeated ids and malformed entries are dropped and listed in `dropped`, and widths and float sizes are clamped to
|
|
233
264
|
the defs. Version 1 only gains optional fields: a field this reader doesn't know is skipped and listed in `dropped`
|
|
234
|
-
too (`"theme"`, `"sides.bottom"`, `"column.<field>"`, `"float.<field>"`, `"clock.<id>"`).
|
|
235
|
-
|
|
265
|
+
too (`"theme"`, `"sides.bottom"`, `"column.<field>"`, `"float.<field>"`, `"clock.<id>"`). A column's `show` and
|
|
266
|
+
`active` are carried; an `active` that isn't one of the column's panels is dropped as `"column.active:<id>"`. The note
|
|
267
|
+
codes (`PanelsNoteCode`):
|
|
236
268
|
|
|
237
269
|
| Code | Says | Also |
|
|
238
270
|
|---|---|---|
|
|
239
271
|
| `column-shrunk` | a column narrowed toward its minimum | `side`, `column`, `items`, `from`, `to` |
|
|
240
272
|
| `column-folded` | a column folded to its side's rail | `side`, `column`, `items` |
|
|
241
273
|
| `kept-folded` | a kept column folded: the pages needed the room | `side`, `column`, `items` |
|
|
274
|
+
| `section-folded` | a section of a stack column folded to a bar: the column was short | `side`, `column`, `items` |
|
|
242
275
|
| `pages-under-min` | the pages are under `pagesMin` with every column folded | `width`, `min` |
|
|
243
276
|
| `float-resized` | a float shrank to fit the view | `items` |
|
|
244
277
|
| `float-clamped` | a float moved to stay in the view | `items` |
|
|
245
278
|
|
|
279
|
+
## Desk presets
|
|
280
|
+
|
|
281
|
+
`@n-uf/placement/desk` holds desk presets as data on `./panels`: each is panels options and a start state. It imports
|
|
282
|
+
only the panels entry, carries no titles or words and wires no toolbar; the app names the presets and draws them.
|
|
283
|
+
|
|
284
|
+
| Preset | Columns and floats |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `docks` | one stack column per side, sections at their natural heights when the defs give `height`, folding to the rail; no floats |
|
|
287
|
+
| `dockTear` | as `docks`, and panels may float (tear-off and dock-back); one panel floats at the start |
|
|
288
|
+
| `navigator` | a tabbed column on the left, a stack column on the right; no floats |
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { PRESETS, presetLayout, presetTargets } from "@n-uf/placement/desk";
|
|
292
|
+
import type { PanelDef } from "@n-uf/placement/panels";
|
|
293
|
+
|
|
294
|
+
const defs: PanelDef[] = [
|
|
295
|
+
{ id: "files", width: { min: 200, natural: 240, max: 360 }, height: { min: 80, natural: 160 } },
|
|
296
|
+
{ id: "outline", width: { min: 200, natural: 240, max: 360 }, height: { min: 80, natural: 200 } },
|
|
297
|
+
{ id: "history", width: { min: 200, natural: 240, max: 360 } },
|
|
298
|
+
];
|
|
299
|
+
const nav = PRESETS.navigator;
|
|
300
|
+
const state = nav.start(defs, { left: ["files", "outline"] });
|
|
301
|
+
const layout = presetLayout(nav, defs, state, { width: 1200, height: 600 });
|
|
302
|
+
console.log(layout.columns[0]!.strip, layout.items.get("outline")!.hidden); // { x: 0, y: 0, width: 240, height: 28 } true
|
|
303
|
+
const targets = presetTargets(nav, { defs, state, id: "history", stage: { width: 1200, height: 600 } });
|
|
304
|
+
console.log(targets.some((t) => t.id === "float")); // false
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`presetLayout(preset, defs, state, stage, options?)` is `layoutPanels` with the preset's options under the caller's;
|
|
308
|
+
`presetTargets(preset, input)` and `presetDrop(preset, input, target, point, grab)` are `panelTargets` and `panelDrop`
|
|
309
|
+
the same way, offering only the places the preset allows (`presetInput` gives that input). `./dock` stays as it is for
|
|
310
|
+
now.
|
|
311
|
+
|
|
246
312
|
## Pane zones
|
|
247
313
|
|
|
248
314
|
`paneZone(rect, point, previous, options?)` says which of a pane's five zones a point is in: the centre rectangle
|
|
@@ -8,13 +8,15 @@ var IDENTITY_VIEW = { x: 0, y: 0, scale: 1 };
|
|
|
8
8
|
var DEFAULT_PANELS_OPTIONS = {
|
|
9
9
|
giveWay: { order: "oldest", by: "used" },
|
|
10
10
|
floats: "clamp",
|
|
11
|
+
fold: "rail",
|
|
11
12
|
floatVisible: 48,
|
|
12
13
|
pagesMin: 320,
|
|
13
14
|
view: IDENTITY_VIEW,
|
|
14
15
|
bar: 28,
|
|
15
16
|
rail: 36,
|
|
16
17
|
segment: 36,
|
|
17
|
-
railInset: 4
|
|
18
|
+
railInset: 4,
|
|
19
|
+
tabs: 28
|
|
18
20
|
};
|
|
19
21
|
var FLOAT_HEIGHT = 360;
|
|
20
22
|
var FLOAT_MIN = { width: 160, height: 120 };
|
|
@@ -67,12 +69,18 @@ function createPanels(defs, start) {
|
|
|
67
69
|
return { sides, floats, folded: [...start.folded ?? []], kept: [...start.kept ?? []], closed: [...start.closed ?? []], clock };
|
|
68
70
|
}
|
|
69
71
|
function liftPanel(state, id) {
|
|
70
|
-
const
|
|
72
|
+
const out = (c) => {
|
|
73
|
+
const { active: _a, ...rest } = c;
|
|
74
|
+
return { ...c.active === id ? rest : c, panels: without(c.panels, id) };
|
|
75
|
+
};
|
|
76
|
+
const lift = (cols) => cols.map(out).filter((c) => c.panels.length > 0);
|
|
71
77
|
return { ...state, sides: { left: lift(state.sides.left), right: lift(state.sides.right) }, floats: state.floats.filter((f) => f.id !== id) };
|
|
72
78
|
}
|
|
73
79
|
function open(state, id, at) {
|
|
74
80
|
let s = { ...state, closed: without(state.closed, id) };
|
|
75
81
|
if (at && whereIs(s, id).kind === "none") s = { ...s, sides: { ...s.sides, [at.side]: [...s.sides[at.side], { panels: [id], width: at.width }] } };
|
|
82
|
+
const w = whereIs(s, id);
|
|
83
|
+
if (w.kind === "column" && s.sides[w.side][w.index].show === "tabs") s = { ...s, sides: { ...s.sides, [w.side]: s.sides[w.side].map((c, i) => i === w.index ? { ...c, active: id } : c) } };
|
|
76
84
|
return bump(s, id, true);
|
|
77
85
|
}
|
|
78
86
|
var close = (state, id) => ({ ...state, closed: withOnce(state.closed, id), kept: without(state.kept, id) });
|
|
@@ -94,13 +102,25 @@ function dock(state, id, side, index, options = {}) {
|
|
|
94
102
|
const from = w.kind === "column" ? state.sides[w.side][w.index].width : 0;
|
|
95
103
|
const s = liftPanel(state, id);
|
|
96
104
|
const cols = [...s.sides[side]];
|
|
97
|
-
if (options.into && cols[index]) cols[index] = { ...cols[index], panels: [...cols[index].panels, id] };
|
|
105
|
+
if (options.into && cols[index]) cols[index] = { ...cols[index], panels: [...cols[index].panels, id], ...cols[index].show === "tabs" ? { active: id } : {} };
|
|
98
106
|
else {
|
|
99
107
|
const at = Math.max(0, Math.min(index, cols.length));
|
|
100
108
|
cols.splice(at, 0, { panels: [id], width: options.width ?? from });
|
|
101
109
|
}
|
|
102
110
|
return bump({ ...s, sides: { ...s.sides, [side]: cols }, closed: without(s.closed, id) }, id, false);
|
|
103
111
|
}
|
|
112
|
+
function showTabs(state, side, index, on) {
|
|
113
|
+
const col = state.sides[side][index];
|
|
114
|
+
if (!col) return state;
|
|
115
|
+
const { show: _s, ...stack } = col;
|
|
116
|
+
return { ...state, sides: { ...state.sides, [side]: state.sides[side].map((c, i) => i === index ? on ? { ...col, show: "tabs" } : stack : c) } };
|
|
117
|
+
}
|
|
118
|
+
function activate(state, id) {
|
|
119
|
+
const w = whereIs(state, id);
|
|
120
|
+
if (w.kind !== "column") return state;
|
|
121
|
+
const sides = { ...state.sides, [w.side]: state.sides[w.side].map((c, i) => i === w.index ? { ...c, active: id } : c) };
|
|
122
|
+
return bump({ ...state, sides }, id, false);
|
|
123
|
+
}
|
|
104
124
|
function float(state, id, rect2, pinned) {
|
|
105
125
|
const was = state.floats.find((f2) => f2.id === id);
|
|
106
126
|
const on = pinned ?? !!was?.pinned;
|
|
@@ -115,7 +135,7 @@ function pin(state, id, on, view = IDENTITY_VIEW, shown) {
|
|
|
115
135
|
...state,
|
|
116
136
|
floats: state.floats.map((f) => {
|
|
117
137
|
if (f.id !== id || !!f.pinned === on) return f;
|
|
118
|
-
const seen = shown ? { ...shown } : f.pinned ? { ...f.rect } : toView(f.rect, view);
|
|
138
|
+
const seen = shown ? { ...f.rect, x: shown.x, y: shown.y } : f.pinned ? { ...f.rect } : toView(f.rect, view);
|
|
119
139
|
return on ? { id, rect: seen, pinned: true } : { id, rect: toPage(seen, view) };
|
|
120
140
|
})
|
|
121
141
|
};
|
|
@@ -140,7 +160,9 @@ function columnWidth(defs, col) {
|
|
|
140
160
|
return Math.min(Math.max(Number.isFinite(col.width) ? col.width : lo, lo), hi);
|
|
141
161
|
}
|
|
142
162
|
function samePlaces(a, b, defs) {
|
|
143
|
-
const cols = (s) => SIDES.map(
|
|
163
|
+
const cols = (s) => SIDES.map(
|
|
164
|
+
(side) => s.sides[side].map((c) => `${c.panels.join(",")}@${Math.round(columnWidth(defs, c))}${c.show === "tabs" ? `/tabs:${c.active ?? c.panels[0]}` : ""}`).join("|")
|
|
165
|
+
).join("\u2016");
|
|
144
166
|
const fl = (s) => s.floats.map((f) => `${f.id}@${Math.round(f.rect.x)},${Math.round(f.rect.y)},${Math.round(f.rect.width)},${Math.round(f.rect.height)}${f.pinned ? "p" : ""}`).join("|");
|
|
145
167
|
const set = (l) => [...l].sort().join(",");
|
|
146
168
|
return cols(a) === cols(b) && fl(a) === fl(b) && set(a.folded) === set(b.folded) && set(a.kept) === set(b.kept) && set(a.closed) === set(b.closed);
|
|
@@ -159,6 +181,7 @@ function layoutPanels(defs, state, stage, options = {}) {
|
|
|
159
181
|
const folded = new Set(state.folded);
|
|
160
182
|
const kept = new Set(state.kept);
|
|
161
183
|
const by = opt.giveWay.by;
|
|
184
|
+
const ride = opt.fold === "ride";
|
|
162
185
|
const cols = [];
|
|
163
186
|
for (const side of SIDES) {
|
|
164
187
|
state.sides[side].forEach((c, index) => {
|
|
@@ -169,16 +192,74 @@ function layoutPanels(defs, state, stage, options = {}) {
|
|
|
169
192
|
const max = Math.max(min, Math.min(...ds.map((d) => d?.width.max ?? Infinity)));
|
|
170
193
|
const width = clamp(Number.isFinite(c.width) ? c.width : min, min, max);
|
|
171
194
|
const age = Math.max(...panels.map((id) => state.clock[id]?.[by] ?? 0));
|
|
172
|
-
|
|
195
|
+
const tabs = c.show === "tabs" ? { show: "tabs", active: c.active !== void 0 && panels.includes(c.active) ? c.active : panels[0] } : {};
|
|
196
|
+
cols.push({ side, index, panels, min, max, width, hand: panels.every((id) => folded.has(id)), kept: panels.some((id) => kept.has(id)), age, ...tabs });
|
|
173
197
|
});
|
|
174
198
|
}
|
|
199
|
+
const sectionHeights = (c, room) => {
|
|
200
|
+
const out = /* @__PURE__ */ new Map();
|
|
201
|
+
const open0 = c.panels.filter((id) => !folded.has(id));
|
|
202
|
+
const hOf = (id) => defOf(defs, id)?.height;
|
|
203
|
+
const auto = /* @__PURE__ */ new Set();
|
|
204
|
+
if (open0.some((id) => hOf(id))) {
|
|
205
|
+
const last = open0.at(-1);
|
|
206
|
+
const minOf = (id) => hOf(id)?.min ?? opt.bar;
|
|
207
|
+
const fits = () => (c.panels.length - open0.length + auto.size) * opt.bar + open0.filter((id) => !auto.has(id)).reduce((a, id) => a + minOf(id), 0) <= room;
|
|
208
|
+
const rank = (ids) => [...ids].sort((a, b) => {
|
|
209
|
+
const age = (id) => state.clock[id]?.[by] ?? 0;
|
|
210
|
+
return Number(kept.has(a)) - Number(kept.has(b)) || (opt.giveWay.order === "oldest" ? age(a) - age(b) : age(b) - age(a)) || c.panels.indexOf(a) - c.panels.indexOf(b);
|
|
211
|
+
});
|
|
212
|
+
for (const id of rank(open0.filter((x) => x !== last))) {
|
|
213
|
+
if (fits()) break;
|
|
214
|
+
if (minOf(id) > opt.bar) auto.add(id);
|
|
215
|
+
}
|
|
216
|
+
const open2 = open0.filter((id) => !auto.has(id));
|
|
217
|
+
const space = room - (c.panels.length - open2.length) * opt.bar;
|
|
218
|
+
const fixed = open2.filter((id) => id !== last && hOf(id));
|
|
219
|
+
const sharers = open2.filter((id) => id === last || !hOf(id));
|
|
220
|
+
const h = new Map(fixed.map((id) => [id, hOf(id).natural]));
|
|
221
|
+
let rest = space - fixed.reduce((a, id) => a + h.get(id), 0);
|
|
222
|
+
const need = sharers.reduce((a, id) => a + minOf(id), 0);
|
|
223
|
+
for (const id of rank(fixed)) {
|
|
224
|
+
if (rest >= need) break;
|
|
225
|
+
const take = Math.min(need - rest, h.get(id) - minOf(id));
|
|
226
|
+
h.set(id, h.get(id) - take);
|
|
227
|
+
rest += take;
|
|
228
|
+
}
|
|
229
|
+
if (rest >= need) {
|
|
230
|
+
let pool = [...sharers];
|
|
231
|
+
let left = rest;
|
|
232
|
+
for (; ; ) {
|
|
233
|
+
const share2 = left / pool.length;
|
|
234
|
+
const under = pool.filter((id) => minOf(id) > share2);
|
|
235
|
+
if (under.length === 0) {
|
|
236
|
+
for (const id of pool) h.set(id, share2);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
for (const id of under) h.set(id, minOf(id));
|
|
240
|
+
left -= under.reduce((a, id) => a + minOf(id), 0);
|
|
241
|
+
pool = pool.filter((id) => !under.includes(id));
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
const lastH = Math.max(0, Math.min(minOf(last), rest));
|
|
245
|
+
for (const id of sharers) h.set(id, id === last ? lastH : Math.max(0, rest - lastH) / (sharers.length - 1));
|
|
246
|
+
}
|
|
247
|
+
for (const id of c.panels) out.set(id, folded.has(id) ? { height: opt.bar, folded: "hand" } : auto.has(id) ? { height: opt.bar, folded: "auto" } : { height: Math.max(0, h.get(id)), folded: null });
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
const bars = c.panels.length - open0.length;
|
|
251
|
+
const share = Math.max(0, (room - bars * opt.bar) / open0.length);
|
|
252
|
+
for (const id of c.panels) out.set(id, folded.has(id) ? { height: opt.bar, folded: "hand" } : { height: share, folded: null });
|
|
253
|
+
return out;
|
|
254
|
+
};
|
|
175
255
|
const order = giveWayOrder(
|
|
176
256
|
cols.filter((c) => !c.hand),
|
|
177
257
|
opt.giveWay.order
|
|
178
258
|
);
|
|
179
259
|
const fit = (k) => {
|
|
180
260
|
const auto = new Set(order.slice(0, k));
|
|
181
|
-
const
|
|
261
|
+
const open2 = (side) => cols.some((c) => c.side === side && !c.hand && !auto.has(c));
|
|
262
|
+
const railed = (side) => cols.some((c) => c.side === side && (auto.has(c) || c.hand && !(ride && open2(side))));
|
|
182
263
|
const widths = new Map(order.filter((c) => !auto.has(c)).map((c) => [c, c.width]));
|
|
183
264
|
let excess = [...widths.values()].reduce((a, b) => a + b, 0) + SIDES.filter(railed).length * opt.rail + opt.pagesMin - W;
|
|
184
265
|
for (const c of order) {
|
|
@@ -206,60 +287,108 @@ function layoutPanels(defs, state, stage, options = {}) {
|
|
|
206
287
|
let x1 = W;
|
|
207
288
|
for (const side of SIDES) {
|
|
208
289
|
const mine = cols.filter((c) => c.side === side);
|
|
209
|
-
const
|
|
290
|
+
const open2 = mine.filter((c) => !c.hand && !g.auto.has(c));
|
|
291
|
+
const host = ride ? open2[0] : void 0;
|
|
292
|
+
const riders = host ? mine.filter((c) => c.hand) : [];
|
|
293
|
+
const inRail = mine.filter((c) => c.hand && !riders.includes(c) || g.auto.has(c));
|
|
210
294
|
if (inRail.length) {
|
|
211
295
|
const r = side === "left" ? rect(0, 0, opt.rail, H) : rect(W - opt.rail, 0, opt.rail, H);
|
|
212
296
|
const segments = inRail.flatMap((c) => c.panels).map((id, i) => ({ id, rect: rect(r.x, opt.railInset + i * opt.segment, opt.rail, opt.segment) }));
|
|
213
297
|
rails[side] = { side, rect: r, segments };
|
|
214
298
|
for (const s of segments) {
|
|
215
299
|
const c = inRail.find((x) => x.panels.includes(s.id));
|
|
216
|
-
items.set(s.id, { rect: s.rect, kind: "segment", layer: 0, hidden: true, folded: c.hand ? "hand" : "auto", kept: kept.has(s.id), pinned: false, side, column: c.index });
|
|
300
|
+
items.set(s.id, { rect: s.rect, kind: "segment", layer: 0, hidden: true, folded: c.hand ? "hand" : "auto", kept: kept.has(s.id), pinned: false, floating: false, side, column: c.index });
|
|
217
301
|
}
|
|
218
302
|
if (side === "left") x0 = opt.rail;
|
|
219
303
|
else x1 = W - opt.rail;
|
|
220
304
|
}
|
|
305
|
+
const at = /* @__PURE__ */ new Map();
|
|
221
306
|
for (const c of mine) {
|
|
222
307
|
const w = g.widths.get(c);
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
r = rect(x0, 0, w, H);
|
|
308
|
+
if (w === void 0 || riders.includes(c)) continue;
|
|
309
|
+
if (side === "left") {
|
|
310
|
+
at.set(c, rect(x0, 0, w, H));
|
|
227
311
|
x0 += w;
|
|
228
312
|
} else {
|
|
229
313
|
x1 -= w;
|
|
230
|
-
|
|
314
|
+
at.set(c, rect(x1, 0, w, H));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const riding = riders.flatMap((c) => c.panels.map((id) => ({ id, c })));
|
|
318
|
+
const top = riding.length * opt.bar;
|
|
319
|
+
for (const c of mine) {
|
|
320
|
+
const w = g.widths.get(c);
|
|
321
|
+
const folded1 = c.hand ? "hand" : g.auto.has(c) ? "auto" : null;
|
|
322
|
+
const base = { side, index: c.index, width: c.width, min: c.min, max: c.max, folded: folded1, kept: c.kept, panels: c.panels };
|
|
323
|
+
if (riders.includes(c)) {
|
|
324
|
+
const hr = at.get(host);
|
|
325
|
+
const first = riding.findIndex((x) => x.c === c);
|
|
326
|
+
c.panels.forEach(
|
|
327
|
+
(id, i) => items.set(id, { rect: rect(hr.x, (first + i) * opt.bar, hr.width, opt.bar), kind: "bar", layer: 0, hidden: false, folded: "hand", kept: kept.has(id), pinned: false, floating: false, side, column: c.index, rides: host.index })
|
|
328
|
+
);
|
|
329
|
+
placedCols.push({ ...base, rect: rect(hr.x, first * opt.bar, hr.width, c.panels.length * opt.bar), shrunk: false, rides: host.index });
|
|
330
|
+
continue;
|
|
231
331
|
}
|
|
232
|
-
|
|
332
|
+
const r = at.get(c) ?? rect(side === "left" ? x0 : x1, 0, 0, H);
|
|
333
|
+
const y0 = c === host ? top : 0;
|
|
334
|
+
const tabbed = c.show === "tabs";
|
|
335
|
+
const strip = rect(r.x, y0, r.width, opt.tabs);
|
|
336
|
+
const shown = tabbed && w !== void 0 ? { strip, active: c.active } : {};
|
|
337
|
+
placedCols.push({ ...base, rect: r, shrunk: w !== void 0 && w < c.width, ...tabbed ? { show: "tabs" } : {}, ...shown });
|
|
233
338
|
if (w === void 0) continue;
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
339
|
+
const panel = (id, to, kind, f, more = {}) => {
|
|
340
|
+
items.set(id, { rect: to, kind, layer: 0, hidden: more.hidden ?? false, folded: f, kept: kept.has(id), pinned: false, floating: false, side, column: c.index, ...more.tab ? { tab: more.tab } : {} });
|
|
341
|
+
};
|
|
342
|
+
if (tabbed) {
|
|
343
|
+
const body = rect(r.x, y0 + opt.tabs, r.width, Math.max(0, H - y0 - opt.tabs));
|
|
344
|
+
const shade = folded.has(c.active);
|
|
345
|
+
const tw = r.width / c.panels.length;
|
|
346
|
+
c.panels.forEach((id, i) => {
|
|
347
|
+
const tab = rect(r.x + i * tw, y0, tw, opt.tabs);
|
|
348
|
+
if (id === c.active) panel(id, shade ? strip : body, shade ? "bar" : "column", shade ? "hand" : null, { tab });
|
|
349
|
+
else panel(id, body, "column", folded.has(id) ? "hand" : null, { tab, hidden: true });
|
|
350
|
+
});
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const hs = sectionHeights(c, H - y0);
|
|
354
|
+
let y = y0;
|
|
237
355
|
for (const id of c.panels) {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
y += h;
|
|
356
|
+
const s = hs.get(id);
|
|
357
|
+
panel(id, rect(r.x, y, r.width, s.height), s.folded ? "bar" : "column", s.folded);
|
|
358
|
+
y += s.height;
|
|
242
359
|
}
|
|
360
|
+
for (const id of c.panels) if (hs.get(id).folded === "auto") notes.push({ code: "section-folded", side, column: c.index, items: [id] });
|
|
243
361
|
}
|
|
244
362
|
}
|
|
245
363
|
const pages = rect(x0, 0, Math.max(0, x1 - x0), H);
|
|
246
364
|
if (pages.width < opt.pagesMin) notes.push({ code: "pages-under-min", width: pages.width, min: opt.pagesMin });
|
|
247
365
|
state.floats.forEach((f, i) => {
|
|
248
366
|
if (closed.has(f.id)) return;
|
|
249
|
-
const
|
|
367
|
+
const shaded = folded.has(f.id);
|
|
368
|
+
const at = shaded ? { ...f.rect, height: opt.bar } : f.rect;
|
|
250
369
|
const k = f.pinned ? 1 : opt.view.scale;
|
|
251
370
|
const r = { ...at };
|
|
252
371
|
if (opt.floats === "clamp") {
|
|
253
372
|
const lim = floatSize(defOf(defs, f.id)).min;
|
|
254
373
|
r.width = Math.max(lim.width, Math.min(r.width, W));
|
|
255
|
-
r.height = Math.max(lim.height, Math.min(r.height, H));
|
|
374
|
+
if (!shaded) r.height = Math.max(lim.height, Math.min(r.height, H));
|
|
256
375
|
if (r.width !== at.width || r.height !== at.height) notes.push({ code: "float-resized", items: [f.id] });
|
|
257
376
|
r.x = r.width <= W ? clamp(r.x, 0, (W - r.width) / k) : clamp(r.x, (opt.floatVisible - r.width) / k, (W - opt.floatVisible) / k);
|
|
258
377
|
r.y = r.height <= H ? clamp(r.y, 0, (H - r.height) / k) : clamp(r.y, 0, (H - opt.bar) / k);
|
|
259
378
|
if (r.x !== at.x || r.y !== at.y) notes.push({ code: "float-clamped", items: [f.id] });
|
|
260
379
|
}
|
|
261
380
|
const clamped = r.x !== at.x || r.y !== at.y || r.width !== at.width || r.height !== at.height;
|
|
262
|
-
items.set(f.id, {
|
|
381
|
+
items.set(f.id, {
|
|
382
|
+
rect: f.pinned ? r : toView(r, opt.view),
|
|
383
|
+
kind: shaded ? "bar" : "float",
|
|
384
|
+
layer: 10 + i,
|
|
385
|
+
hidden: false,
|
|
386
|
+
folded: shaded ? "hand" : null,
|
|
387
|
+
kept: false,
|
|
388
|
+
pinned: !!f.pinned,
|
|
389
|
+
floating: true,
|
|
390
|
+
...clamped ? { clamped: true } : {}
|
|
391
|
+
});
|
|
263
392
|
});
|
|
264
393
|
return { items, columns: placedCols, rails, notes, pages };
|
|
265
394
|
}
|
|
@@ -339,7 +468,8 @@ function panelTargets(input) {
|
|
|
339
468
|
(p, grab) => {
|
|
340
469
|
const at = rect(p.x - grab.x, p.y - grab.y, size.width, size.height);
|
|
341
470
|
const s = float(state, id, pinned ? at : toPage(at, opt.view), pinned);
|
|
342
|
-
const
|
|
471
|
+
const laid = layoutPanels(defs, s, stage, opt).items.get(id).rect;
|
|
472
|
+
const shown = s.folded.includes(id) ? { ...laid, width: at.width, height: at.height } : laid;
|
|
343
473
|
return float(state, id, pinned ? shown : toPage(shown, opt.view), pinned);
|
|
344
474
|
},
|
|
345
475
|
true
|
|
@@ -391,6 +521,8 @@ export {
|
|
|
391
521
|
toggleFold,
|
|
392
522
|
keep,
|
|
393
523
|
dock,
|
|
524
|
+
showTabs,
|
|
525
|
+
activate,
|
|
394
526
|
float,
|
|
395
527
|
toView,
|
|
396
528
|
toPage,
|
|
@@ -409,4 +541,4 @@ export {
|
|
|
409
541
|
panelDrop,
|
|
410
542
|
panelStops
|
|
411
543
|
};
|
|
412
|
-
//# sourceMappingURL=chunk-
|
|
544
|
+
//# sourceMappingURL=chunk-PWQWD2CS.js.map
|