@n-uf/placement 26.11.5 → 26.11.7

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/panels/model.ts","../src/panels/layout.ts","../src/panels/targets.ts"],"sourcesContent":["/**\n * The panels model: plain data and the pure changes on it.\n *\n * Panels have string ids. Each side holds full-height columns (outer edge first), each column one or more panels\n * top to bottom and its own width. A panel may instead float, in page space (it moves with the page) or pinned in\n * view space (it stays put). A logical clock per panel (opened, used) is what give-way reads; every change here that\n * counts as a use moves it on. Nothing here reads a host clock: the next tick is one past the latest in the state.\n */\nimport type { Rect, Size } from \"../geometry\";\n\nexport type Side = \"left\" | \"right\";\nexport const SIDES: readonly Side[] = [\"left\", \"right\"];\n\n/**\n * What a consumer says about a panel: its width limits docked, its size limits floating, and where it may go. No\n * words: names and titles are the consumer's.\n */\nexport interface PanelDef {\n readonly id: string;\n /** A docked column's width limits and its natural width. */\n readonly width: { readonly min: number; readonly max: number; readonly natural: number };\n /** A float's size limits and natural size (default: its natural width, 360 px tall, no smaller than 160 × 120). */\n readonly float?: { readonly min: Size; readonly natural: Size };\n /** Where it may dock (default: both sides). */\n readonly sides?: readonly Side[];\n /** May it float (default: yes). */\n readonly floats?: boolean;\n}\n\n/** A full-height column: its panels top to bottom, and its stored width (the layout clamps it to its panels' limits). */\nexport interface Column {\n readonly panels: readonly string[];\n readonly width: number;\n}\n\n/** A floating panel: `rect` in page space, or in view space when `pinned`. */\nexport interface FloatAt {\n readonly id: string;\n readonly rect: Rect;\n readonly pinned?: boolean;\n}\n\n/** Logical times: when a panel was last opened, and last used (opened, focused, moved or unfolded). */\nexport interface Clock {\n readonly opened: number;\n readonly used: number;\n}\n\nexport interface PanelsState {\n /** Each side's columns, from the outer edge inward. */\n readonly sides: Readonly<Record<Side, readonly Column[]>>;\n /** Floating panels; a later one is on top. */\n readonly floats: readonly FloatAt[];\n /** Folded by hand (give-way never writes this). */\n readonly folded: readonly string[];\n /** Docked panels kept open: their columns give way last. */\n readonly kept: readonly string[];\n /** Not shown at all (they keep their place, for when they open again). */\n readonly closed: readonly string[];\n readonly clock: Readonly<Record<string, Clock>>;\n}\n\n/** The page's offset and scale in the view: a page point p shows at `x + p.x * scale`, `y + p.y * scale`. */\nexport interface View {\n readonly x: number;\n readonly y: number;\n readonly scale: number;\n}\nexport const IDENTITY_VIEW: View = { x: 0, y: 0, scale: 1 };\n\n/** Who gives way first: the oldest or the newest, by when they were last used or last opened. */\nexport interface GiveWay {\n readonly order: \"oldest\" | \"newest\";\n readonly by: \"used\" | \"opened\";\n}\n\nexport interface PanelsOptions {\n /** Default: `{ order: \"oldest\", by: \"used\" }`, so the panel used longest ago gives way first. */\n readonly giveWay: GiveWay;\n /** \"clamp\" keeps floats in the view at layout time; \"free\" never moves them. Default \"clamp\". */\n readonly floats: \"clamp\" | \"free\";\n /** How much of a float wider than the view stays in it, px (default 48). */\n readonly floatVisible: number;\n /** The pages' minimum width (default 320). */\n readonly pagesMin: number;\n /** The page's offset and scale (default: identity). */\n readonly view: View;\n /** A title bar's height: a panel folded in a column, and the part of a float the clamp keeps in the view (default 28). */\n readonly bar: number;\n /** A rail's width (default 36). */\n readonly rail: number;\n /** A rail segment's height, and the gap above the first one (defaults 36 and 4). */\n readonly segment: number;\n readonly railInset: number;\n}\n\nexport const DEFAULT_PANELS_OPTIONS: PanelsOptions = {\n giveWay: { order: \"oldest\", by: \"used\" },\n floats: \"clamp\",\n floatVisible: 48,\n pagesMin: 320,\n view: IDENTITY_VIEW,\n bar: 28,\n rail: 36,\n segment: 36,\n railInset: 4,\n};\n\n/** A float's size when its def gives none: this tall, and never smaller than FLOAT_MIN. */\nexport const FLOAT_HEIGHT = 360;\nexport const FLOAT_MIN: Size = { width: 160, height: 120 };\n\n// --- reading the state ---------------------------------------------------------------------------------------------\n\nconst without = <T>(list: readonly T[], x: T): T[] => list.filter((y) => y !== x);\nconst withOnce = <T>(list: readonly T[], x: T): T[] => (list.includes(x) ? [...list] : [...list, x]);\n\n/** Where a panel is: a column (side and index), floating, or nowhere. */\nexport type Where = { readonly kind: \"column\"; readonly side: Side; readonly index: number } | { readonly kind: \"float\"; readonly float: FloatAt } | { readonly kind: \"none\" };\n\nexport function whereIs(state: PanelsState, id: string): Where {\n for (const side of SIDES) {\n const index = state.sides[side].findIndex((c) => c.panels.includes(id));\n if (index >= 0) return { kind: \"column\", side, index };\n }\n const float = state.floats.find((f) => f.id === id);\n return float ? { kind: \"float\", float } : { kind: \"none\" };\n}\n\n/** One past the latest logical time in the state. */\nexport const tick = (state: PanelsState): number => Object.values(state.clock).reduce((m, c) => Math.max(m, c.opened, c.used), 0) + 1;\n\nconst bump = (state: PanelsState, id: string, opened: boolean): PanelsState => {\n const t = tick(state);\n const was = state.clock[id] ?? { opened: 0, used: 0 };\n return { ...state, clock: { ...state.clock, [id]: { opened: opened ? t : was.opened, used: t } } };\n};\n\nexport const defOf = (defs: readonly PanelDef[], id: string): PanelDef | undefined => defs.find((d) => d.id === id);\n\n/** A float's size limits and natural size from its def. */\nexport function floatSize(def: PanelDef | undefined): { min: Size; natural: Size } {\n if (def?.float) return { min: def.float.min, natural: def.float.natural };\n const width = Math.max(FLOAT_MIN.width, def?.width.natural ?? FLOAT_MIN.width);\n return { min: FLOAT_MIN, natural: { width, height: FLOAT_HEIGHT } };\n}\n\n/**\n * A panel's own width, for a new column made for it: the width of the column it is in, clamped to its own limits;\n * floating or nowhere, its natural width.\n */\nexport function ownWidth(defs: readonly PanelDef[], state: PanelsState, id: string): number {\n const def = defOf(defs, id);\n const w = whereIs(state, id);\n const lo = def?.width.min ?? 0;\n const hi = Math.max(lo, def?.width.max ?? Infinity);\n if (w.kind === \"column\") return Math.min(Math.max(state.sides[w.side][w.index]!.width, lo), hi);\n return def?.width.natural ?? lo;\n}\n\n// --- building one --------------------------------------------------------------------------------------------------\n\nexport interface PanelsStart {\n /** Each side's columns from the outer edge inward, each a list of panel ids top to bottom. */\n readonly left?: readonly (readonly string[])[];\n readonly right?: readonly (readonly string[])[];\n /** Floating panels: where (page space, or view space when pinned); the size defaults to the def's natural size. */\n readonly floats?: readonly { readonly id: string; readonly x: number; readonly y: number; readonly width?: number; readonly height?: number; readonly pinned?: boolean }[];\n readonly folded?: readonly string[];\n readonly kept?: readonly string[];\n readonly closed?: readonly string[];\n}\n\n/**\n * A state from a plain start: each column as wide as the widest natural width of its panels (within their limits),\n * each float its natural size. The clock counts up in the order the panels are listed (left, right, floats), so the\n * last one listed is the newest.\n */\nexport function createPanels(defs: readonly PanelDef[], start: PanelsStart): PanelsState {\n const column = (ids: readonly string[]): Column => {\n const ds = ids.map((id) => defOf(defs, id));\n const lo = Math.max(0, ...ds.map((d) => d?.width.min ?? 0));\n const hi = Math.max(lo, Math.min(...ds.map((d) => d?.width.max ?? Infinity)));\n const natural = Math.max(0, ...ds.map((d) => d?.width.natural ?? 0));\n return { panels: [...ids], width: Math.min(Math.max(natural, lo), hi) };\n };\n const floats = (start.floats ?? []).map((f): FloatAt => {\n const n = floatSize(defOf(defs, f.id)).natural;\n return { id: f.id, rect: { x: f.x, y: f.y, width: f.width ?? n.width, height: f.height ?? n.height }, ...(f.pinned ? { pinned: true } : {}) };\n });\n const sides = { left: (start.left ?? []).map(column), right: (start.right ?? []).map(column) };\n const order = [...sides.left, ...sides.right].flatMap((c) => c.panels).concat(floats.map((f) => f.id));\n const clock: Record<string, Clock> = {};\n order.forEach((id, i) => (clock[id] = { opened: i + 1, used: i + 1 }));\n return { sides, floats, folded: [...(start.folded ?? [])], kept: [...(start.kept ?? [])], closed: [...(start.closed ?? [])], clock };\n}\n\n// --- changes -------------------------------------------------------------------------------------------------------\n\n/** The state with `id` taken out of its column (an emptied column goes) or the floats. Nothing else changes. */\nexport function liftPanel(state: PanelsState, id: string): PanelsState {\n const lift = (cols: readonly Column[]): Column[] => cols.map((c) => ({ ...c, panels: without(c.panels, id) })).filter((c) => c.panels.length > 0);\n return { ...state, sides: { left: lift(state.sides.left), right: lift(state.sides.right) }, floats: state.floats.filter((f) => f.id !== id) };\n}\n\n/**\n * Open a panel: it shows again where it was, and it is the newest opened and used. A panel placed nowhere goes into\n * a new innermost column on `at.side`, `at.width` wide; without `at` it stays placed nowhere.\n */\nexport function open(state: PanelsState, id: string, at?: { readonly side: Side; readonly width: number }): PanelsState {\n let s: PanelsState = { ...state, closed: without(state.closed, id) };\n if (at && whereIs(s, id).kind === \"none\") s = { ...s, sides: { ...s.sides, [at.side]: [...s.sides[at.side], { panels: [id], width: at.width }] } };\n return bump(s, id, true);\n}\n\n/** Close a panel: it isn't shown, keeps its place, and is no longer kept open. */\nexport const close = (state: PanelsState, id: string): PanelsState => ({ ...state, closed: withOnce(state.closed, id), kept: without(state.kept, id) });\n\n/** Focus a panel: it is the newest used; a float comes to the top. */\nexport function focus(state: PanelsState, id: string): PanelsState {\n const f = state.floats.find((x) => x.id === id);\n const s = f ? { ...state, floats: [...state.floats.filter((x) => x !== f), f] } : state;\n return bump(s, id, false);\n}\n\n/** Fold a panel to its title bar by hand, or unfold it (unfolding is a use). */\nexport function toggleFold(state: PanelsState, id: string): PanelsState {\n if (state.folded.includes(id)) return bump({ ...state, folded: without(state.folded, id) }, id, false);\n return { ...state, folded: [...state.folded, id] };\n}\n\n/** Keep a docked panel open (its column gives way last), or stop keeping it. A floating panel isn't kept. */\nexport function keep(state: PanelsState, id: string, on: boolean): PanelsState {\n if (!on) return { ...state, kept: without(state.kept, id) };\n return whereIs(state, id).kind === \"column\" ? { ...state, kept: withOnce(state.kept, id) } : state;\n}\n\nexport interface DockOptions {\n /** Stack into the column at `index` (at its bottom) instead of making a new column there. */\n readonly into?: boolean;\n /** A new column's width (default: the width of the column the panel leaves, else 0, which the layout clamps to the minimum). */\n readonly width?: number;\n}\n\n/**\n * Dock a panel on `side`: a new column at `index` (0 the outer edge; past the end, innermost), or with `into` at the\n * bottom of the column at `index` (counted once the panel is lifted out). It shows, it is the newest used, and it\n * keeps whether it is kept open.\n */\nexport function dock(state: PanelsState, id: string, side: Side, index: number, options: DockOptions = {}): PanelsState {\n const w = whereIs(state, id);\n const from = w.kind === \"column\" ? state.sides[w.side][w.index]!.width : 0;\n const s = liftPanel(state, id);\n const cols = [...s.sides[side]];\n if (options.into && cols[index]) cols[index] = { ...cols[index]!, panels: [...cols[index]!.panels, id] };\n else {\n const at = Math.max(0, Math.min(index, cols.length));\n cols.splice(at, 0, { panels: [id], width: options.width ?? from });\n }\n return bump({ ...s, sides: { ...s.sides, [side]: cols }, closed: without(s.closed, id) }, id, false);\n}\n\n/**\n * Float a panel at `rect` (page space; view space when pinned). A float stays pinned or not unless `pinned` says;\n * a docked panel floats unpinned. It shows, it is the newest used and on top, and it is no longer kept open.\n */\nexport function float(state: PanelsState, id: string, rect: Rect, pinned?: boolean): PanelsState {\n const was = state.floats.find((f) => f.id === id);\n const on = pinned ?? !!was?.pinned;\n const s = liftPanel(state, id);\n const f: FloatAt = { id, rect: { ...rect }, ...(on ? { pinned: true } : {}) };\n return bump({ ...s, floats: [...s.floats, f], kept: without(s.kept, id), closed: without(s.closed, id) }, id, false);\n}\n\n/** A page-space rect in view space, and back. */\nexport const toView = (r: Rect, v: View): Rect => ({ ...r, x: v.x + r.x * v.scale, y: v.y + r.y * v.scale });\nexport const toPage = (r: Rect, v: View): Rect => ({ ...r, x: (r.x - v.x) / v.scale, y: (r.y - v.y) / v.scale });\n\n/**\n * Pin a float to the view, or unpin it to the page. `view` is where the page is now (default: identity): the float\n * doesn't jump, its rect is converted between the two spaces. A float's size is never scaled.\n *\n * `shown` is where the float shows now, in view space (the layout's placed rect for it). When given, it is converted\n * instead of the stored rect, so a float the clamp moved stays where it shows rather than jumping to its stored place.\n */\nexport function pin(state: PanelsState, id: string, on: boolean, view: View = IDENTITY_VIEW, shown?: Rect): PanelsState {\n return {\n ...state,\n floats: state.floats.map((f): FloatAt => {\n if (f.id !== id || !!f.pinned === on) return f;\n // In view space: where it shows, else where it is stored.\n const seen = shown ? { ...shown } : f.pinned ? { ...f.rect } : toView(f.rect, view);\n return on ? { id, rect: seen, pinned: true } : { id, rect: toPage(seen, view) };\n }),\n };\n}\n\n/**\n * Store a column's width (the layout clamps it to its panels' limits). Resizing is a use: the column's panels are\n * the newest used, so give-way takes room from the other columns, not from the one being resized.\n */\nexport function resizeColumn(state: PanelsState, side: Side, index: number, width: number): PanelsState {\n const col = state.sides[side][index];\n if (!col || !Number.isFinite(width)) return state;\n const t = tick(state);\n const clock = { ...state.clock };\n for (const id of col.panels) clock[id] = { opened: clock[id]?.opened ?? 0, used: t };\n return { ...state, sides: { ...state.sides, [side]: state.sides[side].map((c, i) => (i === index ? { ...c, width: Math.max(0, width) } : c)) }, clock };\n}\n\n/** Store a float's rect (its own space). Resizing is a use. */\nexport function resizeFloat(state: PanelsState, id: string, rect: Rect): PanelsState {\n if (!state.floats.some((f) => f.id === id)) return state;\n return bump({ ...state, floats: state.floats.map((f) => (f.id === id ? { ...f, rect: { ...rect } } : f)) }, id, false);\n}\n\n/** A column's width as the layout takes it: its stored width clamped to its panels' limits (with `defs`). */\nexport function columnWidth(defs: readonly PanelDef[] | undefined, col: Column): number {\n if (!defs) return col.width;\n const ds = col.panels.map((id) => defOf(defs, id));\n const lo = Math.max(0, ...ds.map((d) => d?.width.min ?? 0));\n const hi = Math.max(lo, Math.min(...ds.map((d) => d?.width.max ?? Infinity)));\n return Math.min(Math.max(Number.isFinite(col.width) ? col.width : lo, lo), hi);\n}\n\n/**\n * Two states place the panels the same (columns, their panels and widths, floats and their rects; the clock aside).\n * With `defs`, widths compare as the layout takes them (clamped to the panels' limits), so a stored width past a\n * column's max is the same place as that max.\n */\nexport function samePlaces(a: PanelsState, b: PanelsState, defs?: readonly PanelDef[]): boolean {\n const cols = (s: PanelsState): string => SIDES.map((side) => s.sides[side].map((c) => `${c.panels.join(\",\")}@${Math.round(columnWidth(defs, c))}`).join(\"|\")).join(\"‖\");\n const fl = (s: PanelsState): string => 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(\"|\");\n const set = (l: readonly string[]): string => [...l].sort().join(\",\");\n 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);\n}\n","/**\n * `layoutPanels`: the panels' state, a stage and options in; geometry and structure out: every panel's rect, layer\n * and kind, the columns, the rails, the pages' rect, and coded notes on how it gave way. Pure: the same input always\n * lays out the same. How anything looks is the consumer's.\n *\n * - A column is full height and as wide as its stored width, clamped to its panels' limits. Inside it, a panel\n * folded by hand is a title bar (`options.bar` tall) and the open ones share the rest of the height equally.\n * - A column whose panels are all folded is railed: its panels become segments of its side's rail (one rail per\n * side, `options.rail` wide at the outer edge, segments in side order).\n * - Give-way: when the open columns, the rails and `pagesMin` don't fit the stage width, columns shrink toward their\n * minimum, then whole columns fold to the rail, in the policy's order, until the pages fit. Columns holding a kept\n * panel come after every other column; if the pages still don't fit, they fold too (`kept-folded`). Folding by\n * give-way never writes `folded`, so a wider stage brings the column back.\n * - Floats: \"clamp\" keeps a float in the view at layout time (the state keeps the stored rect, so a larger view\n * brings it back where it was); \"free\" never moves one. A float's size is never scaled. A pinned float is in view\n * space and clamped against the view; an unpinned one has its position in page space, clamped so that its screen\n * rect at pan 0 fits the view, then moved by the view's offset and scale, so it scrolls with the page.\n */\nimport { rect, type Rect, type Size } from \"../geometry\";\nimport { DEFAULT_PANELS_OPTIONS, SIDES, defOf, floatSize, toView, type PanelDef, type PanelsOptions, type PanelsState, type Side } from \"./model\";\n\n/**\n * Where a panel is, structurally: an open part of a column, a folded title bar in a column, a segment of a rail\n * (the panel itself isn't laid out), or a float.\n */\nexport type PanelKind = \"column\" | \"bar\" | \"segment\" | \"float\";\n\nexport interface PlacedPanel {\n /** Stage px; for a segment, the segment's rect in its rail. */\n readonly rect: Rect;\n readonly kind: PanelKind;\n /** Columns and rails 0, floats 10 and up (a later float on top). */\n readonly layer: number;\n /** In a rail: there is no panel to lay out, only its segment. */\n readonly hidden: boolean;\n /** Folded by hand (a bar, or a segment of a column folded by hand), or by give-way (a segment). */\n readonly folded: null | \"hand\" | \"auto\";\n /** Kept open. */\n readonly kept: boolean;\n /** A pinned float. */\n readonly pinned: boolean;\n /** Docked: its side and column index (the state's). */\n readonly side?: Side;\n readonly column?: number;\n /** A float the clamp moved or resized. */\n readonly clamped?: boolean;\n}\n\nexport interface PlacedColumn {\n readonly side: Side;\n /** The column's index in the state. */\n readonly index: number;\n /** Its rect (zero wide when railed). */\n readonly rect: Rect;\n /** Its stored width clamped to its panels' limits, and those limits. */\n readonly width: number;\n readonly min: number;\n readonly max: number;\n /** null: open; \"hand\": all its panels folded by hand; \"auto\": folded by give-way. */\n readonly folded: null | \"hand\" | \"auto\";\n /** Shrunk by give-way below its width. */\n readonly shrunk: boolean;\n /** It holds a kept panel. */\n readonly kept: boolean;\n /** The shown panels, top to bottom. */\n readonly panels: readonly string[];\n}\n\nexport interface PlacedRail {\n readonly side: Side;\n readonly rect: Rect;\n readonly segments: readonly { readonly id: string; readonly rect: Rect }[];\n}\n\nexport type PanelsNoteCode = \"column-shrunk\" | \"column-folded\" | \"kept-folded\" | \"pages-under-min\" | \"float-clamped\" | \"float-resized\";\n\n/** One note on a layout: a code and its data. */\nexport type PanelsNote =\n | { readonly code: \"column-shrunk\"; readonly side: Side; readonly column: number; readonly items: readonly string[]; readonly from: number; readonly to: number }\n | { readonly code: \"column-folded\" | \"kept-folded\"; readonly side: Side; readonly column: number; readonly items: readonly string[] }\n | { readonly code: \"pages-under-min\"; readonly width: number; readonly min: number }\n | { readonly code: \"float-clamped\" | \"float-resized\"; readonly items: readonly string[] };\n\nexport interface PanelsLayout {\n readonly items: Map<string, PlacedPanel>;\n /** The shown columns in side order (left outer → inner, then right outer → inner). */\n readonly columns: readonly PlacedColumn[];\n readonly rails: Partial<Record<Side, PlacedRail>>;\n readonly notes: readonly PanelsNote[];\n /** What is left for the documents. */\n readonly pages: Rect;\n}\n\ninterface Col {\n readonly side: Side;\n readonly index: number;\n readonly panels: readonly string[];\n readonly min: number;\n readonly max: number;\n readonly width: number;\n readonly hand: boolean;\n readonly kept: boolean;\n readonly age: number;\n}\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(Math.max(v, lo), Math.max(lo, hi));\n\n/** The order columns give way in: unkept before kept, then the policy (oldest or newest), outer first, left first. */\nexport function giveWayOrder<C extends { readonly side: Side; readonly index: number; readonly kept: boolean; readonly age: number }>(cols: readonly C[], order: \"oldest\" | \"newest\"): C[] {\n return [...cols].sort((a, b) => Number(a.kept) - Number(b.kept) || (order === \"oldest\" ? a.age - b.age : b.age - a.age) || a.index - b.index || SIDES.indexOf(a.side) - SIDES.indexOf(b.side));\n}\n\nexport function layoutPanels(defs: readonly PanelDef[], state: PanelsState, stage: Size, options: Partial<PanelsOptions> = {}): PanelsLayout {\n const opt: PanelsOptions = { ...DEFAULT_PANELS_OPTIONS, ...options };\n const W = stage.width;\n const H = stage.height;\n const closed = new Set(state.closed);\n const folded = new Set(state.folded);\n const kept = new Set(state.kept);\n const by = opt.giveWay.by;\n\n // The shown columns.\n const cols: Col[] = [];\n for (const side of SIDES) {\n state.sides[side].forEach((c, index) => {\n const panels = c.panels.filter((id) => !closed.has(id));\n if (panels.length === 0) return;\n const ds = panels.map((id) => defOf(defs, id));\n const min = Math.max(0, ...ds.map((d) => d?.width.min ?? 0));\n const max = Math.max(min, Math.min(...ds.map((d) => d?.width.max ?? Infinity)));\n const width = clamp(Number.isFinite(c.width) ? c.width : min, min, max);\n const age = Math.max(...panels.map((id) => state.clock[id]?.[by] ?? 0));\n cols.push({ side, index, panels, min, max, width, hand: panels.every((id) => folded.has(id)), kept: panels.some((id) => kept.has(id)), age });\n });\n }\n\n // Give-way: fold the fewest columns in order that let the pages fit, shrinking the rest in order toward their minimum.\n const order = giveWayOrder(\n cols.filter((c) => !c.hand),\n opt.giveWay.order,\n );\n const fit = (k: number): { auto: Set<Col>; widths: Map<Col, number>; excess: number } => {\n const auto = new Set(order.slice(0, k));\n const railed = (side: Side): boolean => cols.some((c) => c.side === side && (c.hand || auto.has(c)));\n const widths = new Map(order.filter((c) => !auto.has(c)).map((c) => [c, c.width]));\n let excess = [...widths.values()].reduce((a, b) => a + b, 0) + SIDES.filter(railed).length * opt.rail + opt.pagesMin - W;\n for (const c of order) {\n if (excess <= 0 || auto.has(c)) continue;\n const take = Math.min(excess, c.width - c.min);\n if (take > 0) {\n widths.set(c, c.width - take);\n excess -= take;\n }\n }\n return { auto, widths, excess };\n };\n let g = fit(0);\n for (let k = 1; g.excess > 0 && k <= order.length; k++) g = fit(k);\n\n const notes: PanelsNote[] = [];\n for (const c of order) {\n const w = g.widths.get(c);\n if (w !== undefined && w < c.width) notes.push({ code: \"column-shrunk\", side: c.side, column: c.index, items: c.panels, from: c.width, to: w });\n }\n for (const c of order) if (g.auto.has(c)) notes.push({ code: c.kept ? \"kept-folded\" : \"column-folded\", side: c.side, column: c.index, items: c.panels });\n\n // Positions: rails at the outer edges, open columns inward from them, the pages between.\n const items = new Map<string, PlacedPanel>();\n const placedCols: PlacedColumn[] = [];\n const rails: Partial<Record<Side, PlacedRail>> = {};\n let x0 = 0;\n let x1 = W;\n for (const side of SIDES) {\n const mine = cols.filter((c) => c.side === side);\n const inRail = mine.filter((c) => c.hand || g.auto.has(c));\n if (inRail.length) {\n const r = side === \"left\" ? rect(0, 0, opt.rail, H) : rect(W - opt.rail, 0, opt.rail, H);\n const segments = inRail.flatMap((c) => c.panels).map((id, i) => ({ id, rect: rect(r.x, opt.railInset + i * opt.segment, opt.rail, opt.segment) }));\n rails[side] = { side, rect: r, segments };\n for (const s of segments) {\n const c = inRail.find((x) => x.panels.includes(s.id))!;\n 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 });\n }\n if (side === \"left\") x0 = opt.rail;\n else x1 = W - opt.rail;\n }\n for (const c of mine) {\n const w = g.widths.get(c);\n let r: Rect;\n if (w === undefined) r = rect(side === \"left\" ? x0 : x1, 0, 0, H);\n else if (side === \"left\") {\n r = rect(x0, 0, w, H);\n x0 += w;\n } else {\n x1 -= w;\n r = rect(x1, 0, w, H);\n }\n placedCols.push({ side, index: c.index, rect: r, width: c.width, min: c.min, max: c.max, folded: c.hand ? \"hand\" : g.auto.has(c) ? \"auto\" : null, shrunk: w !== undefined && w < c.width, kept: c.kept, panels: c.panels });\n if (w === undefined) continue;\n const bars = c.panels.filter((id) => folded.has(id)).length;\n const share = (H - bars * opt.bar) / (c.panels.length - bars);\n let y = 0;\n for (const id of c.panels) {\n const bar = folded.has(id);\n const h = bar ? opt.bar : share;\n items.set(id, { rect: rect(r.x, y, r.width, h), kind: bar ? \"bar\" : \"column\", layer: 0, hidden: false, folded: bar ? \"hand\" : null, kept: kept.has(id), pinned: false, side, column: c.index });\n y += h;\n }\n }\n }\n const pages = rect(x0, 0, Math.max(0, x1 - x0), H);\n if (pages.width < opt.pagesMin) notes.push({ code: \"pages-under-min\", width: pages.width, min: opt.pagesMin });\n\n // Floats, clamped unless free. A float's size is screen px in both spaces (never scaled); only an unpinned float's\n // position is in page space. The clamp keeps the SCREEN rect in the view: a pinned float where it is, an unpinned\n // one as it shows at pan 0, so it still scrolls with the page. In page units (k = the scale, 1 when pinned) a\n // screen interval [lo, hi] is [lo / k, hi / k].\n state.floats.forEach((f, i) => {\n if (closed.has(f.id)) return;\n const at = f.rect;\n const k = f.pinned ? 1 : opt.view.scale;\n const r = { ...at };\n if (opt.floats === \"clamp\") {\n const lim = floatSize(defOf(defs, f.id)).min;\n r.width = Math.max(lim.width, Math.min(r.width, W));\n r.height = Math.max(lim.height, Math.min(r.height, H));\n if (r.width !== at.width || r.height !== at.height) notes.push({ code: \"float-resized\", items: [f.id] });\n // Fits: wholly in the view. Too big even at its minimum: its title bar stays in, and `floatVisible` px of its width.\n 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);\n r.y = r.height <= H ? clamp(r.y, 0, (H - r.height) / k) : clamp(r.y, 0, (H - opt.bar) / k);\n if (r.x !== at.x || r.y !== at.y) notes.push({ code: \"float-clamped\", items: [f.id] });\n }\n const clamped = r.x !== at.x || r.y !== at.y || r.width !== at.width || r.height !== at.height;\n items.set(f.id, { rect: f.pinned ? r : toView(r, opt.view), kind: \"float\", layer: 10 + i, hidden: false, folded: null, kept: false, pinned: !!f.pinned, ...(clamped ? { clamped: true } : {}) });\n });\n\n return { items, columns: placedCols, rails, notes, pages };\n}\n","/**\n * The drop targets for dragging a panel, in the shape @n-uf/motion/drag takes (`{ id, priority, hit, slot?, free? }`),\n * worked out from the state with the panel lifted out:\n *\n * - `column:<side>:<i>` (priority 2): a band across the line between two columns (or a side's edge): a new column\n * there, as wide as the panel's own width.\n * - `into:<side>:<i>` (2): a column's area: the panel stacks at its bottom.\n * - `rail:<side>` (1): the side's rail, or its outer edge: the panel docks folded.\n * - `float` (3, free): anywhere; it floats where it is let go, kept in the view as the float option says.\n *\n * A place the panel may not take (its def's `sides` and `floats`, and `allows`) is no target, and neither is its own\n * place (a drop there changes nothing). Targets carry a structured `place`, never words: the consumer names them.\n */\nimport { rect, type Point, type Rect, type Size } from \"../geometry\";\nimport type { Target } from \"../targets/target\";\nimport { layoutPanels, type PanelsLayout } from \"./layout\";\nimport { DEFAULT_PANELS_OPTIONS, SIDES, defOf, dock, float, floatSize, liftPanel, ownWidth, samePlaces, toPage, whereIs, type PanelDef, type PanelsOptions, type PanelsState, type Side } from \"./model\";\n\n/** Where a drop puts a panel. `index` is a column index in the state with the panel lifted out. */\nexport type PanelPlace =\n | { readonly kind: \"column\"; readonly side: Side; readonly index: number }\n | { readonly kind: \"into\"; readonly side: Side; readonly index: number }\n | { readonly kind: \"rail\"; readonly side: Side }\n | { readonly kind: \"float\" };\n\nexport interface PanelTarget extends Target {\n /** The panel dragged. */\n readonly panel: string;\n readonly place: PanelPlace;\n /** Its rank in reading order (left to right across the stage, floating last). */\n readonly order: number;\n /** The state a drop here makes, with the pointer at `p` holding the panel at `grab` from its top-left corner. */\n readonly make: (p: Point, grab: Point) => PanelsState;\n}\n\nexport interface PanelTargetInput {\n readonly defs: readonly PanelDef[];\n /** The state as it is, the panel still in its place. */\n readonly state: PanelsState;\n readonly id: string;\n readonly stage: Size;\n readonly options?: Partial<PanelsOptions>;\n /** Places the panel may go beyond its def's rules (default: all of them). */\n readonly allows?: (id: string, place: PanelPlace) => boolean;\n}\n\n/** How far a column band reaches on each side of its line, px; how wide a side's edge takes a rail drop when it has no rail. */\nexport const COLUMN_REACH = 22;\nexport const RAIL_EDGE = 10;\n\n/** A place's rank in reading order: the left side outer to inner, the right side inner to outer, floating last. */\nexport function placeRank(place: PanelPlace): number {\n switch (place.kind) {\n case \"float\":\n return 3000;\n case \"rail\":\n return place.side === \"left\" ? 0 : 1999;\n case \"column\":\n return place.side === \"left\" ? 1 + 2 * place.index : 1998 - 2 * place.index;\n case \"into\":\n return place.side === \"left\" ? 2 + 2 * place.index : 1997 - 2 * place.index;\n }\n}\n\n/** Where a panel is now, as a place (a float, a column of its own, or stacked in one); null when placed nowhere. */\nexport function homePlace(state: PanelsState, id: string): PanelPlace | null {\n const w = whereIs(state, id);\n if (w.kind === \"float\") return { kind: \"float\" };\n if (w.kind === \"none\") return null;\n return state.sides[w.side][w.index]!.panels.length === 1 ? { kind: \"column\", side: w.side, index: w.index } : { kind: \"into\", side: w.side, index: w.index };\n}\n\nconst cut = (x0: number, x1: number, lo: number, hi: number): [number, number] => [Math.max(x0, lo), Math.min(x1, hi)];\n\n/** The targets for dragging `input.id`, in reading order. */\nexport function panelTargets(input: PanelTargetInput): PanelTarget[] {\n const { defs, state, id, stage } = input;\n const opt: PanelsOptions = { ...DEFAULT_PANELS_OPTIONS, ...input.options };\n const def = defOf(defs, id);\n const s0 = liftPanel(state, id);\n const l0 = layoutPanels(defs, s0, stage, opt);\n const width = ownWidth(defs, state, id);\n const W = stage.width;\n const H = stage.height;\n const R = COLUMN_REACH;\n const out: PanelTarget[] = [];\n const add = (place: PanelPlace, priority: 1 | 2 | 3, hit: Rect, make: PanelTarget[\"make\"], free = false): void => {\n if (hit.width <= 0 || hit.height <= 0) return;\n if (input.allows && !input.allows(id, place)) return;\n // Its own place: a drop there changes nothing, so it is no target (the drag's home covers it).\n // Widths compare as laid out: a stored width past the column's limits is the same place as the limit.\n if (place.kind !== \"float\" && samePlaces(make({ x: 0, y: 0 }, { x: 0, y: 0 }), state, defs)) return;\n out.push({ id: place.kind === \"float\" ? \"float\" : place.kind === \"rail\" ? `rail:${place.side}` : `${place.kind}:${place.side}:${place.index}`, priority, hit, panel: id, place, order: placeRank(place), make, ...(free ? { free: true } : {}) });\n };\n\n for (const side of SIDES) {\n if (def?.sides && !def.sides.includes(side)) continue;\n const left = side === \"left\";\n const rail = l0.rails[side];\n const edge = rail ? rail.rect.width : RAIL_EDGE;\n // The rail (or the bare edge): dock folded, as the outermost column.\n const railHit = rail ? rail.rect : left ? rect(0, 0, RAIL_EDGE, H) : rect(W - RAIL_EDGE, 0, RAIL_EDGE, H);\n add({ kind: \"rail\", side }, 1, railHit, () => {\n const s = dock(state, id, side, 0, { width });\n return { ...s, folded: [...s.folded.filter((x) => x !== id), id] };\n });\n // The lines between the open columns, outer to inner, and each column's area.\n const open = l0.columns.filter((c) => c.side === side && c.folded === null);\n const lo = left ? edge : 0;\n const hi = left ? W : W - edge;\n const band = (x: number, outer: boolean, i: number): void => {\n // The outermost line reaches inward only: outward is the rail's.\n const [a, b] = outer ? (left ? cut(x, x + 2 * R, lo, hi) : cut(x - 2 * R, x, lo, hi)) : cut(x - R, x + R, lo, hi);\n add({ kind: \"column\", side, index: i }, 2, rect(a, 0, b - a, H), () => dock(state, id, side, i, { width }));\n };\n if (open.length === 0) band(left ? l0.pages.x : l0.pages.x + l0.pages.width, true, s0.sides[side].length);\n open.forEach((c, k) => {\n band(left ? c.rect.x : c.rect.x + c.rect.width, k === 0, c.index);\n add({ kind: \"into\", side, index: c.index }, 2, c.rect, () => dock(state, id, side, c.index, { into: true }));\n });\n const last = open.at(-1);\n if (last) band(left ? last.rect.x + last.rect.width : last.rect.x, false, last.index + 1);\n }\n\n if (def?.floats !== false) {\n const was = state.floats.find((f) => f.id === id);\n const size = was ? { width: was.rect.width, height: was.rect.height } : floatSize(def).natural;\n const pinned = !!was?.pinned;\n add(\n { kind: \"float\" },\n 3,\n rect(0, 0, W, H),\n (p, grab) => {\n // Let go at p (view space); stored where it shows once the float option has had its say.\n const at = rect(p.x - grab.x, p.y - grab.y, size.width, size.height);\n const s = float(state, id, pinned ? at : toPage(at, opt.view), pinned);\n const shown = layoutPanels(defs, s, stage, opt).items.get(id)!.rect;\n return float(state, id, pinned ? shown : toPage(shown, opt.view), pinned);\n },\n true,\n );\n }\n return out.sort((a, b) => a.order - b.order);\n}\n\n/** A target's drop, laid out: where the panel lands. */\nexport interface PanelDrop {\n readonly target: string;\n readonly place: PanelPlace;\n /** Same key, same place (a float's key carries where it lands). */\n readonly key: string;\n readonly state: PanelsState;\n readonly layout: PanelsLayout;\n /** Where the panel lands (its rail segment when it docks folded). */\n readonly slot: Rect;\n /** It lands in a rail. */\n readonly hidden: boolean;\n /** It floats where it is let go: no room is made for it. */\n readonly free: boolean;\n}\n\n/** The drop on `target` with the pointer at `p` holding the panel at `grab`, laid out. */\nexport function panelDrop(input: PanelTargetInput, target: PanelTarget, p: Point, grab: Point): PanelDrop {\n const opt: PanelsOptions = { ...DEFAULT_PANELS_OPTIONS, ...input.options };\n const state = target.make(p, grab);\n const layout = layoutPanels(input.defs, state, input.stage, opt);\n const placed = layout.items.get(input.id)!;\n const key = target.free ? `float:${Math.round(placed.rect.x)},${Math.round(placed.rect.y)}` : target.id;\n return { target: target.id, place: target.place, key, state, layout, slot: placed.rect, hidden: placed.hidden, free: !!target.free };\n}\n\n// --- keyboard stops ------------------------------------------------------------------------------------------------\n\nexport interface PanelStop {\n /** The target's id (\"home\" for the panel's own place). */\n readonly id: string;\n readonly place: PanelPlace;\n /** The state a move here makes (the current one for home). */\n readonly state: PanelsState;\n readonly home: boolean;\n}\n\nexport interface PanelStopsOptions {\n /** The panel's own place (`homePlace`) and the state as it is: a stop of its own in its reading-order position. */\n readonly home?: { readonly place: PanelPlace; readonly state: PanelsState };\n /** Floating as a stop at the end (default: left out); `at` and `grab` as for a drop (default: the stage's middle, the corner). */\n readonly floats?: boolean | { readonly at: Point; readonly grab?: Point };\n}\n\n/** The panel's places in reading order, for the arrow keys and a \"Move to\" menu (`keyboardStops` calls this). */\nexport function panelStops(targets: readonly PanelTarget[], options: PanelStopsOptions = {}): PanelStop[] {\n const stops: (PanelStop & { order: number })[] = [];\n for (const t of targets) {\n if (t.place.kind === \"float\") {\n if (!options.floats) continue;\n const at = options.floats === true ? { x: t.hit.x + t.hit.width / 2, y: t.hit.y + t.hit.height / 2 } : options.floats.at;\n const grab = options.floats === true ? { x: 0, y: 0 } : (options.floats.grab ?? { x: 0, y: 0 });\n stops.push({ id: t.id, place: t.place, state: t.make(at, grab), home: false, order: t.order });\n continue;\n }\n stops.push({ id: t.id, place: t.place, state: t.make({ x: 0, y: 0 }, { x: 0, y: 0 }), home: false, order: t.order });\n }\n const h = options.home;\n // A float's home sits with the floats: last, after any float stop.\n if (h) stops.push({ id: \"home\", place: h.place, state: h.state, home: true, order: placeRank(h.place) + (h.place.kind === \"float\" ? 1 : 0.5) });\n return stops.sort((a, b) => a.order - b.order).map(({ order: _order, ...s }) => s);\n}\n"],"mappings":"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","names":["float","rect","f","open"]}
@@ -61,6 +61,53 @@ function sameArrangement(a, b) {
61
61
  });
