@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,432 @@
1
+ import { S as Size, R as Rect, P as Point } from './geometry-mL-GTRti.js';
2
+
3
+ /**
4
+ * The panels model: plain data and the pure changes on it.
5
+ *
6
+ * Panels have string ids. Each side holds full-height columns (outer edge first), each column one or more panels
7
+ * top to bottom and its own width. A panel may instead float, in page space (it moves with the page) or pinned in
8
+ * view space (it stays put). A logical clock per panel (opened, used) is what give-way reads; every change here that
9
+ * counts as a use moves it on. Nothing here reads a host clock: the next tick is one past the latest in the state.
10
+ */
11
+
12
+ type Side = "left" | "right";
13
+ declare const SIDES: readonly Side[];
14
+ /**
15
+ * What a consumer says about a panel: its width limits docked, its size limits floating, and where it may go. No
16
+ * words: names and titles are the consumer's.
17
+ */
18
+ interface PanelDef {
19
+ readonly id: string;
20
+ /** A docked column's width limits and its natural width. */
21
+ readonly width: {
22
+ readonly min: number;
23
+ readonly max: number;
24
+ readonly natural: number;
25
+ };
26
+ /** A float's size limits and natural size (default: its natural width, 360 px tall, no smaller than 160 × 120). */
27
+ readonly float?: {
28
+ readonly min: Size;
29
+ readonly natural: Size;
30
+ };
31
+ /** Where it may dock (default: both sides). */
32
+ readonly sides?: readonly Side[];
33
+ /** May it float (default: yes). */
34
+ readonly floats?: boolean;
35
+ }
36
+ /** A full-height column: its panels top to bottom, and its stored width (the layout clamps it to its panels' limits). */
37
+ interface Column {
38
+ readonly panels: readonly string[];
39
+ readonly width: number;
40
+ }
41
+ /** A floating panel: `rect` in page space, or in view space when `pinned`. */
42
+ interface FloatAt {
43
+ readonly id: string;
44
+ readonly rect: Rect;
45
+ readonly pinned?: boolean;
46
+ }
47
+ /** Logical times: when a panel was last opened, and last used (opened, focused, moved or unfolded). */
48
+ interface Clock {
49
+ readonly opened: number;
50
+ readonly used: number;
51
+ }
52
+ interface PanelsState {
53
+ /** Each side's columns, from the outer edge inward. */
54
+ readonly sides: Readonly<Record<Side, readonly Column[]>>;
55
+ /** Floating panels; a later one is on top. */
56
+ readonly floats: readonly FloatAt[];
57
+ /** Folded by hand (give-way never writes this). */
58
+ readonly folded: readonly string[];
59
+ /** Docked panels kept open: their columns give way last. */
60
+ readonly kept: readonly string[];
61
+ /** Not shown at all (they keep their place, for when they open again). */
62
+ readonly closed: readonly string[];
63
+ readonly clock: Readonly<Record<string, Clock>>;
64
+ }
65
+ /** The page's offset and scale in the view: a page point p shows at `x + p.x * scale`, `y + p.y * scale`. */
66
+ interface View {
67
+ readonly x: number;
68
+ readonly y: number;
69
+ readonly scale: number;
70
+ }
71
+ declare const IDENTITY_VIEW: View;
72
+ /** Who gives way first: the oldest or the newest, by when they were last used or last opened. */
73
+ interface GiveWay {
74
+ readonly order: "oldest" | "newest";
75
+ readonly by: "used" | "opened";
76
+ }
77
+ interface PanelsOptions {
78
+ /** Default: `{ order: "oldest", by: "used" }`, so the panel used longest ago gives way first. */
79
+ readonly giveWay: GiveWay;
80
+ /** "clamp" keeps floats in the view at layout time; "free" never moves them. Default "clamp". */
81
+ readonly floats: "clamp" | "free";
82
+ /** How much of a float wider than the view stays in it, px (default 48). */
83
+ readonly floatVisible: number;
84
+ /** The pages' minimum width (default 320). */
85
+ readonly pagesMin: number;
86
+ /** The page's offset and scale (default: identity). */
87
+ readonly view: View;
88
+ /** 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). */
89
+ readonly bar: number;
90
+ /** A rail's width (default 36). */
91
+ readonly rail: number;
92
+ /** A rail segment's height, and the gap above the first one (defaults 36 and 4). */
93
+ readonly segment: number;
94
+ readonly railInset: number;
95
+ }
96
+ declare const DEFAULT_PANELS_OPTIONS: PanelsOptions;
97
+ /** A float's size when its def gives none: this tall, and never smaller than FLOAT_MIN. */
98
+ declare const FLOAT_HEIGHT = 360;
99
+ declare const FLOAT_MIN: Size;
100
+ /** Where a panel is: a column (side and index), floating, or nowhere. */
101
+ type Where = {
102
+ readonly kind: "column";
103
+ readonly side: Side;
104
+ readonly index: number;
105
+ } | {
106
+ readonly kind: "float";
107
+ readonly float: FloatAt;
108
+ } | {
109
+ readonly kind: "none";
110
+ };
111
+ declare function whereIs(state: PanelsState, id: string): Where;
112
+ /** One past the latest logical time in the state. */
113
+ declare const tick: (state: PanelsState) => number;
114
+ declare const defOf: (defs: readonly PanelDef[], id: string) => PanelDef | undefined;
115
+ /** A float's size limits and natural size from its def. */
116
+ declare function floatSize(def: PanelDef | undefined): {
117
+ min: Size;
118
+ natural: Size;
119
+ };
120
+ /**
121
+ * 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;
122
+ * floating or nowhere, its natural width.
123
+ */
124
+ declare function ownWidth(defs: readonly PanelDef[], state: PanelsState, id: string): number;
125
+ interface PanelsStart {
126
+ /** Each side's columns from the outer edge inward, each a list of panel ids top to bottom. */
127
+ readonly left?: readonly (readonly string[])[];
128
+ readonly right?: readonly (readonly string[])[];
129
+ /** Floating panels: where (page space, or view space when pinned); the size defaults to the def's natural size. */
130
+ readonly floats?: readonly {
131
+ readonly id: string;
132
+ readonly x: number;
133
+ readonly y: number;
134
+ readonly width?: number;
135
+ readonly height?: number;
136
+ readonly pinned?: boolean;
137
+ }[];
138
+ readonly folded?: readonly string[];
139
+ readonly kept?: readonly string[];
140
+ readonly closed?: readonly string[];
141
+ }
142
+ /**
143
+ * A state from a plain start: each column as wide as the widest natural width of its panels (within their limits),
144
+ * each float its natural size. The clock counts up in the order the panels are listed (left, right, floats), so the
145
+ * last one listed is the newest.
146
+ */
147
+ declare function createPanels(defs: readonly PanelDef[], start: PanelsStart): PanelsState;
148
+ /** The state with `id` taken out of its column (an emptied column goes) or the floats. Nothing else changes. */
149
+ declare function liftPanel(state: PanelsState, id: string): PanelsState;
150
+ /**
151
+ * Open a panel: it shows again where it was, and it is the newest opened and used. A panel placed nowhere goes into
152
+ * a new innermost column on `at.side`, `at.width` wide; without `at` it stays placed nowhere.
153
+ */
154
+ declare function open(state: PanelsState, id: string, at?: {
155
+ readonly side: Side;
156
+ readonly width: number;
157
+ }): PanelsState;
158
+ /** Close a panel: it isn't shown, keeps its place, and is no longer kept open. */
159
+ declare const close: (state: PanelsState, id: string) => PanelsState;
160
+ /** Focus a panel: it is the newest used; a float comes to the top. */
161
+ declare function focus(state: PanelsState, id: string): PanelsState;
162
+ /** Fold a panel to its title bar by hand, or unfold it (unfolding is a use). */
163
+ declare function toggleFold(state: PanelsState, id: string): PanelsState;
164
+ /** Keep a docked panel open (its column gives way last), or stop keeping it. A floating panel isn't kept. */
165
+ declare function keep(state: PanelsState, id: string, on: boolean): PanelsState;
166
+ interface DockOptions {
167
+ /** Stack into the column at `index` (at its bottom) instead of making a new column there. */
168
+ readonly into?: boolean;
169
+ /** A new column's width (default: the width of the column the panel leaves, else 0, which the layout clamps to the minimum). */
170
+ readonly width?: number;
171
+ }
172
+ /**
173
+ * Dock a panel on `side`: a new column at `index` (0 the outer edge; past the end, innermost), or with `into` at the
174
+ * bottom of the column at `index` (counted once the panel is lifted out). It shows, it is the newest used, and it
175
+ * keeps whether it is kept open.
176
+ */
177
+ declare function dock(state: PanelsState, id: string, side: Side, index: number, options?: DockOptions): PanelsState;
178
+ /**
179
+ * Float a panel at `rect` (page space; view space when pinned). A float stays pinned or not unless `pinned` says;
180
+ * a docked panel floats unpinned. It shows, it is the newest used and on top, and it is no longer kept open.
181
+ */
182
+ declare function float(state: PanelsState, id: string, rect: Rect, pinned?: boolean): PanelsState;
183
+ /** A page-space rect in view space, and back. */
184
+ declare const toView: (r: Rect, v: View) => Rect;
185
+ declare const toPage: (r: Rect, v: View) => Rect;
186
+ /**
187
+ * Pin a float to the view, or unpin it to the page. `view` is where the page is now (default: identity): the float
188
+ * doesn't jump, its rect is converted between the two spaces. A float's size is never scaled.
189
+ *
190
+ * `shown` is where the float shows now, in view space (the layout's placed rect for it). When given, it is converted
191
+ * instead of the stored rect, so a float the clamp moved stays where it shows rather than jumping to its stored place.
192
+ */
193
+ declare function pin(state: PanelsState, id: string, on: boolean, view?: View, shown?: Rect): PanelsState;
194
+ /**
195
+ * Store a column's width (the layout clamps it to its panels' limits). Resizing is a use: the column's panels are
196
+ * the newest used, so give-way takes room from the other columns, not from the one being resized.
197
+ */
198
+ declare function resizeColumn(state: PanelsState, side: Side, index: number, width: number): PanelsState;
199
+ /** Store a float's rect (its own space). Resizing is a use. */
200
+ declare function resizeFloat(state: PanelsState, id: string, rect: Rect): PanelsState;
201
+ /** A column's width as the layout takes it: its stored width clamped to its panels' limits (with `defs`). */
202
+ declare function columnWidth(defs: readonly PanelDef[] | undefined, col: Column): number;
203
+ /**
204
+ * Two states place the panels the same (columns, their panels and widths, floats and their rects; the clock aside).
205
+ * With `defs`, widths compare as the layout takes them (clamped to the panels' limits), so a stored width past a
206
+ * column's max is the same place as that max.
207
+ */
208
+ declare function samePlaces(a: PanelsState, b: PanelsState, defs?: readonly PanelDef[]): boolean;
209
+
210
+ /**
211
+ * `layoutPanels`: the panels' state, a stage and options in; geometry and structure out: every panel's rect, layer
212
+ * and kind, the columns, the rails, the pages' rect, and coded notes on how it gave way. Pure: the same input always
213
+ * lays out the same. How anything looks is the consumer's.
214
+ *
215
+ * - A column is full height and as wide as its stored width, clamped to its panels' limits. Inside it, a panel
216
+ * folded by hand is a title bar (`options.bar` tall) and the open ones share the rest of the height equally.
217
+ * - A column whose panels are all folded is railed: its panels become segments of its side's rail (one rail per
218
+ * side, `options.rail` wide at the outer edge, segments in side order).
219
+ * - Give-way: when the open columns, the rails and `pagesMin` don't fit the stage width, columns shrink toward their
220
+ * minimum, then whole columns fold to the rail, in the policy's order, until the pages fit. Columns holding a kept
221
+ * panel come after every other column; if the pages still don't fit, they fold too (`kept-folded`). Folding by
222
+ * give-way never writes `folded`, so a wider stage brings the column back.
223
+ * - Floats: "clamp" keeps a float in the view at layout time (the state keeps the stored rect, so a larger view
224
+ * brings it back where it was); "free" never moves one. A float's size is never scaled. A pinned float is in view
225
+ * space and clamped against the view; an unpinned one has its position in page space, clamped so that its screen
226
+ * rect at pan 0 fits the view, then moved by the view's offset and scale, so it scrolls with the page.
227
+ */
228
+
229
+ /**
230
+ * Where a panel is, structurally: an open part of a column, a folded title bar in a column, a segment of a rail
231
+ * (the panel itself isn't laid out), or a float.
232
+ */
233
+ type PanelKind = "column" | "bar" | "segment" | "float";
234
+ interface PlacedPanel {
235
+ /** Stage px; for a segment, the segment's rect in its rail. */
236
+ readonly rect: Rect;
237
+ readonly kind: PanelKind;
238
+ /** Columns and rails 0, floats 10 and up (a later float on top). */
239
+ readonly layer: number;
240
+ /** In a rail: there is no panel to lay out, only its segment. */
241
+ readonly hidden: boolean;
242
+ /** Folded by hand (a bar, or a segment of a column folded by hand), or by give-way (a segment). */
243
+ readonly folded: null | "hand" | "auto";
244
+ /** Kept open. */
245
+ readonly kept: boolean;
246
+ /** A pinned float. */
247
+ readonly pinned: boolean;
248
+ /** Docked: its side and column index (the state's). */
249
+ readonly side?: Side;
250
+ readonly column?: number;
251
+ /** A float the clamp moved or resized. */
252
+ readonly clamped?: boolean;
253
+ }
254
+ interface PlacedColumn {
255
+ readonly side: Side;
256
+ /** The column's index in the state. */
257
+ readonly index: number;
258
+ /** Its rect (zero wide when railed). */
259
+ readonly rect: Rect;
260
+ /** Its stored width clamped to its panels' limits, and those limits. */
261
+ readonly width: number;
262
+ readonly min: number;
263
+ readonly max: number;
264
+ /** null: open; "hand": all its panels folded by hand; "auto": folded by give-way. */
265
+ readonly folded: null | "hand" | "auto";
266
+ /** Shrunk by give-way below its width. */
267
+ readonly shrunk: boolean;
268
+ /** It holds a kept panel. */
269
+ readonly kept: boolean;
270
+ /** The shown panels, top to bottom. */
271
+ readonly panels: readonly string[];
272
+ }
273
+ interface PlacedRail {
274
+ readonly side: Side;
275
+ readonly rect: Rect;
276
+ readonly segments: readonly {
277
+ readonly id: string;
278
+ readonly rect: Rect;
279
+ }[];
280
+ }
281
+ type PanelsNoteCode = "column-shrunk" | "column-folded" | "kept-folded" | "pages-under-min" | "float-clamped" | "float-resized";
282
+ /** One note on a layout: a code and its data. */
283
+ type PanelsNote = {
284
+ readonly code: "column-shrunk";
285
+ readonly side: Side;
286
+ readonly column: number;
287
+ readonly items: readonly string[];
288
+ readonly from: number;
289
+ readonly to: number;
290
+ } | {
291
+ readonly code: "column-folded" | "kept-folded";
292
+ readonly side: Side;
293
+ readonly column: number;
294
+ readonly items: readonly string[];
295
+ } | {
296
+ readonly code: "pages-under-min";
297
+ readonly width: number;
298
+ readonly min: number;
299
+ } | {
300
+ readonly code: "float-clamped" | "float-resized";
301
+ readonly items: readonly string[];
302
+ };
303
+ interface PanelsLayout {
304
+ readonly items: Map<string, PlacedPanel>;
305
+ /** The shown columns in side order (left outer → inner, then right outer → inner). */
306
+ readonly columns: readonly PlacedColumn[];
307
+ readonly rails: Partial<Record<Side, PlacedRail>>;
308
+ readonly notes: readonly PanelsNote[];
309
+ /** What is left for the documents. */
310
+ readonly pages: Rect;
311
+ }
312
+ /** The order columns give way in: unkept before kept, then the policy (oldest or newest), outer first, left first. */
313
+ declare function giveWayOrder<C extends {
314
+ readonly side: Side;
315
+ readonly index: number;
316
+ readonly kept: boolean;
317
+ readonly age: number;
318
+ }>(cols: readonly C[], order: "oldest" | "newest"): C[];
319
+ declare function layoutPanels(defs: readonly PanelDef[], state: PanelsState, stage: Size, options?: Partial<PanelsOptions>): PanelsLayout;
320
+
321
+ /**
322
+ * A drop target in the shape @n-uf/motion/drag takes: an id, a priority (1 most precise: a rail or a tab strip;
323
+ * 2 a slot; 3 a broad area such as floating anywhere), the zone the pointer hits, and optionally where the item
324
+ * lands, a label and whether it lands freely (no room is made for it).
325
+ */
326
+ interface Target {
327
+ readonly id: string;
328
+ readonly priority: 1 | 2 | 3;
329
+ readonly hit: Rect;
330
+ readonly slot?: Rect;
331
+ readonly label?: string;
332
+ readonly free?: boolean;
333
+ }
334
+
335
+ /**
336
+ * The drop targets for dragging a panel, in the shape @n-uf/motion/drag takes (`{ id, priority, hit, slot?, free? }`),
337
+ * worked out from the state with the panel lifted out:
338
+ *
339
+ * - `column:<side>:<i>` (priority 2): a band across the line between two columns (or a side's edge): a new column
340
+ * there, as wide as the panel's own width.
341
+ * - `into:<side>:<i>` (2): a column's area: the panel stacks at its bottom.
342
+ * - `rail:<side>` (1): the side's rail, or its outer edge: the panel docks folded.
343
+ * - `float` (3, free): anywhere; it floats where it is let go, kept in the view as the float option says.
344
+ *
345
+ * A place the panel may not take (its def's `sides` and `floats`, and `allows`) is no target, and neither is its own
346
+ * place (a drop there changes nothing). Targets carry a structured `place`, never words: the consumer names them.
347
+ */
348
+
349
+ /** Where a drop puts a panel. `index` is a column index in the state with the panel lifted out. */
350
+ type PanelPlace = {
351
+ readonly kind: "column";
352
+ readonly side: Side;
353
+ readonly index: number;
354
+ } | {
355
+ readonly kind: "into";
356
+ readonly side: Side;
357
+ readonly index: number;
358
+ } | {
359
+ readonly kind: "rail";
360
+ readonly side: Side;
361
+ } | {
362
+ readonly kind: "float";
363
+ };
364
+ interface PanelTarget extends Target {
365
+ /** The panel dragged. */
366
+ readonly panel: string;
367
+ readonly place: PanelPlace;
368
+ /** Its rank in reading order (left to right across the stage, floating last). */
369
+ readonly order: number;
370
+ /** The state a drop here makes, with the pointer at `p` holding the panel at `grab` from its top-left corner. */
371
+ readonly make: (p: Point, grab: Point) => PanelsState;
372
+ }
373
+ interface PanelTargetInput {
374
+ readonly defs: readonly PanelDef[];
375
+ /** The state as it is, the panel still in its place. */
376
+ readonly state: PanelsState;
377
+ readonly id: string;
378
+ readonly stage: Size;
379
+ readonly options?: Partial<PanelsOptions>;
380
+ /** Places the panel may go beyond its def's rules (default: all of them). */
381
+ readonly allows?: (id: string, place: PanelPlace) => boolean;
382
+ }
383
+ /** 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. */
384
+ declare const COLUMN_REACH = 22;
385
+ declare const RAIL_EDGE = 10;
386
+ /** A place's rank in reading order: the left side outer to inner, the right side inner to outer, floating last. */
387
+ declare function placeRank(place: PanelPlace): number;
388
+ /** Where a panel is now, as a place (a float, a column of its own, or stacked in one); null when placed nowhere. */
389
+ declare function homePlace(state: PanelsState, id: string): PanelPlace | null;
390
+ /** The targets for dragging `input.id`, in reading order. */
391
+ declare function panelTargets(input: PanelTargetInput): PanelTarget[];
392
+ /** A target's drop, laid out: where the panel lands. */
393
+ interface PanelDrop {
394
+ readonly target: string;
395
+ readonly place: PanelPlace;
396
+ /** Same key, same place (a float's key carries where it lands). */
397
+ readonly key: string;
398
+ readonly state: PanelsState;
399
+ readonly layout: PanelsLayout;
400
+ /** Where the panel lands (its rail segment when it docks folded). */
401
+ readonly slot: Rect;
402
+ /** It lands in a rail. */
403
+ readonly hidden: boolean;
404
+ /** It floats where it is let go: no room is made for it. */
405
+ readonly free: boolean;
406
+ }
407
+ /** The drop on `target` with the pointer at `p` holding the panel at `grab`, laid out. */
408
+ declare function panelDrop(input: PanelTargetInput, target: PanelTarget, p: Point, grab: Point): PanelDrop;
409
+ interface PanelStop {
410
+ /** The target's id ("home" for the panel's own place). */
411
+ readonly id: string;
412
+ readonly place: PanelPlace;
413
+ /** The state a move here makes (the current one for home). */
414
+ readonly state: PanelsState;
415
+ readonly home: boolean;
416
+ }
417
+ interface PanelStopsOptions {
418
+ /** The panel's own place (`homePlace`) and the state as it is: a stop of its own in its reading-order position. */
419
+ readonly home?: {
420
+ readonly place: PanelPlace;
421
+ readonly state: PanelsState;
422
+ };
423
+ /** Floating as a stop at the end (default: left out); `at` and `grab` as for a drop (default: the stage's middle, the corner). */
424
+ readonly floats?: boolean | {
425
+ readonly at: Point;
426
+ readonly grab?: Point;
427
+ };
428
+ }
429
+ /** The panel's places in reading order, for the arrow keys and a "Move to" menu (`keyboardStops` calls this). */
430
+ declare function panelStops(targets: readonly PanelTarget[], options?: PanelStopsOptions): PanelStop[];
431
+
432
+ export { resizeFloat as $, dock as A, float as B, type Clock as C, DEFAULT_PANELS_OPTIONS as D, floatSize as E, FLOAT_HEIGHT as F, type GiveWay as G, focus as H, IDENTITY_VIEW as I, giveWayOrder as J, homePlace as K, keep as L, layoutPanels as M, liftPanel as N, open as O, type PanelsState as P, ownWidth as Q, RAIL_EDGE as R, type Side as S, panelDrop as T, panelStops as U, type View as V, type Where as W, panelTargets as X, pin as Y, placeRank as Z, resizeColumn as _, type PanelDef as a, samePlaces as a0, tick as a1, toPage as a2, toView as a3, toggleFold as a4, whereIs as a5, type Target as a6, COLUMN_REACH as b, type Column as c, type DockOptions as d, FLOAT_MIN as e, type FloatAt as f, type PanelDrop as g, type PanelKind as h, type PanelPlace as i, type PanelStop as j, type PanelStopsOptions as k, type PanelTarget as l, type PanelTargetInput as m, type PanelsLayout as n, type PanelsNote as o, type PanelsNoteCode as p, type PanelsOptions as q, type PanelsStart as r, type PlacedColumn as s, type PlacedPanel as t, type PlacedRail as u, SIDES as v, close as w, columnWidth as x, createPanels as y, defOf as z };
package/dist/targets.d.ts CHANGED
@@ -1,20 +1,7 @@
1
+ import { a6 as Target, l as PanelTarget, k as PanelStopsOptions, j as PanelStop } from './targets-BEqoVKbF.js';
1
2
  import { R as Rect, P as Point, S as Size } from './geometry-mL-GTRti.js';
