@n-uf/placement 26.11.9 → 26.11.10
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 +33 -0
- package/README.md +89 -7
- package/dist/{chunk-XUV6MJ2P.js → chunk-7R5H2ABL.js} +38 -4
- package/dist/chunk-7R5H2ABL.js.map +1 -0
- package/dist/{chunk-PWQWD2CS.js → chunk-PJD5HKYX.js} +240 -25
- package/dist/chunk-PJD5HKYX.js.map +1 -0
- package/dist/desk.d.ts +12 -6
- package/dist/desk.js +35 -7
- package/dist/desk.js.map +1 -1
- package/dist/panels.d.ts +12 -3
- package/dist/panels.js +14 -2
- package/dist/{targets-DwOTs4UC.d.ts → targets-CLV69zMw.d.ts} +150 -22
- package/dist/targets.d.ts +1 -1
- package/dist/targets.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PWQWD2CS.js.map +0 -1
- package/dist/chunk-XUV6MJ2P.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
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.10
|
|
7
|
+
|
|
8
|
+
- `@n-uf/placement/panels`, bands and reserved room (all opt-in; a state and options without them lay out as before,
|
|
9
|
+
and the store stays v1):
|
|
10
|
+
- Bands: a top and a bottom dock (`options.bands`, `{ top?, bottom? }`, each `{ mode: "reserved" | "overlay",
|
|
11
|
+
span: "between" | "full" }`). Its panels sit side by side and share its width (`kind` `"band"`, `edge`, `index`);
|
|
12
|
+
a stored height clamped to `PanelDef.band` (default `BAND_SIZE`, min 120, max 480, natural 200). It folds as a
|
|
13
|
+
whole, to one bar. `layout.bands` (`PlacedBand`). New: `Edge`, `EDGES`, `Place`, `Band`, `BandOption`,
|
|
14
|
+
`PanelsState.bands`, `PanelsStart.top` / `bottom`, `dockBand`, `resizeBand`, `bandSize`, `bandHeight`;
|
|
15
|
+
`open(state, id, { edge, height? })`; `Where` and `PanelPlace` gain a `band` kind; `PanelDef.sides` may name
|
|
16
|
+
edges.
|
|
17
|
+
- Height give-way for bands: `options.pagesMinHeight` (default 240). Reserved bands shrink, then fold to a bar,
|
|
18
|
+
unkept before kept, policy order, bottom first on a tie, until the pages keep the min; then the overlay bands,
|
|
19
|
+
together, until they leave the min of the pages uncovered. Notes `band-shrunk`, `band-folded`, `kept-folded` with
|
|
20
|
+
`edge`; `pages-short` when the pages' rect is still under the min (overlay bands never cause it); `band-off` for a
|
|
21
|
+
band on an edge the options don't enable.
|
|
22
|
+
- Reserved room: `options.reserve` (`{ top?, right?, bottom?, left? }`) keeps px clear along the pages' edges for an
|
|
23
|
+
app's toolbar; `layout.reserved` has the rects. `pagesMin` and `pagesMinHeight` count after it. An overlay band
|
|
24
|
+
lies next to the reserve on its edge, on the pages' side, never over it.
|
|
25
|
+
- Targets `band:<edge>:<i>` and `band:<edge>` (an empty enabled edge). The stored form carries `bands` (an optional
|
|
26
|
+
v1 field).
|
|
27
|
+
- Typing: `PanelKind`, `PanelPlace`, `Where` and `PanelsNote` gain members, and `@n-uf/placement/desk`'s `PresetId`
|
|
28
|
+
gains `"docksFull"` (so `PRESETS` has a fourth key), so an exhaustive `switch` over them, or a `Record` keyed by
|
|
29
|
+
`PresetId`, in a consumer needs the new cases.
|
|
30
|
+
- `@n-uf/placement/desk`: `docks` and `dockTear` get a bottom band (reserved, between the columns); a new preset
|
|
31
|
+
`docksFull` (the bottom band full width). `DeskHint.bottom` starts the band; else the last def whose `sides` names
|
|
32
|
+
`"bottom"`. `navigator` has no band. Because the band is enabled, `docks` and `dockTear` now always offer a
|
|
33
|
+
`band:bottom` drop strip along the pages' bottom, even when the band is empty (a drop there used to be a float in
|
|
34
|
+
`dockTear`, and no drop in `docks`).
|
|
35
|
+
- Known limit: with `pagesMinHeight` below about 100 px on a very short stage, a column can be shorter than its title
|
|
36
|
+
bars, which then run past it (column bars were always a fixed `options.bar` tall). Keep the default 240, or at least
|
|
37
|
+
a few bars' height, when bands are on.
|
|
38
|
+
|
|
6
39
|
## 26.11.9
|
|
7
40
|
|
|
8
41
|
- `@n-uf/placement/panels`:
|
package/README.md
CHANGED
|
@@ -231,7 +231,77 @@ each with a `section-folded` note; that never writes `folded`, so a taller stage
|
|
|
231
231
|
|
|
232
232
|
**Changes** are pure: `open`, `close`, `focus`, `toggleFold`, `keep`, `dock(state, id, side, index, { into?, width? })`,
|
|
233
233
|
`float`, `pin(state, id, on, view?, shown?)` (pass the layout's rect as `shown` so a clamped float pins where it
|
|
234
|
-
shows), `resizeColumn`, `resizeFloat`, `showTabs`, `activate`.
|
|
234
|
+
shows), `resizeColumn`, `resizeFloat`, `showTabs`, `activate`, and for bands `dockBand`, `resizeBand`.
|
|
235
|
+
|
|
236
|
+
### Bands
|
|
237
|
+
|
|
238
|
+
A band is a horizontal dock along the stage's top or bottom edge (opt-in: `options.bands`, default none). Its panels
|
|
239
|
+
sit side by side, left to right, and share its width equally; it has one stored height, clamped to its panels' limits
|
|
240
|
+
(`PanelDef.band`, default `BAND_SIZE`: min 120, max 480, natural 200; the largest min and the smallest max win, as a
|
|
241
|
+
column's width does). Each enabled edge says how its band lies:
|
|
242
|
+
|
|
243
|
+
- `mode: "reserved"` shortens the pages; `"overlay"` lies over the pages' upper or lower part (layer 5, over columns'
|
|
244
|
+
layer 0 and under floats) and leaves the pages' rect as it is.
|
|
245
|
+
- `span: "full"` takes its height from the stage first, across the whole width, and the columns and rails stand on it
|
|
246
|
+
(or hang from it); `"between"` runs between the innermost open column (or rail) on each side.
|
|
247
|
+
|
|
248
|
+
A band's open panels are `kind` `"band"` (with `edge` and `index`); `layout.bands` has each band as laid out
|
|
249
|
+
(`PlacedBand`: `rect`, `height`, `min`, `max`, `folded`, `shrunk`, `kept`, `mode`, `span`, `panels`). It folds as a
|
|
250
|
+
whole: `toggleFold` on any of its panels folds or unfolds them all, and a folded band is one bar, `options.bar` tall,
|
|
251
|
+
each panel a `"bar"` side by side. `keep` works on band panels.
|
|
252
|
+
|
|
253
|
+
**Height give-way**: when the pages would be shorter than `pagesMinHeight` (default 240; counted after the top and
|
|
254
|
+
bottom reserves), reserved bands shrink toward their min, then fold to a bar, unkept before kept, in the give-way
|
|
255
|
+
policy's order, bottom before top on a tie (`band-shrunk`, `band-folded`, or `kept-folded` with `edge`); they leave the
|
|
256
|
+
pages room for the overlay bands' bars at least. Then the overlay bands give way the same way, together, until they leave
|
|
257
|
+
`pagesMinHeight` of the pages uncovered, so a top and a bottom one never meet. Give-way never writes `folded`, so a
|
|
258
|
+
taller stage brings a band back. If the pages' rect is still under the min: `pages-short` (overlay bands don't change
|
|
259
|
+
the pages' rect, so they never cause it; how far one covers the pages is its own `band-shrunk` or `band-folded`).
|
|
260
|
+
Columns never give way by height to a band.
|
|
261
|
+
|
|
262
|
+
`dockBand(state, id, edge, index, defs?)` puts a panel into a band (0 the leftmost, past the end the rightmost; a new
|
|
263
|
+
band takes the panel's natural band height); `open(state, id, { edge, height? })` puts a panel placed nowhere at a
|
|
264
|
+
band's right end; `liftPanel`, `dock` and `float` take a panel out, and an emptied band goes from the state.
|
|
265
|
+
`createPanels` takes `top` and `bottom` (the clock counts left, right, top, bottom, floats). A band in the state on an
|
|
266
|
+
edge the options don't enable is left out of `items` (`band-off`). A def's `sides` may name edges: without `sides` a
|
|
267
|
+
panel may go to every enabled edge; with them, only where they say. The drop targets add `band:<edge>:<i>` (the slots
|
|
268
|
+
at a band's ends and between its panels, the band split at its panels' middles) and, on an empty enabled edge,
|
|
269
|
+
`band:<edge>` (a strip `2 × bar` tall along it inside the pages); a drop's `slot` is the panel's share of the band.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { createPanels, layoutPanels, panelTargets, type PanelDef } from "@n-uf/placement/panels";
|
|
273
|
+
|
|
274
|
+
const defs: PanelDef[] = [
|
|
275
|
+
{ id: "files", width: { min: 180, natural: 220, max: 360 } },
|
|
276
|
+
{ id: "history", width: { min: 200, natural: 240, max: 360 }, band: { min: 100, max: 400, natural: 180 } },
|
|
277
|
+
];
|
|
278
|
+
const state = createPanels(defs, { left: [["files"]], bottom: ["history"] });
|
|
279
|
+
const options = { bands: { bottom: { mode: "reserved", span: "between" } } } as const;
|
|
280
|
+
const tall = layoutPanels(defs, state, { width: 1200, height: 700 }, options);
|
|
281
|
+
console.log(tall.bands.bottom!.rect, tall.pages); // { x: 220, y: 520, width: 980, height: 180 } { x: 220, y: 0, width: 980, height: 520 }
|
|
282
|
+
const short = layoutPanels(defs, state, { width: 1200, height: 300 }, options);
|
|
283
|
+
console.log(short.notes.map((n) => n.code), short.items.get("history")!.kind); // [ 'band-folded' ] bar
|
|
284
|
+
const targets = panelTargets({ defs, state, id: "files", stage: { width: 1200, height: 700 }, options });
|
|
285
|
+
console.log(targets.filter((t) => t.place.kind === "band").map((t) => t.id)); // [ 'band:bottom:0', 'band:bottom:1' ]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Reserved room
|
|
289
|
+
|
|
290
|
+
`options.reserve` (`{ top?, right?, bottom?, left? }`, px, default none) keeps room clear along the pages' edges for a
|
|
291
|
+
toolbar the app draws; `./toolbar` stays the toolbar's model, and placement neither draws nor names it. The reserve
|
|
292
|
+
comes off the pages' area after the columns and bands: `layout.reserved.top` is the pages' full width and `reserve.top`
|
|
293
|
+
tall at their top (bottom likewise), and the left and right reserves are as tall as what the top and bottom reserves
|
|
294
|
+
(and overlay bands) leave. `pagesMin` counts after the left and right reserves, `pagesMinHeight` after the top and
|
|
295
|
+
bottom ones; a reserve never shrinks or folds. An overlay band lies next to the reserve on its edge, on the pages' side, never over it.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
// The defs and state above; without band options the band is off (a `band-off` note).
|
|
299
|
+
const l = layoutPanels(defs, state, { width: 1200, height: 700 }, { reserve: { top: 40 } });
|
|
300
|
+
console.log(l.reserved.top, l.pages); // { x: 220, y: 0, width: 980, height: 40 } { x: 220, y: 40, width: 980, height: 660 }
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Without band options, no `bands` in the state and no reserve, every layout, target and note is what it was before
|
|
304
|
+
bands.
|
|
235
305
|
|
|
236
306
|
**Drop targets**: `panelTargets({ defs, state, id, stage, options?, allows? })` gives `column:<side>:<i>` (a new
|
|
237
307
|
column as wide as the panel's own width), `into:<side>:<i>` (stack into a column), `rail:<side>` (dock folded) and
|
|
@@ -263,8 +333,10 @@ parsed data back and never throws. Anything but an object of version 1 is `{ ok:
|
|
|
263
333
|
or repeated ids and malformed entries are dropped and listed in `dropped`, and widths and float sizes are clamped to
|
|
264
334
|
the defs. Version 1 only gains optional fields: a field this reader doesn't know is skipped and listed in `dropped`
|
|
265
335
|
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>"`.
|
|
267
|
-
|
|
336
|
+
`active` are carried; an `active` that isn't one of the column's panels is dropped as `"column.active:<id>"`. `bands`
|
|
337
|
+
is carried too, only when the state has one: an unknown edge is dropped as `"bands.<key>"`, a band without a panels
|
|
338
|
+
list or a numeric height (or whose panels are all dropped) as `"bands.<edge>"`, an unknown field in one as
|
|
339
|
+
`"band.<field>"`, and heights are clamped to the defs. The note codes (`PanelsNoteCode`):
|
|
268
340
|
|
|
269
341
|
| Code | Says | Also |
|
|
270
342
|
|---|---|---|
|
|
@@ -275,17 +347,27 @@ codes (`PanelsNoteCode`):
|
|
|
275
347
|
| `pages-under-min` | the pages are under `pagesMin` with every column folded | `width`, `min` |
|
|
276
348
|
| `float-resized` | a float shrank to fit the view | `items` |
|
|
277
349
|
| `float-clamped` | a float moved to stay in the view | `items` |
|
|
350
|
+
| `band-shrunk` | a band lowered toward its min: the pages needed the height | `edge`, `items`, `from`, `to` |
|
|
351
|
+
| `band-folded` | a band folded to a bar: the pages needed the height | `edge`, `items` |
|
|
352
|
+
| `kept-folded` | (on a band) a kept band folded, last | `edge`, `items` |
|
|
353
|
+
| `band-off` | a band on an edge the options don't enable: its panels aren't laid out | `edge`, `items` |
|
|
354
|
+
| `pages-short` | the pages' rect is under `pagesMinHeight` with every reserved band folded (or by the reserves alone) | `height`, `min` |
|
|
278
355
|
|
|
279
356
|
## Desk presets
|
|
280
357
|
|
|
281
358
|
`@n-uf/placement/desk` holds desk presets as data on `./panels`: each is panels options and a start state. It imports
|
|
282
359
|
only the panels entry, carries no titles or words and wires no toolbar; the app names the presets and draws them.
|
|
283
360
|
|
|
284
|
-
| Preset | Columns and floats |
|
|
361
|
+
| Preset | Columns, bands and floats |
|
|
285
362
|
|---|---|
|
|
286
|
-
| `docks` | one stack column per side, sections at their natural heights when the defs give `height`, folding to the rail; no floats |
|
|
287
|
-
| `
|
|
288
|
-
| `
|
|
363
|
+
| `docks` | one stack column per side, sections at their natural heights when the defs give `height`, folding to the rail; a bottom band between the columns (reserved), folding to a bar on a short stage; no floats |
|
|
364
|
+
| `docksFull` | as `docks`, with the bottom band full width: the columns stand on it |
|
|
365
|
+
| `dockTear` | as `docks` (with its bottom band), and panels may float (tear-off and dock-back); one panel floats at the start |
|
|
366
|
+
| `navigator` | a tabbed column on the left, a stack column on the right; no band, no floats |
|
|
367
|
+
|
|
368
|
+
The bottom band starts with the hint's `bottom` panels (`start(defs, { bottom: [...] })`), else with the last def whose
|
|
369
|
+
`sides` names `"bottom"`; a def without `sides` may be dropped into the band but doesn't start there. `presetTargets`
|
|
370
|
+
and `presetDrop` include the band's targets.
|
|
289
371
|
|
|
290
372
|
```ts
|
|
291
373
|
import { PRESETS, presetLayout, presetTargets } from "@n-uf/placement/desk";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EDGES,
|
|
2
3
|
SIDES,
|
|
4
|
+
bandSize,
|
|
3
5
|
defOf,
|
|
4
6
|
floatSize
|
|
5
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PJD5HKYX.js";
|
|
6
8
|
|
|
7
9
|
// src/panels/store.ts
|
|
8
10
|
function storePanels(state) {
|
|
@@ -13,9 +15,15 @@ function storePanels(state) {
|
|
|
13
15
|
...c.active !== void 0 ? { active: c.active } : {}
|
|
14
16
|
});
|
|
15
17
|
const sides = Object.fromEntries(SIDES.map((s) => [s, state.sides[s].map(column)]));
|
|
18
|
+
const bands = {};
|
|
19
|
+
for (const e of EDGES) {
|
|
20
|
+
const b = state.bands?.[e];
|
|
21
|
+
if (b) bands[e] = { panels: [...b.panels], height: b.height };
|
|
22
|
+
}
|
|
16
23
|
return {
|
|
17
24
|
v: 1,
|
|
18
25
|
sides,
|
|
26
|
+
...EDGES.some((e) => bands[e]) ? { bands } : {},
|
|
19
27
|
floats: state.floats.map((f) => ({ id: f.id, rect: { x: f.rect.x, y: f.rect.y, width: f.rect.width, height: f.rect.height }, pinned: !!f.pinned })),
|
|
20
28
|
folded: [...state.folded],
|
|
21
29
|
kept: [...state.kept],
|
|
@@ -56,7 +64,7 @@ function read(data, defs) {
|
|
|
56
64
|
const extra = (o, known, prefix) => {
|
|
57
65
|
for (const k of Object.keys(o)) if (!known.includes(k)) drop(`${prefix}${k}`);
|
|
58
66
|
};
|
|
59
|
-
extra(data, ["v", "sides", "floats", "folded", "kept", "closed", "clock"], "");
|
|
67
|
+
extra(data, ["v", "sides", "bands", "floats", "folded", "kept", "closed", "clock"], "");
|
|
60
68
|
extra(data.sides, SIDES, "sides.");
|
|
61
69
|
const sides = { left: [], right: [] };
|
|
62
70
|
for (const side of SIDES) {
|
|
@@ -86,6 +94,31 @@ function read(data, defs) {
|
|
|
86
94
|
sides[side].push({ panels, width: defs ? Math.min(Math.max(width, lo), hi) : Math.max(0, width), ...tabs, ...active });
|
|
87
95
|
}
|
|
88
96
|
}
|
|
97
|
+
const bands = {};
|
|
98
|
+
if (data.bands !== void 0) {
|
|
99
|
+
if (!isObject(data.bands)) drop("bands");
|
|
100
|
+
else {
|
|
101
|
+
extra(data.bands, EDGES, "bands.");
|
|
102
|
+
for (const e of EDGES) {
|
|
103
|
+
const b = data.bands[e];
|
|
104
|
+
if (b === void 0) continue;
|
|
105
|
+
if (!isObject(b) || !Array.isArray(b.panels) || !finite(b.height)) {
|
|
106
|
+
drop(`bands.${e}`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
extra(b, ["panels", "height"], "band.");
|
|
110
|
+
const panels = b.panels.map(take).filter((x) => x !== null);
|
|
111
|
+
if (panels.length === 0) {
|
|
112
|
+
drop(`bands.${e}`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const ds = defs ? panels.map((id) => bandSize(defOf(defs, id))) : [];
|
|
116
|
+
const lo = Math.max(0, ...ds.map((d) => d.min));
|
|
117
|
+
const hi = Math.max(lo, Math.min(...ds.map((d) => d.max)));
|
|
118
|
+
bands[e] = { panels, height: defs ? Math.min(Math.max(b.height, lo), hi) : Math.max(0, b.height) };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
89
122
|
const floats = [];
|
|
90
123
|
for (const f of Array.isArray(data.floats) ? data.floats : []) {
|
|
91
124
|
const r = isObject(f) ? f.rect : void 0;
|
|
@@ -128,11 +161,12 @@ function read(data, defs) {
|
|
|
128
161
|
clock[id] = isObject(c) && finite(c.opened) && finite(c.used) ? { opened: c.opened, used: c.used } : { opened: 0, used: 0 };
|
|
129
162
|
}
|
|
130
163
|
}
|
|
131
|
-
|
|
164
|
+
const state = { sides, floats, folded: placed("folded"), kept, closed: placed("closed"), clock };
|
|
165
|
+
return { ok: true, state: EDGES.some((e) => bands[e]) ? { ...state, bands } : state, dropped };
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
export {
|
|
135
169
|
storePanels,
|
|
136
170
|
readPanels
|
|
137
171
|
};
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
172
|
+
//# sourceMappingURL=chunk-7R5H2ABL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/panels/store.ts"],"sourcesContent":["/**\n * The panels' stored form: `storePanels(state)` gives plain JSON-safe data (`{ v: 1, … }`); `readPanels(data, defs?)`\n * reads parsed data back and never throws. As with the toolbar's `readArrangement`: anything but an object of\n * version 1 is `{ ok: false, error }`. Inside one, it is lenient: an unknown id (with `defs`), a repeated one or a\n * malformed entry is dropped and listed in `dropped`; widths and float sizes are clamped to the defs. A panel the\n * consumer has but the stored state lacks is no error: the consumer places it (`open` with a place, say).\n *\n * Version 1 only gains optional fields. A field this reader doesn't know (a newer writer's, say) is no error either:\n * it is skipped and reported in `dropped` (`\"<field>\"` at the top, `\"sides.<key>\"`, `\"column.<field>\"`,\n * `\"float.<field>\"`; a clock entry for a panel that isn't placed as `\"clock.<id>\"`), so a consumer can tell that\n * something was left out. A column's `show` (\"tabs\") and `active` are such optional fields: an unknown `show` is\n * dropped as `\"column.show\"`, an `active` that isn't one of the column's panels as `\"column.active:<id>\"`.\n *\n * `bands` (26.11.10) is such an optional field too: `{ top?, bottom? }`, each `{ panels, height }`. An unknown edge is\n * dropped as `\"bands.<key>\"`, a band that isn't an object with a panels list and a numeric height (or whose panels are\n * all dropped) as `\"bands.<edge>\"`, an unknown field in one as `\"band.<field>\"`. Heights are clamped to the defs.\n */\nimport type { Rect } from \"../geometry\";\nimport { EDGES, SIDES, bandSize, defOf, floatSize, type Band, type Clock, type Column, type Edge, type FloatAt, type PanelDef, type PanelsState, type Side } from \"./model\";\n\nexport interface StoredPanels {\n readonly v: 1;\n /** A column's `show` and `active` only when it is tabbed / has one (optional fields of v1). */\n readonly sides: Record<Side, { panels: string[]; width: number; show?: \"tabs\"; active?: string }[]>;\n /** The bands, only when the state has one (an optional field of v1). */\n readonly bands?: Partial<Record<Edge, { panels: string[]; height: number }>>;\n readonly floats: { id: string; rect: Rect; pinned: boolean }[];\n readonly folded: string[];\n readonly kept: string[];\n readonly closed: string[];\n readonly clock: Record<string, Clock>;\n}\n\nexport type PanelsRead = { readonly ok: true; readonly state: PanelsState; readonly dropped: readonly string[] } | { readonly ok: false; readonly error: string };\n\nexport function storePanels(state: PanelsState): StoredPanels {\n const column = (c: Column): StoredPanels[\"sides\"][\"left\"][number] => ({\n panels: [...c.panels],\n width: c.width,\n ...(c.show === \"tabs\" ? { show: \"tabs\" as const } : {}),\n ...(c.active !== undefined ? { active: c.active } : {}),\n });\n const sides = Object.fromEntries(SIDES.map((s) => [s, state.sides[s].map(column)])) as StoredPanels[\"sides\"];\n const bands: NonNullable<StoredPanels[\"bands\"]> = {};\n for (const e of EDGES) {\n const b = state.bands?.[e];\n if (b) bands[e] = { panels: [...b.panels], height: b.height };\n }\n return {\n v: 1,\n sides,\n ...(EDGES.some((e) => bands[e]) ? { bands } : {}),\n floats: state.floats.map((f) => ({ id: f.id, rect: { x: f.rect.x, y: f.rect.y, width: f.rect.width, height: f.rect.height }, pinned: !!f.pinned })),\n folded: [...state.folded],\n kept: [...state.kept],\n closed: [...state.closed],\n clock: Object.fromEntries(Object.entries(state.clock).map(([k, c]) => [k, { opened: c.opened, used: c.used }])),\n };\n}\n\nconst isObject = (x: unknown): x is Record<string, unknown> => typeof x === \"object\" && x !== null && !Array.isArray(x);\nconst finite = (x: unknown): x is number => typeof x === \"number\" && Number.isFinite(x);\n\nexport function readPanels(data: unknown, defs?: readonly PanelDef[]): PanelsRead {\n try {\n return read(data, defs);\n } catch (e) {\n // A getter that throws, a proxy: still no throw.\n return { ok: false, error: `unreadable: ${e instanceof Error ? e.message : typeof e}` };\n }\n}\n\nfunction read(data: unknown, defs?: readonly PanelDef[]): PanelsRead {\n if (!isObject(data)) return { ok: false, error: \"not an object\" };\n // Named without serialising it: a BigInt or a circular value must not throw.\n if (data.v !== 1) return { ok: false, error: `unknown version ${typeof data.v === \"number\" || typeof data.v === \"string\" ? String(data.v) : typeof data.v} (this reads version 1)` };\n if (!isObject(data.sides)) return { ok: false, error: \"sides is missing\" };\n const dropped: string[] = [];\n const drop = (what: string): void => {\n if (!dropped.includes(what)) dropped.push(what);\n };\n const seen = new Set<string>();\n // An id the consumer has, not placed yet.\n const take = (x: unknown): string | null => {\n if (typeof x !== \"string\") {\n drop(`a ${typeof x} where an id goes`);\n return null;\n }\n if ((defs && !defOf(defs, x)) || seen.has(x)) {\n drop(x);\n return null;\n }\n seen.add(x);\n return x;\n };\n // Fields this version doesn't know: skipped, and reported.\n const extra = (o: Record<string, unknown>, known: readonly string[], prefix: string): void => {\n for (const k of Object.keys(o)) if (!known.includes(k)) drop(`${prefix}${k}`);\n };\n extra(data, [\"v\", \"sides\", \"bands\", \"floats\", \"folded\", \"kept\", \"closed\", \"clock\"], \"\");\n extra(data.sides, SIDES, \"sides.\");\n const sides = { left: [] as Column[], right: [] as Column[] };\n for (const side of SIDES) {\n const cols = data.sides[side];\n if (cols === undefined) continue;\n if (!Array.isArray(cols)) {\n drop(`sides.${side}`);\n continue;\n }\n for (const c of cols as unknown[]) {\n if (!isObject(c) || !Array.isArray(c.panels)) {\n drop(`a column on the ${side}`);\n continue;\n }\n extra(c, [\"panels\", \"width\", \"show\", \"active\"], \"column.\");\n const panels = (c.panels as unknown[]).map(take).filter((x): x is string => x !== null);\n if (panels.length === 0) continue;\n const ds = defs ? panels.map((id) => defOf(defs, id)!) : [];\n const lo = Math.max(0, ...ds.map((d) => d.width.min));\n const hi = Math.max(lo, Math.min(...ds.map((d) => d.width.max)));\n const natural = ds.length ? Math.max(...ds.map((d) => d.width.natural)) : 0;\n const width = finite(c.width) ? c.width : natural;\n // `show`: \"tabs\" or \"stack\" (a stack is the default, so not kept); `active`: a panel of this column.\n if (c.show !== undefined && c.show !== \"tabs\" && c.show !== \"stack\") drop(\"column.show\");\n if (c.active !== undefined && !(typeof c.active === \"string\" && panels.includes(c.active))) drop(`column.active:${typeof c.active === \"string\" ? c.active : typeof c.active}`);\n const tabs = c.show === \"tabs\" ? { show: \"tabs\" as const } : {};\n const active = typeof c.active === \"string\" && panels.includes(c.active) ? { active: c.active } : {};\n sides[side].push({ panels, width: defs ? Math.min(Math.max(width, lo), hi) : Math.max(0, width), ...tabs, ...active });\n }\n }\n const bands: Partial<Record<Edge, Band>> = {};\n if (data.bands !== undefined) {\n if (!isObject(data.bands)) drop(\"bands\");\n else {\n extra(data.bands, EDGES, \"bands.\");\n for (const e of EDGES) {\n const b = data.bands[e];\n if (b === undefined) continue;\n if (!isObject(b) || !Array.isArray(b.panels) || !finite(b.height)) {\n drop(`bands.${e}`);\n continue;\n }\n extra(b, [\"panels\", \"height\"], \"band.\");\n const panels = (b.panels as unknown[]).map(take).filter((x): x is string => x !== null);\n if (panels.length === 0) {\n drop(`bands.${e}`);\n continue;\n }\n const ds = defs ? panels.map((id) => bandSize(defOf(defs, id))) : [];\n const lo = Math.max(0, ...ds.map((d) => d.min));\n const hi = Math.max(lo, Math.min(...ds.map((d) => d.max)));\n bands[e] = { panels, height: defs ? Math.min(Math.max(b.height, lo), hi) : Math.max(0, b.height) };\n }\n }\n }\n const floats: FloatAt[] = [];\n for (const f of Array.isArray(data.floats) ? (data.floats as unknown[]) : []) {\n const r = isObject(f) ? f.rect : undefined;\n if (!isObject(f) || !isObject(r) || !finite(r.x) || !finite(r.y) || !finite(r.width) || !finite(r.height)) {\n drop(isObject(f) && typeof f.id === \"string\" ? f.id : \"a float\");\n continue;\n }\n extra(f, [\"id\", \"rect\", \"pinned\"], \"float.\");\n const id = take(f.id);\n if (id === null) continue;\n const min = defs ? floatSize(defOf(defs, id)).min : { width: 0, height: 0 };\n floats.push({ id, rect: { x: r.x, y: r.y, width: Math.max(min.width, r.width), height: Math.max(min.height, r.height) }, ...(f.pinned === true ? { pinned: true } : {}) });\n }\n if (data.floats !== undefined && !Array.isArray(data.floats)) drop(\"floats\");\n // The lists keep only ids that are placed (a closed panel keeps its place).\n const placed = (key: \"folded\" | \"kept\" | \"closed\"): string[] => {\n const x = data[key];\n if (x === undefined) return [];\n if (!Array.isArray(x)) {\n drop(key);\n return [];\n }\n const out: string[] = [];\n for (const id of x as unknown[]) {\n if (typeof id === \"string\" && seen.has(id)) {\n if (!out.includes(id)) out.push(id);\n } else drop(typeof id === \"string\" ? id : `a ${typeof id} in ${key}`);\n }\n return out;\n };\n const floating = new Set(floats.map((f) => f.id));\n // Only a docked panel is kept open.\n const kept = placed(\"kept\").filter((id) => {\n if (floating.has(id)) drop(id);\n return !floating.has(id);\n });\n const clock: Record<string, Clock> = {};\n if (isObject(data.clock)) {\n for (const id of Object.keys(data.clock)) if (!seen.has(id)) drop(`clock.${id}`);\n for (const id of seen) {\n const c = data.clock[id];\n clock[id] = isObject(c) && finite(c.opened) && finite(c.used) ? { opened: c.opened, used: c.used } : { opened: 0, used: 0 };\n }\n }\n const state: PanelsState = { sides, floats, folded: placed(\"folded\"), kept, closed: placed(\"closed\"), clock };\n return { ok: true, state: EDGES.some((e) => bands[e]) ? { ...state, bands } : state, dropped };\n}\n"],"mappings":";;;;;;;;;AAmCO,SAAS,YAAY,OAAkC;AAC5D,QAAM,SAAS,CAAC,OAAsD;AAAA,IACpE,QAAQ,CAAC,GAAG,EAAE,MAAM;AAAA,IACpB,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,SAAS,SAAS,EAAE,MAAM,OAAgB,IAAI,CAAC;AAAA,IACrD,GAAI,EAAE,WAAW,SAAY,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,EACvD;AACA,QAAM,QAAQ,OAAO,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC;AAClF,QAAM,QAA4C,CAAC;AACnD,aAAW,KAAK,OAAO;AACrB,UAAM,IAAI,MAAM,QAAQ,CAAC;AACzB,QAAI,EAAG,OAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,OAAO;AAAA,EAC9D;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,GAAI,MAAM,KAAK,CAAC,MAAM,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;AAAA,IAC/C,QAAQ,MAAM,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,GAAG,OAAO,EAAE,KAAK,OAAO,QAAQ,EAAE,KAAK,OAAO,GAAG,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE;AAAA,IAClJ,QAAQ,CAAC,GAAG,MAAM,MAAM;AAAA,IACxB,MAAM,CAAC,GAAG,MAAM,IAAI;AAAA,IACpB,QAAQ,CAAC,GAAG,MAAM,MAAM;AAAA,IACxB,OAAO,OAAO,YAAY,OAAO,QAAQ,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAAA,EAChH;AACF;AAEA,IAAM,WAAW,CAAC,MAA6C,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AACtH,IAAM,SAAS,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAE/E,SAAS,WAAW,MAAe,MAAwC;AAChF,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,SAAS,GAAG;AAEV,WAAO,EAAE,IAAI,OAAO,OAAO,eAAe,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG;AAAA,EACxF;AACF;AAEA,SAAS,KAAK,MAAe,MAAwC;AACnE,MAAI,CAAC,SAAS,IAAI,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,gBAAgB;AAEhE,MAAI,KAAK,MAAM,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB,OAAO,KAAK,MAAM,YAAY,OAAO,KAAK,MAAM,WAAW,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,0BAA0B;AACnL,MAAI,CAAC,SAAS,KAAK,KAAK,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AACzE,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAO,CAAC,SAAuB;AACnC,QAAI,CAAC,QAAQ,SAAS,IAAI,EAAG,SAAQ,KAAK,IAAI;AAAA,EAChD;AACA,QAAM,OAAO,oBAAI,IAAY;AAE7B,QAAM,OAAO,CAAC,MAA8B;AAC1C,QAAI,OAAO,MAAM,UAAU;AACzB,WAAK,KAAK,OAAO,CAAC,mBAAmB;AACrC,aAAO;AAAA,IACT;AACA,QAAK,QAAQ,CAAC,MAAM,MAAM,CAAC,KAAM,KAAK,IAAI,CAAC,GAAG;AAC5C,WAAK,CAAC;AACN,aAAO;AAAA,IACT;AACA,SAAK,IAAI,CAAC;AACV,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,CAAC,GAA4B,OAA0B,WAAyB;AAC5F,eAAW,KAAK,OAAO,KAAK,CAAC,EAAG,KAAI,CAAC,MAAM,SAAS,CAAC,EAAG,MAAK,GAAG,MAAM,GAAG,CAAC,EAAE;AAAA,EAC9E;AACA,QAAM,MAAM,CAAC,KAAK,SAAS,SAAS,UAAU,UAAU,QAAQ,UAAU,OAAO,GAAG,EAAE;AACtF,QAAM,KAAK,OAAO,OAAO,QAAQ;AACjC,QAAM,QAAQ,EAAE,MAAM,CAAC,GAAe,OAAO,CAAC,EAAc;AAC5D,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,QAAI,SAAS,OAAW;AACxB,QAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,WAAK,SAAS,IAAI,EAAE;AACpB;AAAA,IACF;AACA,eAAW,KAAK,MAAmB;AACjC,UAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,QAAQ,EAAE,MAAM,GAAG;AAC5C,aAAK,mBAAmB,IAAI,EAAE;AAC9B;AAAA,MACF;AACA,YAAM,GAAG,CAAC,UAAU,SAAS,QAAQ,QAAQ,GAAG,SAAS;AACzD,YAAM,SAAU,EAAE,OAAqB,IAAI,IAAI,EAAE,OAAO,CAAC,MAAmB,MAAM,IAAI;AACtF,UAAI,OAAO,WAAW,EAAG;AACzB,YAAM,KAAK,OAAO,OAAO,IAAI,CAAC,OAAO,MAAM,MAAM,EAAE,CAAE,IAAI,CAAC;AAC1D,YAAM,KAAK,KAAK,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC;AACpD,YAAM,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC;AAC/D,YAAM,UAAU,GAAG,SAAS,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI;AAC1E,YAAM,QAAQ,OAAO,EAAE,KAAK,IAAI,EAAE,QAAQ;AAE1C,UAAI,EAAE,SAAS,UAAa,EAAE,SAAS,UAAU,EAAE,SAAS,QAAS,MAAK,aAAa;AACvF,UAAI,EAAE,WAAW,UAAa,EAAE,OAAO,EAAE,WAAW,YAAY,OAAO,SAAS,EAAE,MAAM,GAAI,MAAK,iBAAiB,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS,OAAO,EAAE,MAAM,EAAE;AAC7K,YAAM,OAAO,EAAE,SAAS,SAAS,EAAE,MAAM,OAAgB,IAAI,CAAC;AAC9D,YAAM,SAAS,OAAO,EAAE,WAAW,YAAY,OAAO,SAAS,EAAE,MAAM,IAAI,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AACnG,YAAM,IAAI,EAAE,KAAK,EAAE,QAAQ,OAAO,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,KAAK,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC;AAAA,IACvH;AAAA,EACF;AACA,QAAM,QAAqC,CAAC;AAC5C,MAAI,KAAK,UAAU,QAAW;AAC5B,QAAI,CAAC,SAAS,KAAK,KAAK,EAAG,MAAK,OAAO;AAAA,SAClC;AACH,YAAM,KAAK,OAAO,OAAO,QAAQ;AACjC,iBAAW,KAAK,OAAO;AACrB,cAAM,IAAI,KAAK,MAAM,CAAC;AACtB,YAAI,MAAM,OAAW;AACrB,YAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,QAAQ,EAAE,MAAM,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG;AACjE,eAAK,SAAS,CAAC,EAAE;AACjB;AAAA,QACF;AACA,cAAM,GAAG,CAAC,UAAU,QAAQ,GAAG,OAAO;AACtC,cAAM,SAAU,EAAE,OAAqB,IAAI,IAAI,EAAE,OAAO,CAAC,MAAmB,MAAM,IAAI;AACtF,YAAI,OAAO,WAAW,GAAG;AACvB,eAAK,SAAS,CAAC,EAAE;AACjB;AAAA,QACF;AACA,cAAM,KAAK,OAAO,OAAO,IAAI,CAAC,OAAO,SAAS,MAAM,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;AACnE,cAAM,KAAK,KAAK,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AAC9C,cAAM,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACzD,cAAM,CAAC,IAAI,EAAE,QAAQ,QAAQ,OAAO,KAAK,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE;AAAA,MACnG;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAoB,CAAC;AAC3B,aAAW,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAuB,CAAC,GAAG;AAC5E,UAAM,IAAI,SAAS,CAAC,IAAI,EAAE,OAAO;AACjC,QAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG;AACzG,WAAK,SAAS,CAAC,KAAK,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK,SAAS;AAC/D;AAAA,IACF;AACA,UAAM,GAAG,CAAC,MAAM,QAAQ,QAAQ,GAAG,QAAQ;AAC3C,UAAM,KAAK,KAAK,EAAE,EAAE;AACpB,QAAI,OAAO,KAAM;AACjB,UAAM,MAAM,OAAO,UAAU,MAAM,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,GAAG,QAAQ,EAAE;AAC1E,WAAO,KAAK,EAAE,IAAI,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,EAAE,KAAK,GAAG,QAAQ,KAAK,IAAI,IAAI,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAI,EAAE,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC,EAAG,CAAC;AAAA,EAC3K;AACA,MAAI,KAAK,WAAW,UAAa,CAAC,MAAM,QAAQ,KAAK,MAAM,EAAG,MAAK,QAAQ;AAE3E,QAAM,SAAS,CAAC,QAAgD;AAC9D,UAAM,IAAI,KAAK,GAAG;AAClB,QAAI,MAAM,OAAW,QAAO,CAAC;AAC7B,QAAI,CAAC,MAAM,QAAQ,CAAC,GAAG;AACrB,WAAK,GAAG;AACR,aAAO,CAAC;AAAA,IACV;AACA,UAAM,MAAgB,CAAC;AACvB,eAAW,MAAM,GAAgB;AAC/B,UAAI,OAAO,OAAO,YAAY,KAAK,IAAI,EAAE,GAAG;AAC1C,YAAI,CAAC,IAAI,SAAS,EAAE,EAAG,KAAI,KAAK,EAAE;AAAA,MACpC,MAAO,MAAK,OAAO,OAAO,WAAW,KAAK,KAAK,OAAO,EAAE,OAAO,GAAG,EAAE;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AACA,QAAM,WAAW,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAEhD,QAAM,OAAO,OAAO,MAAM,EAAE,OAAO,CAAC,OAAO;AACzC,QAAI,SAAS,IAAI,EAAE,EAAG,MAAK,EAAE;AAC7B,WAAO,CAAC,SAAS,IAAI,EAAE;AAAA,EACzB,CAAC;AACD,QAAM,QAA+B,CAAC;AACtC,MAAI,SAAS,KAAK,KAAK,GAAG;AACxB,eAAW,MAAM,OAAO,KAAK,KAAK,KAAK,EAAG,KAAI,CAAC,KAAK,IAAI,EAAE,EAAG,MAAK,SAAS,EAAE,EAAE;AAC/E,eAAW,MAAM,MAAM;AACrB,YAAM,IAAI,KAAK,MAAM,EAAE;AACvB,YAAM,EAAE,IAAI,SAAS,CAAC,KAAK,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,IAAI,IAAI,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE;AAAA,IAC5H;AAAA,EACF;AACA,QAAM,QAAqB,EAAE,OAAO,QAAQ,QAAQ,OAAO,QAAQ,GAAG,MAAM,QAAQ,OAAO,QAAQ,GAAG,MAAM;AAC5G,SAAO,EAAE,IAAI,MAAM,OAAO,MAAM,KAAK,CAAC,MAAM,MAAM,CAAC,CAAC,IAAI,EAAE,GAAG,OAAO,MAAM,IAAI,OAAO,QAAQ;AAC/F;","names":[]}
|