62
62
  }
63
63
  var isOwnPlace = (a, g, place) => sameArrangement(insertGroup(a, g, place.host, place.zone, place.index), a);
64
+ function storeArrangement(a) {
65
+ const hosts = Object.fromEntries(HOSTS.map((h) => [h, Object.fromEntries(ZONES.map((zn) => [zn, [...a[h][zn]]]))]));
66
+ return { v: 1, hosts, collapsed: [...a.collapsed], floating: a.floating.map((f) => ({ id: f.id, x: f.x, y: f.y, from: f.from ? { ...f.from } : null })) };
67
+ }
68
+ function readArrangement(data, groups) {
69
+ const fail = (error) => ({ ok: false, error });
70
+ const isObject = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
71
+ if (!isObject(data)) return fail("not an object");
72
+ if (data.v !== 1) return fail(`unknown version ${typeof data.v === "number" || typeof data.v === "string" ? String(data.v) : typeof data.v} (this reads version 1)`);
73
+ const seen = /* @__PURE__ */ new Set();
74
+ const ids = (x, where, placed) => {
75
+ if (!Array.isArray(x) || !x.every((g) => typeof g === "string")) return `${where} is not a list of group ids`;
76
+ for (const g of x) {
77
+ if (groups && !groups.includes(g)) return `${where} has an unknown group "${g}"`;
78
+ if (placed && seen.has(g)) return `group "${g}" is in two places`;
79
+ if (placed) seen.add(g);
80
+ }
81
+ return x;
82
+ };
83
+ if (!isObject(data.hosts)) return fail("hosts is missing");
84
+ const hosts = {};
85
+ for (const h of HOSTS) {
86
+ const zones = data.hosts[h];
87
+ if (!isObject(zones)) return fail(`host ${h} is missing`);
88
+ const out = {};
89
+ for (const zn of ZONES) {
90
+ const list = ids(zones[zn], `${h}.${zn}`, true);
91
+ if (typeof list === "string") return fail(list);
92
+ out[zn] = list;
93
+ }
94
+ hosts[h] = out;
95
+ }
96
+ const collapsed = ids(data.collapsed, "collapsed", false);
97
+ if (typeof collapsed === "string") return fail(collapsed);
98
+ if (!Array.isArray(data.floating)) return fail("floating is not a list");
99
+ const floating = [];
100
+ for (const f of data.floating) {
101
+ if (!isObject(f) || typeof f.id !== "string" || typeof f.x !== "number" || typeof f.y !== "number" || !Number.isFinite(f.x) || !Number.isFinite(f.y)) return fail("a floating group is not { id, x, y, from }");
102
+ const id = ids([f.id], "floating", true);
103
+ if (typeof id === "string") return fail(id);
104
+ const from = f.from;
105
+ const place = from === null || from === void 0 ? null : isObject(from) && HOSTS.includes(from.host) && ZONES.includes(from.zone) && Number.isInteger(from.index) && from.index >= 0 ? { host: from.host, zone: from.zone, index: from.index } : void 0;
106
+ if (place === void 0) return fail(`floating group "${f.id}" has a bad home`);
107
+ floating.push({ id: id[0], x: f.x, y: f.y, from: place });
108
+ }
109
+ return { ok: true, arrangement: { ...hosts, collapsed, floating } };
110
+ }
64
111
  function hostNeed(zones, collapsed, spec, size, v) {
65
112
  const lens = ZONES.map((zn) => zones[zn].reduce((s, g) => s + size(g, v, collapsed.includes(g)).along, 0) + GAP * Math.max(0, zones[zn].length - 1));
66
113
  const used = ZONES.filter((zn) => zones[zn].length > 0).length;
@@ -75,7 +122,12 @@ function layoutHost(zones, h, spec, size, byHand = []) {
75
122
  const len = (g) => size(g, v, collapsed.has(g)).along;
76
123
  const zoneLen = (l) => l.reduce((s, g) => s + len(g), 0) + GAP * Math.max(0, l.length - 1);
77
124
  const needNow = () => hostNeed(zones, [...collapsed], spec, size, v);
78
- for (const g of [...ZONES].reverse().flatMap((zn) => [...zones[zn]].reverse())) {
125
+ const keep = (g) => {
126
+ const k = spec.keep && Object.hasOwn(spec.keep, g) ? spec.keep[g] : 0;
127
+ return typeof k === "number" && Number.isFinite(k) ? k : 0;
128
+ };
129
+ const order = [...ZONES].reverse().flatMap((zn) => [...zones[zn]].reverse());
130
+ for (const g of order.sort((x, y) => keep(x) - keep(y))) {
79
131
  if (needNow() <= spec.length) break;
80
132
  if (collapsed.has(g)) continue;
81
133
  collapsed.add(g);
@@ -160,8 +212,10 @@ export {
160
212
  isEmptyHost,
161
213
  sameArrangement,
162
214
  isOwnPlace,
215
+ storeArrangement,
216
+ readArrangement,
163
217
  hostNeed,
164
218
  layoutHost,
165
219
  insertions
166
220
  };
167
- //# sourceMappingURL=chunk-FMTCXDKK.js.map
221
+ //# sourceMappingURL=chunk-WLMJ6JGO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/toolbar/index.ts"],"sourcesContent":["/**\n * @n-uf/placement/toolbar: toolbar groups as a pure model. Commands come in groups, each sitting in one of five\n * hosts (the title bar, the top toolbar, a bottom toolbar, a left and a right side toolbar), in one of three zones\n * (start, centre, end; top, middle, bottom on a side), in order; or floating. A toolbar is a slot: it never moves\n * itself, its groups do. Group sizes come in as numbers; group ids are the consumer's strings.\n *\n * - A host lays its zones out along its length: start from the lead, end up to the tail, the centre centred on the\n * whole host; when the centre would hit the start or end zone it centres in the space between them.\n * - Toolbars never wrap: when room runs out, groups collapse to their handle and » from the trailing end. A group\n * collapsed by hand stays collapsed.\n * - A group drop lands in a zone before all, after all, or between two groups. An empty zone is a target of its\n * own. A group's own place is home: a drop there changes nothing.\n */\nimport type { Rect } from \"../geometry\";\n\nexport type HostId = \"title\" | \"top\" | \"bottom\" | \"left\" | \"right\";\nexport type ZoneId = \"start\" | \"center\" | \"end\";\nexport type Zones<G extends string = string> = Readonly<Record<ZoneId, readonly G[]>>;\n\n/** Where a group is docked: a host, a zone, and its index there. */\nexport interface Place {\n readonly host: HostId;\n readonly zone: ZoneId;\n readonly index: number;\n}\n\n/** A group floating over the content: its top-left corner, and where it was docked. */\nexport interface Floating<G extends string = string> {\n readonly id: G;\n readonly x: number;\n readonly y: number;\n readonly from: Place | null;\n}\n\nexport type Arrangement<G extends string = string> = Readonly<Record<HostId, Zones<G>>> & {\n /** Groups collapsed to their handle and » by hand. */\n readonly collapsed: readonly G[];\n readonly floating: readonly Floating<G>[];\n};\n\nexport const HOSTS: readonly HostId[] = [\"title\", \"top\", \"bottom\", \"left\", \"right\"];\nexport const ZONES: readonly ZoneId[] = [\"start\", \"center\", \"end\"];\n\nexport const HOST_LABEL: Readonly<Record<HostId, string>> = { title: \"Title bar\", top: \"Top bar\", bottom: \"Bottom bar\", left: \"Left edge\", right: \"Right edge\" };\nexport const zoneLabel = (h: HostId, z: ZoneId): string => (vertical(h) ? { start: \"top\", center: \"middle\", end: \"bottom\" } : { start: \"left\", center: \"centre\", end: \"right\" })[z];\n\n/** The side edges hold their groups in a column. */\nexport const vertical = (h: HostId): boolean => h === \"left\" || h === \"right\";\n\n/** Between two groups (the handle of the second is the separator). */\nexport const GAP = 12;\n/** Between zones. */\nexport const ZONE_GAP = 16;\n\n/** An arrangement from the zones each host has (the rest empty), nothing collapsed or floating. */\nexport function arrangement<G extends string>(p: Partial<Record<HostId, Partial<Record<ZoneId, readonly G[]>>>>): Arrangement<G> {\n const z = (q: Partial<Record<ZoneId, readonly G[]>> = {}): Zones<G> => ({ start: [], center: [], end: [], ...q });\n return { title: z(p.title), top: z(p.top), bottom: z(p.bottom), left: z(p.left), right: z(p.right), collapsed: [], floating: [] };\n}\n\n// --- changes -------------------------------------------------------------------------------------------------------\n\nconst mapHosts = <G extends string>(a: Arrangement<G>, f: (h: HostId, zone: ZoneId, list: readonly G[]) => readonly G[]): Arrangement<G> => ({\n ...a,\n ...(Object.fromEntries(HOSTS.map((h) => [h, Object.fromEntries(ZONES.map((zn) => [zn, f(h, zn, a[h][zn])]))])) as Record<HostId, Zones<G>>),\n});\n\n/** Where `g` is docked (null: floating or absent). */\nexport function hostOf<G extends string>(a: Arrangement<G>, g: G): Place | null {\n for (const h of HOSTS) for (const zn of ZONES) if (a[h][zn].includes(g)) return { host: h, zone: zn, index: a[h][zn].indexOf(g) };\n return null;\n}\n\n/** The arrangement with group `g` lifted out (of its host, or from floating). */\nexport const withoutGroup = <G extends string>(a: Arrangement<G>, g: G): Arrangement<G> => ({ ...mapHosts(a, (_h, _z, l) => l.filter((x) => x !== g)), floating: a.floating.filter((f) => f.id !== g) });\n\n/** The arrangement with `g` docked at place `i` of zone `zone` of host `h` (`i` counts the others). */\nexport function insertGroup<G extends string>(a: Arrangement<G>, g: G, h: HostId, zone: ZoneId, i: number): Arrangement<G> {\n return mapHosts(withoutGroup(a, g), (hh, zz, l) => (hh === h && zz === zone ? [...l.slice(0, i), g, ...l.slice(i)] : l));\n}\n\n/** `g` floating with its top-left corner at (x, y); it remembers where it was docked. */\nexport function floatGroup<G extends string>(a: Arrangement<G>, g: G, x: number, y: number): Arrangement<G> {\n const from = hostOf(a, g) ?? a.floating.find((f) => f.id === g)?.from ?? null;\n const out = withoutGroup(a, g);\n return { ...out, floating: [...out.floating, { id: g, x, y, from }] };\n}\n\n/** `g` at `place`, its index kept within the zone. */\nconst dockAt = <G extends string>(a: Arrangement<G>, g: G, place: Place): Arrangement<G> =>\n insertGroup(a, g, place.host, place.zone, Math.min(place.index, a[place.host][place.zone].filter((x) => x !== g).length));\n\n/** `g` docked again where it floated from, else where `home` (the default arrangement) has it. */\nexport function dockGroup<G extends string>(a: Arrangement<G>, g: G, home: Arrangement<G>): Arrangement<G> {\n const from = a.floating.find((f) => f.id === g)?.from ?? hostOf(home, g);\n return from ? dockAt(a, g, from) : a;\n}\n\n/** `g` back where `home` (the default arrangement) has it, expanded. */\nexport function resetGroup<G extends string>(a: Arrangement<G>, g: G, home: Arrangement<G>): Arrangement<G> {\n const place = hostOf(home, g);\n const out = place ? dockAt(a, g, place) : a;\n return { ...out, collapsed: out.collapsed.filter((x) => x !== g) };\n}\n\n/** `g` moved to host `h`: after its last group (in its last zone that has any), or into its start zone. */\nexport function moveToHost<G extends string>(a: Arrangement<G>, g: G, h: HostId): Arrangement<G> {\n const rest = withoutGroup(a, g);\n const zone = [...ZONES].reverse().find((zn) => rest[h][zn].length > 0) ?? \"start\";\n return insertGroup(a, g, h, zone, rest[h][zone].length);\n}\n\n/** Every group of host `from` moved to host `to`, zone by zone, after the groups there. */\nexport function moveBar<G extends string>(a: Arrangement<G>, from: HostId, to: HostId): Arrangement<G> {\n if (from === to) return a;\n return mapHosts(a, (h, zn, l) => (h === from ? [] : h === to ? [...l, ...a[from][zn]] : l));\n}\n\nexport const toggleCollapsed = <G extends string>(a: Arrangement<G>, g: G): Arrangement<G> => ({ ...a, collapsed: a.collapsed.includes(g) ? a.collapsed.filter((x) => x !== g) : [...a.collapsed, g] });\n\nexport const isEmptyHost = <G extends string>(a: Arrangement<G>, h: HostId): boolean => ZONES.every((zn) => a[h][zn].length === 0);\n\nconst samePlace = (a: Place | null, b: Place | null): boolean => (a === null || b === null ? a === b : a.host === b.host && a.zone === b.zone && a.index === b.index);\n\n/**\n * The same arrangement: the same groups in the same order in every zone of every host, the same groups collapsed,\n * and the same groups floating at the same corner with the same home (in any order: neither list's order means\n * anything). Compared field by field, so how the arrangement was written down (its keys' order) doesn't matter.\n */\nexport function sameArrangement<G extends string>(a: Arrangement<G>, b: Arrangement<G>): boolean {\n const sameList = (x: readonly G[], y: readonly G[]): boolean => x.length === y.length && x.every((g, i) => g === y[i]);\n // Both ways, so a repeated id on one side can't stand in for a missing one ([\"a\", \"a\"] is not [\"a\", \"b\"]).\n const sameSet = (x: readonly G[], y: readonly G[]): boolean => x.length === y.length && x.every((g) => y.includes(g)) && y.every((g) => x.includes(g));\n if (!HOSTS.every((h) => ZONES.every((zn) => sameList(a[h][zn], b[h][zn])))) return false;\n if (!sameSet(a.collapsed, b.collapsed)) return false;\n if (!sameSet(a.floating.map((f) => f.id), b.floating.map((f) => f.id))) return false;\n return a.floating.every((f) => {\n const o = b.floating.find((x) => x.id === f.id);\n return !!o && o.x === f.x && o.y === f.y && samePlace(o.from, f.from);\n });\n}\n\n/** Dropping `g` at `place` changes nothing: it is the group's own place, its home, never a target. */\nexport const isOwnPlace = <G extends string>(a: Arrangement<G>, g: G, place: Place): boolean => sameArrangement(insertGroup(a, g, place.host, place.zone, place.index), a);\n\n// --- stored arrangements -------------------------------------------------------------------------------------------\n\n/** An arrangement as stored: version 1. */\nexport interface StoredArrangement<G extends string = string> {\n readonly v: 1;\n readonly hosts: Readonly<Record<HostId, Readonly<Record<ZoneId, readonly G[]>>>>;\n readonly collapsed: readonly G[];\n readonly floating: readonly { readonly id: G; readonly x: number; readonly y: number; readonly from: Place | null }[];\n}\n\n/** What reading a stored arrangement gives: the arrangement, or why it couldn't be read. Never a throw. */\nexport type ArrangementRead<G extends string = string> = { readonly ok: true; readonly arrangement: Arrangement<G> } | { readonly ok: false; readonly error: string };\n\n/** The arrangement as plain data with its version, ready for `JSON.stringify`. */\nexport function storeArrangement<G extends string>(a: Arrangement<G>): StoredArrangement<G> {\n const hosts = Object.fromEntries(HOSTS.map((h) => [h, Object.fromEntries(ZONES.map((zn) => [zn, [...a[h][zn]]]))])) as unknown as Record<HostId, Record<ZoneId, G[]>>;\n return { v: 1, hosts, collapsed: [...a.collapsed], floating: a.floating.map((f) => ({ id: f.id, x: f.x, y: f.y, from: f.from ? { ...f.from } : null })) };\n}\n\n/**\n * A stored arrangement read back from parsed data (`JSON.parse`'s result, not the string). Anything else gives\n * `{ ok: false, error }`, never a throw: an unknown version, a missing host or zone, a value of the wrong kind, a\n * group in two places, or (with `groups`) a group the consumer doesn't have. A group the consumer has but the stored\n * arrangement lacks is no error: the consumer places it (`resetGroup` with its default arrangement, say).\n */\nexport function readArrangement<G extends string = string>(data: unknown, groups?: readonly G[]): ArrangementRead<G> {\n const fail = (error: string): ArrangementRead<G> => ({ ok: false, error });\n const isObject = (x: unknown): x is Record<string, unknown> => typeof x === \"object\" && x !== null && !Array.isArray(x);\n if (!isObject(data)) return fail(\"not an object\");\n // Named without serialising it: a BigInt or a circular value must not throw.\n if (data.v !== 1) return fail(`unknown version ${typeof data.v === \"number\" || typeof data.v === \"string\" ? String(data.v) : typeof data.v} (this reads version 1)`);\n const seen = new Set<string>();\n const ids = (x: unknown, where: string, placed: boolean): G[] | string => {\n if (!Array.isArray(x) || !x.every((g) => typeof g === \"string\")) return `${where} is not a list of group ids`;\n for (const g of x as string[]) {\n if (groups && !(groups as readonly string[]).includes(g)) return `${where} has an unknown group \"${g}\"`;\n if (placed && seen.has(g)) return `group \"${g}\" is in two places`;\n if (placed) seen.add(g);\n }\n return x as G[];\n };\n if (!isObject(data.hosts)) return fail(\"hosts is missing\");\n const hosts = {} as Record<HostId, Zones<G>>;\n for (const h of HOSTS) {\n const zones = data.hosts[h];\n if (!isObject(zones)) return fail(`host ${h} is missing`);\n const out = {} as Record<ZoneId, readonly G[]>;\n for (const zn of ZONES) {\n const list = ids(zones[zn], `${h}.${zn}`, true);\n if (typeof list === \"string\") return fail(list);\n out[zn] = list;\n }\n hosts[h] = out;\n }\n const collapsed = ids(data.collapsed, \"collapsed\", false);\n if (typeof collapsed === \"string\") return fail(collapsed);\n if (!Array.isArray(data.floating)) return fail(\"floating is not a list\");\n const floating: Floating<G>[] = [];\n for (const f of data.floating as unknown[]) {\n if (!isObject(f) || typeof f.id !== \"string\" || typeof f.x !== \"number\" || typeof f.y !== \"number\" || !Number.isFinite(f.x) || !Number.isFinite(f.y)) return fail(\"a floating group is not { id, x, y, from }\");\n const id = ids([f.id], \"floating\", true);\n if (typeof id === \"string\") return fail(id);\n const from = f.from;\n const place = from === null || from === undefined ? null : isObject(from) && HOSTS.includes(from.host as HostId) && ZONES.includes(from.zone as ZoneId) && Number.isInteger(from.index) && (from.index as number) >= 0 ? { host: from.host as HostId, zone: from.zone as ZoneId, index: from.index as number } : undefined;\n if (place === undefined) return fail(`floating group \"${f.id}\" has a bad home`);\n floating.push({ id: id[0]!, x: f.x, y: f.y, from: place });\n }\n return { ok: true, arrangement: { ...hosts, collapsed, floating } };\n}\n\n// --- a host's layout -----------------------------------------------------------------------------------------------\n\n/** A host's room: its length and thickness, and what its ends keep for themselves (window controls, a margin). */\nexport interface HostSpec<G extends string = string> {\n readonly length: number;\n readonly thickness: number;\n readonly lead: number;\n readonly tail: number;\n /**\n * The centre zone's minimum length and its margin on each side, counted in the need whether it holds groups or\n * not (an empty centre keeps its drop size). Unset: the centre counts only its groups.\n */\n readonly center?: { readonly min: number; readonly margin: number };\n /**\n * How long each group keeps its full form when room runs out (default 0): groups with a lower keep collapse\n * first, and among equal keeps the trailing end goes first. A primary group at the end keeps open with a keep of 1.\n */\n readonly keep?: Readonly<Partial<Record<G, number>>>;\n}\n\n/** A group's size in a host, expanded or collapsed: along it (`along`) and across it (`across`). */\nexport type GroupSize<G extends string = string> = (id: G, vertical: boolean, collapsed: boolean) => { along: number; across: number };\n\nexport interface HostLayout<G extends string = string> {\n /** Each group's rect in the host (x along, y across for a horizontal host; transposed on a side). */\n readonly slots: ReadonlyMap<G, Rect>;\n /** Groups shown collapsed (by hand, or because the room ran out). */\n readonly collapsed: readonly G[];\n /** Of those, the ones the room collapsed (from the trailing end). */\n readonly overflow: readonly G[];\n /** The length the groups and the ends take. */\n readonly need: number;\n}\n\n/** How long the host's groups need it to be, collapsing only those in `collapsed`. */\nexport function hostNeed<G extends string>(zones: Zones<G>, collapsed: readonly G[], spec: Pick<HostSpec, \"lead\" | \"tail\" | \"center\">, size: GroupSize<G>, v: boolean): number {\n const lens = ZONES.map((zn) => zones[zn].reduce((s, g) => s + size(g, v, collapsed.includes(g)).along, 0) + GAP * Math.max(0, zones[zn].length - 1));\n const used = ZONES.filter((zn) => zones[zn].length > 0).length;\n const c = spec.center;\n if (!c) return spec.lead + spec.tail + lens.reduce((s, x) => s + x, 0) + ZONE_GAP * Math.max(0, used - 1);\n // The centre always counts, at its minimum at least, and its margins keep it apart from the start and end zones.\n return spec.lead + spec.tail + lens[0]! + lens[2]! + Math.max(lens[1]!, c.min) + 2 * c.margin;\n}\n\n/**\n * Lay a host out: zones along its length; when they don't fit, groups collapse, the lowest keep first and, among\n * equal keeps, from the trailing end.\n */\nexport function layoutHost<G extends string>(zones: Zones<G>, h: HostId, spec: HostSpec<G>, size: GroupSize<G>, byHand: readonly G[] = []): HostLayout<G> {\n const v = vertical(h);\n const collapsed = new Set<G>(ZONES.flatMap((zn) => zones[zn]).filter((g) => byHand.includes(g)));\n const overflow: G[] = [];\n const len = (g: G): number => size(g, v, collapsed.has(g)).along;\n const zoneLen = (l: readonly G[]): number => l.reduce((s, g) => s + len(g), 0) + GAP * Math.max(0, l.length - 1);\n const needNow = (): number => hostNeed(zones, [...collapsed], spec, size, v);\n // The lowest keep first; among equal keeps the trailing end first: the end zone's last group, back to the start\n // zone's first (the sort is stable).\n // Own keys only (a group named \"toString\" mustn't read the prototype), and finite numbers only (NaN breaks the sort).\n const keep = (g: G): number => {\n const k = spec.keep && Object.hasOwn(spec.keep, g) ? spec.keep[g] : 0;\n return typeof k === \"number\" && Number.isFinite(k) ? k : 0;\n };\n const order = [...ZONES].reverse().flatMap((zn) => [...zones[zn]].reverse());\n for (const g of order.sort((x, y) => keep(x) - keep(y))) {\n if (needNow() <= spec.length) break;\n if (collapsed.has(g)) continue;\n collapsed.add(g);\n overflow.push(g);\n }\n const a0 = spec.lead;\n const b0 = spec.length - spec.tail;\n const S = zoneLen(zones.start);\n const C = zoneLen(zones.center);\n const E = zoneLen(zones.end);\n const lo = a0 + S + (S > 0 ? ZONE_GAP : 0);\n const hi = b0 - E - (E > 0 ? ZONE_GAP : 0);\n let c0 = spec.length / 2 - C / 2;\n if (c0 < lo || c0 + C > hi) c0 = (lo + hi) / 2 - C / 2;\n const slots = new Map<G, Rect>();\n const place = (l: readonly G[], from: number): void => {\n let at = from;\n for (const g of l) {\n const s = size(g, v, collapsed.has(g));\n const across = (spec.thickness - s.across) / 2;\n slots.set(g, v ? { x: across, y: at, width: s.across, height: s.along } : { x: at, y: across, width: s.along, height: s.across });\n at += s.along + GAP;\n }\n };\n place(zones.start, a0);\n place(zones.center, c0);\n place(zones.end, b0 - E);\n return { slots, collapsed: [...collapsed], overflow, need: needNow() };\n}\n\n// --- insertion points ----------------------------------------------------------------------------------------------\n\n/** A place a dragged group can go: a zone of a host and the index among the others there, and where it is. */\nexport interface Insertion extends Place {\n /** Along the host: the insertion line (between groups); for an empty zone, the zone's middle. */\n readonly at: number;\n /** The stretch of the host it owns (host coordinates). */\n readonly span: readonly [number, number];\n /** An empty zone: it owns all of its stretch, whatever is nearer (it wins while the pointer is inside it). */\n readonly empty: boolean;\n readonly label: string;\n}\n\n/**\n * The insertion points of a host laid out without the dragged group: in each zone before all, between two, after\n * all; each owns the stretch of the host nearest to it. An empty zone owns its own stretch: the host's first third\n * (start), middle third (centre) or last third (end), less what its neighbours' groups take. `label` names a group\n * in the \"between\" labels (default: its id).\n */\nexport function insertions<G extends string>(zones: Zones<G>, h: HostId, spec: HostSpec<G>, l: HostLayout<G>, label: (g: G) => string = (g) => g): Insertion[] {\n const v = vertical(h);\n const lo = (r: Rect): number => (v ? r.y : r.x);\n const hi = (r: Rect): number => (v ? r.y + r.height : r.x + r.width);\n const L = spec.length;\n const all = ZONES.flatMap((zn) => zones[zn].map((g) => l.slots.get(g)).filter((r): r is Rect => !!r));\n const first = all.length ? Math.min(...all.map(lo)) : L;\n const last = all.length ? Math.max(...all.map(hi)) : 0;\n const zoneEdge = (zn: ZoneId, end: \"lo\" | \"hi\"): number | null => {\n const rs = zones[zn].map((g) => l.slots.get(g)).filter((r): r is Rect => !!r);\n if (!rs.length) return null;\n return end === \"lo\" ? Math.min(...rs.map(lo)) : Math.max(...rs.map(hi));\n };\n const near: Omit<Insertion, \"span\">[] = [];\n const empties: Insertion[] = [];\n for (const zn of ZONES) {\n const name = `${HOST_LABEL[h]} · ${zoneLabel(h, zn)}`;\n const vis = zones[zn].map((g, i) => ({ g, i, r: l.slots.get(g)! }));\n if (!vis.length) {\n const sEnd = zoneEdge(\"start\", \"hi\");\n const eStart = zoneEdge(\"end\", \"lo\");\n const span: [number, number] =\n zn === \"start\" ? [0, Math.min(L / 3, first - GAP / 2)] : zn === \"end\" ? [Math.max((2 * L) / 3, last + GAP / 2), L] : [Math.max(L / 3, (sEnd ?? 0) + GAP / 2), Math.min((2 * L) / 3, (eStart ?? L) - GAP / 2)];\n if (span[1] - span[0] >= 8) empties.push({ host: h, zone: zn, index: 0, at: (span[0] + span[1]) / 2, span, empty: true, label: name });\n continue;\n }\n near.push({ host: h, zone: zn, index: vis[0]!.i, at: lo(vis[0]!.r) - GAP / 2, empty: false, label: `${name} · before all` });\n for (let k = 1; k < vis.length; k++) near.push({ host: h, zone: zn, index: vis[k]!.i, at: (hi(vis[k - 1]!.r) + lo(vis[k]!.r)) / 2, empty: false, label: `${name} · between ${label(vis[k - 1]!.g)} and ${label(vis[k]!.g)}` });\n near.push({ host: h, zone: zn, index: zones[zn].length, at: hi(vis.at(-1)!.r) + GAP / 2, empty: false, label: `${name} · after all` });\n }\n near.sort((a, b) => a.at - b.at);\n const lines = near.map((p, k) => ({ ...p, span: [k === 0 ? 0 : (near[k - 1]!.at + p.at) / 2, k === near.length - 1 ? L : (p.at + near[k + 1]!.at) / 2] as const }));\n return [...lines, ...empties];\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,45 @@
1
+ import { P as PanelsState, S as Side, C as Clock, a as PanelDef } from './targets-BEqoVKbF.js';
2
+ export { b as COLUMN_REACH, c as Column, D as DEFAULT_PANELS_OPTIONS, d as DockOptions, F as FLOAT_HEIGHT, e as FLOAT_MIN, f as FloatAt, G as GiveWay, I as IDENTITY_VIEW, g as PanelDrop, h as PanelKind, i as PanelPlace, j as PanelStop, k as PanelStopsOptions, l as PanelTarget, m as PanelTargetInput, n as PanelsLayout, o as PanelsNote, p as PanelsNoteCode, q as PanelsOptions, r as PanelsStart, s as PlacedColumn, t as PlacedPanel, u as PlacedRail, R as RAIL_EDGE, v as SIDES, V as View, W as Where, w as close, x as columnWidth, y as createPanels, z as defOf, A as dock, B as float, E as floatSize, H as focus, J as giveWayOrder, K as homePlace, L as keep, M as layoutPanels, N as liftPanel, O as open, Q as ownWidth, T as panelDrop, U as panelStops, X as panelTargets, Y as pin, Z as placeRank, _ as resizeColumn, $ as resizeFloat, a0 as samePlaces, a1 as tick, a2 as toPage, a3 as toView, a4 as toggleFold, a5 as whereIs } from './targets-BEqoVKbF.js';
3
+ import { R as Rect } from './geometry-mL-GTRti.js';
4
+
5
+ /**
6
+ * The panels' stored form: `storePanels(state)` gives plain JSON-safe data (`{ v: 1, … }`); `readPanels(data, defs?)`
7
+ * reads parsed data back and never throws. As with the toolbar's `readArrangement`: anything but an object of
8
+ * version 1 is `{ ok: false, error }`. Inside one, it is lenient: an unknown id (with `defs`), a repeated one or a
9
+ * malformed entry is dropped and listed in `dropped`; widths and float sizes are clamped to the defs. A panel the
10
+ * consumer has but the stored state lacks is no error: the consumer places it (`open` with a place, say).
11
+ *
12
+ * Version 1 only gains optional fields. A field this reader doesn't know (a newer writer's, say) is no error either:
13
+ * it is skipped and reported in `dropped` (`"<field>"` at the top, `"sides.<key>"`, `"column.<field>"`,
14
+ * `"float.<field>"`; a clock entry for a panel that isn't placed as `"clock.<id>"`), so a consumer can tell that
15
+ * something was left out.
16
+ */
17
+
18
+ interface StoredPanels {
19
+ readonly v: 1;
20
+ readonly sides: Record<Side, {
21
+ panels: string[];
22
+ width: number;
23
+ }[]>;
24
+ readonly floats: {
25
+ id: string;
26
+ rect: Rect;
27
+ pinned: boolean;
28
+ }[];
29
+ readonly folded: string[];
30
+ readonly kept: string[];
31
+ readonly closed: string[];
32
+ readonly clock: Record<string, Clock>;
33
+ }
34
+ type PanelsRead = {
35
+ readonly ok: true;
36
+ readonly state: PanelsState;
37
+ readonly dropped: readonly string[];
38
+ } | {
39
+ readonly ok: false;
40
+ readonly error: string;
41
+ };
42
+ declare function storePanels(state: PanelsState): StoredPanels;
43
+ declare function readPanels(data: unknown, defs?: readonly PanelDef[]): PanelsRead;
44
+
45
+ export { Clock, PanelDef, type PanelsRead, PanelsState, Side, type StoredPanels, readPanels, storePanels };
package/dist/panels.js ADDED
@@ -0,0 +1,195 @@
1
+ import {
2
+ COLUMN_REACH,
3
+ DEFAULT_PANELS_OPTIONS,
4
+ FLOAT_HEIGHT,
5
+ FLOAT_MIN,
6
+ IDENTITY_VIEW,
7
+ RAIL_EDGE,
8
+ SIDES,
9
+ close,
10
+ columnWidth,
11
+ createPanels,
12
+ defOf,
13
+ dock,
14
+ float,
15
+ floatSize,
16
+ focus,
17
+ giveWayOrder,
18
+ homePlace,
19
+ keep,
20
+ layoutPanels,
21
+ liftPanel,
22
+ open,
23
+ ownWidth,
24
+ panelDrop,
25
+ panelStops,
26
+ panelTargets,
27
+ pin,
28
+ placeRank,
29
+ resizeColumn,
30
+ resizeFloat,
31
+ samePlaces,
32
+ tick,
33
+ toPage,
34
+ toView,
35
+ toggleFold,
36
+ whereIs
37
+ } from "./chunk-NNFNMIW4.js";
38
+ import "./chunk-QMTNB2C3.js";
39
+
40
+ // src/panels/store.ts
41
+ function storePanels(state) {
42
+ const sides = Object.fromEntries(SIDES.map((s) => [s, state.sides[s].map((c) => ({ panels: [...c.panels], width: c.width }))]));
43
+ return {
44
+ v: 1,
45
+ sides,
46
+ 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 })),
47
+ folded: [...state.folded],
48
+ kept: [...state.kept],
49
+ closed: [...state.closed],
50
+ clock: Object.fromEntries(Object.entries(state.clock).map(([k, c]) => [k, { opened: c.opened, used: c.used }]))
51
+ };
52
+ }
53
+ var isObject = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
54
+ var finite = (x) => typeof x === "number" && Number.isFinite(x);
55
+ function readPanels(data, defs) {
56
+ try {
57
+ return read(data, defs);
58
+ } catch (e) {
59
+ return { ok: false, error: `unreadable: ${e instanceof Error ? e.message : typeof e}` };
60
+ }
61
+ }
62
+ function read(data, defs) {
63
+ if (!isObject(data)) return { ok: false, error: "not an object" };
64
+ 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)` };
65
+ if (!isObject(data.sides)) return { ok: false, error: "sides is missing" };
66
+ const dropped = [];
67
+ const drop = (what) => {
68
+ if (!dropped.includes(what)) dropped.push(what);
69
+ };
70
+ const seen = /* @__PURE__ */ new Set();
71
+ const take = (x) => {
72
+ if (typeof x !== "string") {
73
+ drop(`a ${typeof x} where an id goes`);
74
+ return null;
75
+ }
76
+ if (defs && !defOf(defs, x) || seen.has(x)) {
77
+ drop(x);
78
+ return null;
79
+ }
80
+ seen.add(x);
81
+ return x;
82
+ };
83
+ const extra = (o, known, prefix) => {
84
+ for (const k of Object.keys(o)) if (!known.includes(k)) drop(`${prefix}${k}`);
85
+ };
86
+ extra(data, ["v", "sides", "floats", "folded", "kept", "closed", "clock"], "");
87
+ extra(data.sides, SIDES, "sides.");
88
+ const sides = { left: [], right: [] };
89
+ for (const side of SIDES) {
90
+ const cols = data.sides[side];
91
+ if (cols === void 0) continue;
92
+ if (!Array.isArray(cols)) {
93
+ drop(`sides.${side}`);
94
+ continue;
95
+ }
96
+ for (const c of cols) {
97
+ if (!isObject(c) || !Array.isArray(c.panels)) {
98
+ drop(`a column on the ${side}`);
99
+ continue;
100
+ }
101
+ extra(c, ["panels", "width"], "column.");
102
+ const panels = c.panels.map(take).filter((x) => x !== null);
103
+ if (panels.length === 0) continue;
104
+ const ds = defs ? panels.map((id) => defOf(defs, id)) : [];
105
+ const lo = Math.max(0, ...ds.map((d) => d.width.min));
106
+ const hi = Math.max(lo, Math.min(...ds.map((d) => d.width.max)));
107
+ const natural = ds.length ? Math.max(...ds.map((d) => d.width.natural)) : 0;
108
+ const width = finite(c.width) ? c.width : natural;
109
+ sides[side].push({ panels, width: defs ? Math.min(Math.max(width, lo), hi) : Math.max(0, width) });
110
+ }
111
+ }
112
+ const floats = [];
113
+ for (const f of Array.isArray(data.floats) ? data.floats : []) {
114
+ const r = isObject(f) ? f.rect : void 0;
115
+ if (!isObject(f) || !isObject(r) || !finite(r.x) || !finite(r.y) || !finite(r.width) || !finite(r.height)) {
116
+ drop(isObject(f) && typeof f.id === "string" ? f.id : "a float");
117
+ continue;
118
+ }
119
+ extra(f, ["id", "rect", "pinned"], "float.");
120
+ const id = take(f.id);
121
+ if (id === null) continue;
122
+ const min = defs ? floatSize(defOf(defs, id)).min : { width: 0, height: 0 };
123
+ 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 } : {} });
124
+ }
125
+ if (data.floats !== void 0 && !Array.isArray(data.floats)) drop("floats");
126
+ const placed = (key) => {
127
+ const x = data[key];
128
+ if (x === void 0) return [];
129
+ if (!Array.isArray(x)) {
130
+ drop(key);
131
+ return [];
132
+ }
133
+ const out = [];
134
+ for (const id of x) {
135
+ if (typeof id === "string" && seen.has(id)) {
136
+ if (!out.includes(id)) out.push(id);
137
+ } else drop(typeof id === "string" ? id : `a ${typeof id} in ${key}`);
138
+ }
139
+ return out;
140
+ };
141
+ const floating = new Set(floats.map((f) => f.id));
142
+ const kept = placed("kept").filter((id) => {
143
+ if (floating.has(id)) drop(id);
144
+ return !floating.has(id);
145
+ });
146
+ const clock = {};
147
+ if (isObject(data.clock)) {
148
+ for (const id of Object.keys(data.clock)) if (!seen.has(id)) drop(`clock.${id}`);
149
+ for (const id of seen) {
150
+ const c = data.clock[id];
151
+ clock[id] = isObject(c) && finite(c.opened) && finite(c.used) ? { opened: c.opened, used: c.used } : { opened: 0, used: 0 };
152
+ }
153
+ }
154
+ return { ok: true, state: { sides, floats, folded: placed("folded"), kept, closed: placed("closed"), clock }, dropped };
155
+ }
156
+ export {
157
+ COLUMN_REACH,
158
+ DEFAULT_PANELS_OPTIONS,
159
+ FLOAT_HEIGHT,
160
+ FLOAT_MIN,
161
+ IDENTITY_VIEW,
162
+ RAIL_EDGE,
163
+ SIDES,
164
+ close,
165
+ columnWidth,
166
+ createPanels,
167
+ defOf,
168
+ dock,
169
+ float,
170
+ floatSize,
171
+ focus,
172
+ giveWayOrder,
173
+ homePlace,
174
+ keep,
175
+ layoutPanels,
176
+ liftPanel,
177
+ open,
178
+ ownWidth,
179
+ panelDrop,
180
+ panelStops,
181
+ panelTargets,
182
+ pin,
183
+ placeRank,
184
+ readPanels,
185
+ resizeColumn,
186
+ resizeFloat,
187
+ samePlaces,
188
+ storePanels,
189
+ tick,
190
+ toPage,
191
+ toView,
192
+ toggleFold,
193
+ whereIs
194
+ };
195
+ //# sourceMappingURL=panels.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.\n */\nimport type { Rect } from \"../geometry\";\nimport { SIDES, defOf, floatSize, type Clock, type Column, type FloatAt, type PanelDef, type PanelsState, type Side } from \"./model\";\n\nexport interface StoredPanels {\n readonly v: 1;\n readonly sides: Record<Side, { panels: string[]; width: 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 sides = Object.fromEntries(SIDES.map((s) => [s, state.sides[s].map((c) => ({ panels: [...c.panels], width: c.width }))])) as StoredPanels[\"sides\"];\n return {\n v: 1,\n sides,\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\", \"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\"], \"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 sides[side].push({ panels, width: defs ? Math.min(Math.max(width, lo), hi) : Math.max(0, width) });\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 return { ok: true, state: { sides, floats, folded: placed(\"folded\"), kept, closed: placed(\"closed\"), clock }, dropped };\n}\n"],"mappings":"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","names":[]}