2
3
  import { D as DeskState, g as Layout, a as Construction, O as Options } from './constructions-VDK_mwmz.js';
3
- import { Arrangement, HostId, HostSpec, GroupSize, Place } from './toolbar.js';
4
-
5
- /**
6
- * A drop target in the shape @n-uf/motion/drag takes: an id, a priority (1 most precise: a rail or a tab strip;
7
- * 2 a slot; 3 a broad area such as floating anywhere), the zone the pointer hits, and optionally where the item
8
- * lands, a label and whether it lands freely (no room is made for it).
9
- */
10
- interface Target {
11
- readonly id: string;
12
- readonly priority: 1 | 2 | 3;
13
- readonly hit: Rect;
14
- readonly slot?: Rect;
15
- readonly label?: string;
16
- readonly free?: boolean;
17
- }
4
+ import { Place, Arrangement, HostId, HostSpec, GroupSize } from './toolbar.js';
18
5
 
19
6
  /**
20
7
  * Each desk construction's drop targets, as pure functions: from the state with the dragged item lifted out, a list
@@ -73,7 +60,7 @@ declare function deskDrop(input: DeskTargetInput, target: DeskTarget, p: Point,
73
60
  /**
74
61
  * Toolbar group targets: a host's insertion points (lines between groups, priority 2) and empty zones (priority 1),
75
62
  * and floating over the content (priority 3). A group's own place is home, never a target: its stretch comes back
76
- * in `own`, for the consumer to join to the drag's home.
63
+ * in `own`, for the consumer to join to the drag's home. A place `allows` forbids is no host target either.
77
64
  */
