@n-uf/placement 26.11.6 → 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,21 +1,8 @@
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
4
  import { Place, Arrangement, HostId, HostSpec, GroupSize } from './toolbar.js';
4
5
 
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
- }
18
-
19
6
  /**
20
7
  * Each desk construction's drop targets, as pure functions: from the state with the dragged item lifted out, a list
21
8
  * of zones with a priority, each able to make the state with the item dropped there.
@@ -185,7 +172,12 @@ interface KeyboardStopsOptions<G extends string> {
185
172
  * The places a group can go, in reading order: hosts title → top → left → right → bottom, zones start → centre →
186
173
  * end, then each insertion point in order; the group's own place among them when `home` is given; floating targets
187
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`).
188
179
  */
180
+ declare function keyboardStops(targets: readonly PanelTarget[], options?: PanelStopsOptions): PanelStop[];
189
181
  declare function keyboardStops<G extends string>(targets: readonly (ToolbarTarget<G> | ToolbarFloatTarget<G>)[], options?: KeyboardStopsOptions<G>): KeyboardStop<G>[];
190
182
 
191
183
  /**
@@ -231,4 +223,4 @@ declare function paneCentre(size: Size, options?: PaneZoneOptions): PaneCentre;
231
223
  */
232
224
  declare function paneZone(rect: Rect, point: Point, previous?: PaneZone | null, options?: PaneZoneOptions): PaneZone;
233
225
 
234
- 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, type Target, type ToolbarFloatTarget, type ToolbarTarget, ZONE_REACH, deskDrop, deskTargets, floatTarget, hostTargets, keyboardStops, 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 };
package/dist/targets.js CHANGED
@@ -24,11 +24,6 @@ import {
24
24
  treeShown,
25
25
  treeTiles
26
26
  } from "./chunk-LPMZ2FFR.js";
27
- import {
28
- centre,
29
- overlap,
30
- rect
31
- } from "./chunk-QMTNB2C3.js";
32
27
  import {
33
28
  HOST_LABEL,
34
29
  ZONES,
@@ -41,6 +36,14 @@ import {
41
36
  withoutGroup,
42
37
  zoneLabel
43
38
  } from "./chunk-WLMJ6JGO.js";
39
+ import {
40
+ panelStops
41
+ } from "./chunk-NNFNMIW4.js";
42
+ import {
43
+ centre,
44
+ overlap,
45
+ rect
46
+ } from "./chunk-QMTNB2C3.js";
44
47
 
45
48
  // src/targets/desk.ts
46
49
  var SNAP = 18;
@@ -382,7 +385,12 @@ function floatTarget(input) {
382
385
  var HOST_READING_ORDER = ["title", "top", "left", "right", "bottom"];
383
386
  var isFloat = (t) => t.kind === "float";
384
387
  var rank = (p) => HOST_READING_ORDER.indexOf(p.host) * 3e3 + ZONES.indexOf(p.zone) * 1e3 + p.index;
388
+ var isPanelTargets = (targets, options) => targets.some((t) => "panel" in t) || targets.length === 0 && !!options.home && "state" in options.home;
385
389
  function keyboardStops(targets, options = {}) {
390
+ if (isPanelTargets(targets, options)) return panelStops(targets, options);
391
+ return toolbarStops(targets, options);
392
+ }
393
+ function toolbarStops(targets, options) {
386
394
  const placed = [];
387
395
  const floating = [];
388
396
  for (const t of targets) {