78
65
 
79
66
  /** How far a line's hit reaches beyond the host's thickness on each side; an empty zone's. */
@@ -87,9 +74,17 @@ interface ToolbarTarget<G extends string = string> extends Target {
87
74
  readonly vertical: boolean;
88
75
  /** The insertion line (3 px, inset 4 px from the host's sides) in stage coordinates; for an empty zone, at its middle. */
89
76
  readonly line: Rect;
77
+ /** Where the group lands, in stage coordinates: its rect in the host once the drop is made. */
78
+ readonly slot: Rect;
90
79
  /** The arrangement a drop here makes. */
91
80
  readonly arrangement: Arrangement<G>;
92
81
  }
82
+ /**
83
+ * Where a group may go: a host's place, or floating. `hostTargets` leaves out a place it refuses; for floating, the
84
+ * consumer adds `floatTarget` only when `allows(group, "float")`, so one rule covers both. `place.index` counts the
85
+ * groups in that zone without the dragged one (as `insertGroup` does): 0 is before all of the others.
86
+ */
87
+ type Allows<G extends string> = (group: G, place: Place | "float") => boolean;
93
88
  interface HostTargetsInput<G extends string> {
94
89
  readonly group: G;
95
90
  /** The arrangement as it is, the group still in its place. */
@@ -97,10 +92,20 @@ interface HostTargetsInput<G extends string> {
97
92
  readonly host: HostId;
98
93
  /** The host's top-left corner in stage coordinates. */
99
94
  readonly origin: Point;
100
- readonly spec: HostSpec;
95
+ readonly spec: HostSpec<G>;
101
96
  readonly size: GroupSize<G>;
102
97
  /** A group's name in the labels (default: its id). */
103
98
  readonly label?: (g: G) => string;
99
+ /** Where the group may go (default: anywhere). A place it may not go is left out. */
100
+ readonly allows?: Allows<G>;
101
+ /**
102
+ * Where this host is, and how long, once a drop has made `arrangement` (a host may grow or appear when it gets a
103
+ * group); default: where it is now. The targets' `slot`s are laid out there.
104
+ */
105
+ readonly hostAfter?: (arrangement: Arrangement<G>) => {
106
+ readonly origin: Point;
107
+ readonly spec: HostSpec<G>;
108
+ };
104
109
  }
105
110
  /** The targets a host offers a dragged group, zone by zone in order, and the stretches that are its own place. */
106
111
  declare function hostTargets<G extends string>(input: HostTargetsInput<G>): {
@@ -113,14 +118,67 @@ interface ToolbarFloatTarget<G extends string = string> extends Target {
113
118
  /** The group's top-left corner relative to the stage, and the arrangement, for a drop with the pointer at `p`. */
114
119
  readonly make: (p: Point, grab: Point) => Arrangement<G>;
115
120
  }
116
- /** Floating over the content (`stage`, in stage coordinates): the group's top-left corner kept on the stage. */
117
- declare function floatTarget<G extends string>(input: {
121
+ interface FloatTargetInput<G extends string> {
118
122
  readonly group: G;
119
123
  readonly arrangement: Arrangement<G>;
124
+ /** The content the group floats over, in stage coordinates. */
120
125
  readonly stage: Rect;
126
+ /** The group's size as it floats. */
121
127
  readonly width: number;
122
128
  readonly height: number;
123
- }): ToolbarFloatTarget<G>;
129
+ }
130
+ /**
131
+ * Floating over the content (`stage`, in stage coordinates): the group's top-left corner kept on the stage. A
132
+ * group that may not float simply gets no float target: add it only when your rule allows it, with the same
133
+ * `allows` you give `hostTargets`: `...(allows(group, "float") ? [floatTarget(input)] : [])`.
134
+ */
135
+ declare function floatTarget<G extends string>(input: FloatTargetInput<G>): ToolbarFloatTarget<G>;
136
+
137
+ /**
138
+ * Keyboard stops: every place a toolbar group can go, in one list in reading order, for the arrow keys and a
139
+ * "Move to" menu alike. A consumer steps through the list; it never parses target ids.
140
+ */
141
+
142
+ /** The hosts in reading order: the title bar, the top bar, the left edge, the right edge, the bottom bar. */
143
+ declare const HOST_READING_ORDER: readonly HostId[];
144
+ interface KeyboardStop<G extends string = string> {
145
+ /** The target's id ("home" for the group's own place). */
146
+ readonly id: string;
147
+ readonly label: string;
148
+ /** Where in a host it is; null for floating. */
149
+ readonly place: Place | null;
150
+ /** The arrangement a move here makes (the current one for home). */
151
+ readonly arrangement: Arrangement<G>;
152
+ /** The group's own place: where the keyboard starts, and a move there changes nothing. */
153
+ readonly home: boolean;
154
+ }
155
+ interface KeyboardStopsOptions<G extends string> {
156
+ /** The group's own place, as a stop of its own in its reading-order position (the targets leave it out). */
157
+ readonly home?: {
158
+ readonly place: Place;
159
+ readonly arrangement: Arrangement<G>;
160
+ readonly label?: string;
161
+ };
162
+ /**
163
+ * Floating targets as stops at the end (default: left out). `at` is where the pointer would drop it and `grab`
164
+ * where it holds the group (default: the float target's middle, the group's top-left corner).
165
+ */
166
+ readonly floats?: boolean | {
167
+ readonly at: Point;
168
+ readonly grab?: Point;
169
+ };
170
+ }
171
+ /**
172
+ * The places a group can go, in reading order: hosts title → top → left → right → bottom, zones start → centre →
173
+ * end, then each insertion point in order; the group's own place among them when `home` is given; floating targets
174
+ * at the end only when `floats` asks for them. One list serves the arrow keys and a "Move to" menu.
175
+ *
176
+ * Given panel targets (`@n-uf/placement/panels`' `panelTargets`), the same for a panel: left to right across the
177
+ * stage (the left rail, the left side's lines and columns outer to inner, the right side's inner to outer, the right
178
+ * rail), floating last when asked; each stop carries the state a move there makes (`panelStops`).
179
+ */
180
+ declare function keyboardStops(targets: readonly PanelTarget[], options?: PanelStopsOptions): PanelStop[];
181
+ declare function keyboardStops<G extends string>(targets: readonly (ToolbarTarget<G> | ToolbarFloatTarget<G>)[], options?: KeyboardStopsOptions<G>): KeyboardStop<G>[];
124
182
 
125
183
  /**
126
184
  * A pane's drop zones: a centre rectangle (put it in the pane's place, merge or swap) and four edge trapezoids (put
@@ -165,4 +223,4 @@ declare function paneCentre(size: Size, options?: PaneZoneOptions): PaneCentre;
165
223
  */
166
224
  declare function paneZone(rect: Rect, point: Point, previous?: PaneZone | null, options?: PaneZoneOptions): PaneZone;
167
225
 
168
- export { type DeskDrop, type DeskTarget, type DeskTargetInput, type HostTargetsInput, LINE_REACH, type Made, PANE_EDGES, PANE_ZONE_DEFAULTS, type PaneCentre, type PaneZone, type PaneZoneOptions, SNAP, type Target, type ToolbarFloatTarget, type ToolbarTarget, ZONE_REACH, deskDrop, deskTargets, floatTarget, hostTargets, paneCentre, paneZone };
226
+ export { type Allows, type DeskDrop, type DeskTarget, type DeskTargetInput, type FloatTargetInput, HOST_READING_ORDER, type HostTargetsInput, type KeyboardStop, type KeyboardStopsOptions, LINE_REACH, type Made, PANE_EDGES, PANE_ZONE_DEFAULTS, type PaneCentre, type PaneZone, type PaneZoneOptions, SNAP, Target, type ToolbarFloatTarget, type ToolbarTarget, ZONE_REACH, deskDrop, deskTargets, floatTarget, hostTargets, keyboardStops, paneCentre, paneZone };