@n-uf/placement 26.11.9 → 26.11.11

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.
@@ -4,6 +4,8 @@ import {
4
4
 
5
5
  // src/panels/model.ts
6
6
  var SIDES = ["left", "right"];
7
+ var EDGES = ["top", "bottom"];
8
+ var BAND_SIZE = { min: 120, max: 480, natural: 200 };
7
9
  var IDENTITY_VIEW = { x: 0, y: 0, scale: 1 };
8
10
  var DEFAULT_PANELS_OPTIONS = {
9
11
  giveWay: { order: "oldest", by: "used" },
@@ -16,7 +18,10 @@ var DEFAULT_PANELS_OPTIONS = {
16
18
  rail: 36,
17
19
  segment: 36,
18
20
  railInset: 4,
19
- tabs: 28
21
+ tabs: 28,
22
+ bands: {},
23
+ pagesMinHeight: 240,
24
+ reserve: {}
20
25
  };
21
26
  var FLOAT_HEIGHT = 360;
22
27
  var FLOAT_MIN = { width: 160, height: 120 };
@@ -27,6 +32,10 @@ function whereIs(state, id) {
27
32
  const index = state.sides[side].findIndex((c) => c.panels.includes(id));
28
33
  if (index >= 0) return { kind: "column", side, index };
29
34
  }
35
+ for (const edge of EDGES) {
36
+ const index = state.bands?.[edge]?.panels.indexOf(id) ?? -1;
37
+ if (index >= 0) return { kind: "band", edge, index };
38
+ }
30
39
  const float2 = state.floats.find((f) => f.id === id);
31
40
  return float2 ? { kind: "float", float: float2 } : { kind: "none" };
32
41
  }
@@ -42,6 +51,21 @@ function floatSize(def) {
42
51
  const width = Math.max(FLOAT_MIN.width, def?.width.natural ?? FLOAT_MIN.width);
43
52
  return { min: FLOAT_MIN, natural: { width, height: FLOAT_HEIGHT } };
44
53
  }
54
+ var bandSize = (def) => ({ ...def?.band ?? BAND_SIZE });
55
+ function bandHeight(defs, band) {
56
+ if (!defs) return band.height;
57
+ const ds = band.panels.map((id) => bandSize(defOf(defs, id)));
58
+ const lo = Math.max(0, ...ds.map((d) => d.min));
59
+ const hi = Math.max(lo, Math.min(...ds.map((d) => d.max)));
60
+ return Math.min(Math.max(Number.isFinite(band.height) ? band.height : lo, lo), hi);
61
+ }
62
+ var withBand = (state, edge, band) => {
63
+ const { bands: _b, ...rest } = state;
64
+ const bands = { ...state.bands };
65
+ if (band && band.panels.length) bands[edge] = band;
66
+ else delete bands[edge];
67
+ return EDGES.some((e) => bands[e]) ? { ...rest, bands } : rest;
68
+ };
45
69
  function ownWidth(defs, state, id) {
46
70
  const def = defOf(defs, id);
47
71
  const w = whereIs(state, id);
@@ -63,10 +87,18 @@ function createPanels(defs, start) {
63
87
  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 } : {} };
64
88
  });
65
89
  const sides = { left: (start.left ?? []).map(column), right: (start.right ?? []).map(column) };
66
- const order = [...sides.left, ...sides.right].flatMap((c) => c.panels).concat(floats.map((f) => f.id));
90
+ const bands = {};
91
+ for (const edge of EDGES) {
92
+ const ids = start[edge] ?? [];
93
+ if (!ids.length) continue;
94
+ const natural = Math.max(0, ...ids.map((id) => bandSize(defOf(defs, id)).natural));
95
+ bands[edge] = { panels: [...ids], height: bandHeight(defs, { panels: ids, height: natural }) };
96
+ }
97
+ const order = [...sides.left, ...sides.right].flatMap((c) => c.panels).concat(EDGES.flatMap((e) => bands[e]?.panels ?? [])).concat(floats.map((f) => f.id));
67
98
  const clock = {};
68
99
  order.forEach((id, i) => clock[id] = { opened: i + 1, used: i + 1 });
69
- return { sides, floats, folded: [...start.folded ?? []], kept: [...start.kept ?? []], closed: [...start.closed ?? []], clock };
100
+ const s = { sides, floats, folded: [...start.folded ?? []], kept: [...start.kept ?? []], closed: [...start.closed ?? []], clock };
101
+ return EDGES.some((e) => bands[e]) ? { ...s, bands } : s;
70
102
  }
71
103
  function liftPanel(state, id) {
72
104
  const out = (c) => {
@@ -74,11 +106,22 @@ function liftPanel(state, id) {
74
106
  return { ...c.active === id ? rest : c, panels: without(c.panels, id) };
75
107
  };
76
108
  const lift = (cols) => cols.map(out).filter((c) => c.panels.length > 0);
77
- return { ...state, sides: { left: lift(state.sides.left), right: lift(state.sides.right) }, floats: state.floats.filter((f) => f.id !== id) };
109
+ let s = { ...state, sides: { left: lift(state.sides.left), right: lift(state.sides.right) }, floats: state.floats.filter((f) => f.id !== id) };
110
+ for (const edge of EDGES) {
111
+ const b = s.bands?.[edge];
112
+ if (b?.panels.includes(id)) s = withBand(s, edge, { ...b, panels: without(b.panels, id) });
113
+ }
114
+ return s;
78
115
  }
79
116
  function open(state, id, at) {
80
117
  let s = { ...state, closed: without(state.closed, id) };
81
- if (at && whereIs(s, id).kind === "none") s = { ...s, sides: { ...s.sides, [at.side]: [...s.sides[at.side], { panels: [id], width: at.width }] } };
118
+ if (at && whereIs(s, id).kind === "none") {
119
+ if ("edge" in at) {
120
+ const b = s.bands?.[at.edge];
121
+ const panels = [...b?.panels ?? [], id];
122
+ s = withBand({ ...s, folded: s.folded.filter((x) => !panels.includes(x)) }, at.edge, { panels, height: b?.height ?? at.height ?? BAND_SIZE.natural });
123
+ } else s = { ...s, sides: { ...s.sides, [at.side]: [...s.sides[at.side], { panels: [id], width: at.width }] } };
124
+ }
82
125
  const w = whereIs(s, id);
83
126
  if (w.kind === "column" && s.sides[w.side][w.index].show === "tabs") s = { ...s, sides: { ...s.sides, [w.side]: s.sides[w.side].map((c, i) => i === w.index ? { ...c, active: id } : c) } };
84
127
  return bump(s, id, true);
@@ -90,20 +133,49 @@ function focus(state, id) {
90
133
  return bump(s, id, false);
91
134
  }
92
135
  function toggleFold(state, id) {
136
+ const w = whereIs(state, id);
137
+ if (w.kind === "band") {
138
+ const ids = state.bands[w.edge].panels;
139
+ const shown = ids.filter((x) => !state.closed.includes(x));
140
+ if ((shown.length ? shown : ids).every((x) => state.folded.includes(x))) return bump({ ...state, folded: state.folded.filter((x) => !ids.includes(x)) }, id, false);
141
+ return { ...state, folded: [...state.folded.filter((x) => !ids.includes(x)), ...ids] };
142
+ }
93
143
  if (state.folded.includes(id)) return bump({ ...state, folded: without(state.folded, id) }, id, false);
94
144
  return { ...state, folded: [...state.folded, id] };
95
145
  }
96
146
  function keep(state, id, on) {
97
147
  if (!on) return { ...state, kept: without(state.kept, id) };
98
- return whereIs(state, id).kind === "column" ? { ...state, kept: withOnce(state.kept, id) } : state;
148
+ const k = whereIs(state, id).kind;
149
+ return k === "column" || k === "band" ? { ...state, kept: withOnce(state.kept, id) } : state;
150
+ }
151
+ function dockBand(state, id, edge, index, defs) {
152
+ const s = liftPanel(state, id);
153
+ const b = s.bands?.[edge];
154
+ const panels = [...b?.panels ?? []];
155
+ panels.splice(Math.max(0, Math.min(index, panels.length)), 0, id);
156
+ const height = b?.height ?? bandSize(defs ? defOf(defs, id) : void 0).natural;
157
+ const s1 = withBand(s, edge, { panels, height });
158
+ return bump({ ...s1, folded: s1.folded.filter((x) => !panels.includes(x)), closed: without(s1.closed, id) }, id, false);
159
+ }
160
+ function resizeBand(state, edge, height) {
161
+ const b = state.bands?.[edge];
162
+ if (!b || !Number.isFinite(height)) return state;
163
+ const t = tick(state);
164
+ const clock = { ...state.clock };
165
+ for (const id of b.panels) clock[id] = { opened: clock[id]?.opened ?? 0, used: t };
166
+ return { ...withBand(state, edge, { ...b, height: Math.max(0, height) }), clock };
99
167
  }
100
168
  function dock(state, id, side, index, options = {}) {
101
169
  const w = whereIs(state, id);
102
170
  const from = w.kind === "column" ? state.sides[w.side][w.index].width : 0;
103
171
  const s = liftPanel(state, id);
104
172
  const cols = [...s.sides[side]];
105
- if (options.into && cols[index]) cols[index] = { ...cols[index], panels: [...cols[index].panels, id], ...cols[index].show === "tabs" ? { active: id } : {} };
106
- else {
173
+ if (options.into && cols[index]) {
174
+ const panels = [...cols[index].panels];
175
+ const at = typeof options.at === "number" && !Number.isNaN(options.at) ? Math.max(0, Math.min(Math.floor(options.at), panels.length)) : panels.length;
176
+ panels.splice(at, 0, id);
177
+ cols[index] = { ...cols[index], panels, ...cols[index].show === "tabs" ? { active: id } : {} };
178
+ } else {
107
179
  const at = Math.max(0, Math.min(index, cols.length));
108
180
  cols.splice(at, 0, { panels: [id], width: options.width ?? from });
109
181
  }
@@ -164,8 +236,12 @@ function samePlaces(a, b, defs) {
164
236
  (side) => s.sides[side].map((c) => `${c.panels.join(",")}@${Math.round(columnWidth(defs, c))}${c.show === "tabs" ? `/tabs:${c.active ?? c.panels[0]}` : ""}`).join("|")
165
237
  ).join("\u2016");
166
238
  const fl = (s) => s.floats.map((f) => `${f.id}@${Math.round(f.rect.x)},${Math.round(f.rect.y)},${Math.round(f.rect.width)},${Math.round(f.rect.height)}${f.pinned ? "p" : ""}`).join("|");
239
+ const bs = (s) => EDGES.map((e) => {
240
+ const b2 = s.bands?.[e];
241
+ return b2 ? `${b2.panels.join(",")}@${Math.round(bandHeight(defs, b2))}` : "";
242
+ }).join("\u2016");
167
243
  const set = (l) => [...l].sort().join(",");
168
- return cols(a) === cols(b) && fl(a) === fl(b) && set(a.folded) === set(b.folded) && set(a.kept) === set(b.kept) && set(a.closed) === set(b.closed);
244
+ return cols(a) === cols(b) && bs(a) === bs(b) && fl(a) === fl(b) && set(a.folded) === set(b.folded) && set(a.kept) === set(b.kept) && set(a.closed) === set(b.closed);
169
245
  }
170
246
 
171
247
  // src/panels/layout.ts
@@ -182,6 +258,67 @@ function layoutPanels(defs, state, stage, options = {}) {
182
258
  const kept = new Set(state.kept);
183
259
  const by = opt.giveWay.by;
184
260
  const ride = opt.fold === "ride";
261
+ const px = (v) => typeof v === "number" && Number.isFinite(v) ? Math.max(0, v) : 0;
262
+ const reserveOpt = opt.reserve ?? {};
263
+ const bandOpts = opt.bands ?? {};
264
+ const minH = typeof opt.pagesMinHeight === "number" ? opt.pagesMinHeight : DEFAULT_PANELS_OPTIONS.pagesMinHeight;
265
+ const res = { top: px(reserveOpt.top), right: px(reserveOpt.right), bottom: px(reserveOpt.bottom), left: px(reserveOpt.left) };
266
+ const bandsIn = [];
267
+ const offNotes = [];
268
+ for (const edge of EDGES) {
269
+ const b = state.bands?.[edge];
270
+ if (!b) continue;
271
+ const panels = b.panels.filter((id) => !closed.has(id));
272
+ if (panels.length === 0) continue;
273
+ const o = bandOpts[edge];
274
+ if (!o) {
275
+ offNotes.push({ code: "band-off", edge, items: panels });
276
+ continue;
277
+ }
278
+ const ds = panels.map((id) => bandSize(defOf(defs, id)));
279
+ const min = Math.max(0, ...ds.map((d) => d.min));
280
+ const max = Math.max(min, Math.min(...ds.map((d) => d.max)));
281
+ const height = clamp(Number.isFinite(b.height) ? b.height : min, min, max);
282
+ const age = Math.max(...panels.map((id) => state.clock[id]?.[by] ?? 0));
283
+ bandsIn.push({ edge, panels, at: panels.map((id) => b.panels.indexOf(id)), min, max, height, hand: panels.every((id) => folded.has(id)), kept: panels.some((id) => kept.has(id)), age, mode: o.mode, span: o.span });
284
+ }
285
+ const bOrder = bandsIn.filter((b) => !b.hand).sort((a, b) => Number(a.kept) - Number(b.kept) || (opt.giveWay.order === "oldest" ? a.age - b.age : b.age - a.age) || EDGES.indexOf(b.edge) - EDGES.indexOf(a.edge));
286
+ const gh = { auto: /* @__PURE__ */ new Set(), heights: /* @__PURE__ */ new Map() };
287
+ const fitH = (list, room) => {
288
+ const order2 = bOrder.filter((b) => list.includes(b));
289
+ const hands = list.length - order2.length;
290
+ const fit2 = (k) => {
291
+ const auto = new Set(order2.slice(0, k));
292
+ const heights = new Map(order2.filter((b) => !auto.has(b)).map((b) => [b, b.height]));
293
+ let excess = [...heights.values()].reduce((a, b) => a + b, 0) + (hands + auto.size) * opt.bar - room;
294
+ for (const b of order2) {
295
+ if (excess <= 0 || auto.has(b)) continue;
296
+ const take = Math.min(excess, b.height - b.min);
297
+ if (take > 0) {
298
+ heights.set(b, b.height - take);
299
+ excess -= take;
300
+ }
301
+ }
302
+ return { auto, heights, excess };
303
+ };
304
+ let g2 = fit2(0);
305
+ for (let k = 1; g2.excess > 0 && k <= order2.length; k++) g2 = fit2(k);
306
+ for (const b of g2.auto) gh.auto.add(b);
307
+ for (const [b, h] of g2.heights) gh.heights.set(b, h);
308
+ };
309
+ const reservedIn = bandsIn.filter((b) => b.mode === "reserved");
310
+ const overlayIn = bandsIn.filter((b) => b.mode === "overlay");
311
+ fitH(reservedIn, H - res.top - res.bottom - Math.max(minH, overlayIn.length * opt.bar));
312
+ const bandH = (b) => b ? gh.heights.get(b) ?? opt.bar : 0;
313
+ const pagesH = H - res.top - res.bottom - reservedIn.reduce((a, b) => a + bandH(b), 0);
314
+ fitH(overlayIn, pagesH - minH);
315
+ const topB = bandsIn.find((b) => b.edge === "top");
316
+ const botB = bandsIn.find((b) => b.edge === "bottom");
317
+ const hT = bandH(topB);
318
+ const hB = bandH(botB);
319
+ const colTop = topB?.span === "full" ? (topB.mode === "overlay" ? res.top : 0) + hT : 0;
320
+ const colBot = botB?.span === "full" ? H - (botB.mode === "overlay" ? res.bottom : 0) - hB : H;
321
+ const CH = Math.max(0, colBot - colTop);
185
322
  const cols = [];
186
323
  for (const side of SIDES) {
187
324
  state.sides[side].forEach((c, index) => {
@@ -261,7 +398,7 @@ function layoutPanels(defs, state, stage, options = {}) {
261
398
  const open2 = (side) => cols.some((c) => c.side === side && !c.hand && !auto.has(c));
262
399
  const railed = (side) => cols.some((c) => c.side === side && (auto.has(c) || c.hand && !(ride && open2(side))));
263
400
  const widths = new Map(order.filter((c) => !auto.has(c)).map((c) => [c, c.width]));
264
- let excess = [...widths.values()].reduce((a, b) => a + b, 0) + SIDES.filter(railed).length * opt.rail + opt.pagesMin - W;
401
+ let excess = [...widths.values()].reduce((a, b) => a + b, 0) + SIDES.filter(railed).length * opt.rail + opt.pagesMin + res.left + res.right - W;
265
402
  for (const c of order) {
266
403
  if (excess <= 0 || auto.has(c)) continue;
267
404
  const take = Math.min(excess, c.width - c.min);
@@ -292,8 +429,8 @@ function layoutPanels(defs, state, stage, options = {}) {
292
429
  const riders = host ? mine.filter((c) => c.hand) : [];
293
430
  const inRail = mine.filter((c) => c.hand && !riders.includes(c) || g.auto.has(c));
294
431
  if (inRail.length) {
295
- const r = side === "left" ? rect(0, 0, opt.rail, H) : rect(W - opt.rail, 0, opt.rail, H);
296
- const segments = inRail.flatMap((c) => c.panels).map((id, i) => ({ id, rect: rect(r.x, opt.railInset + i * opt.segment, opt.rail, opt.segment) }));
432
+ const r = side === "left" ? rect(0, colTop, opt.rail, CH) : rect(W - opt.rail, colTop, opt.rail, CH);
433
+ const segments = inRail.flatMap((c) => c.panels).map((id, i) => ({ id, rect: rect(r.x, colTop + opt.railInset + i * opt.segment, opt.rail, opt.segment) }));
297
434
  rails[side] = { side, rect: r, segments };
298
435
  for (const s of segments) {
299
436
  const c = inRail.find((x) => x.panels.includes(s.id));
@@ -307,11 +444,11 @@ function layoutPanels(defs, state, stage, options = {}) {
307
444
  const w = g.widths.get(c);
308
445
  if (w === void 0 || riders.includes(c)) continue;
309
446
  if (side === "left") {
310
- at.set(c, rect(x0, 0, w, H));
447
+ at.set(c, rect(x0, colTop, w, CH));
311
448
  x0 += w;
312
449
  } else {
313
450
  x1 -= w;
314
- at.set(c, rect(x1, 0, w, H));
451
+ at.set(c, rect(x1, colTop, w, CH));
315
452
  }
316
453
  }
317
454
  const riding = riders.flatMap((c) => c.panels.map((id) => ({ id, c })));
@@ -324,13 +461,13 @@ function layoutPanels(defs, state, stage, options = {}) {
324
461
  const hr = at.get(host);
325
462
  const first = riding.findIndex((x) => x.c === c);
326
463
  c.panels.forEach(
327
- (id, i) => items.set(id, { rect: rect(hr.x, (first + i) * opt.bar, hr.width, opt.bar), kind: "bar", layer: 0, hidden: false, folded: "hand", kept: kept.has(id), pinned: false, floating: false, side, column: c.index, rides: host.index })
464
+ (id, i) => items.set(id, { rect: rect(hr.x, colTop + (first + i) * opt.bar, hr.width, opt.bar), kind: "bar", layer: 0, hidden: false, folded: "hand", kept: kept.has(id), pinned: false, floating: false, side, column: c.index, rides: host.index })
328
465
  );
329
- placedCols.push({ ...base, rect: rect(hr.x, first * opt.bar, hr.width, c.panels.length * opt.bar), shrunk: false, rides: host.index });
466
+ placedCols.push({ ...base, rect: rect(hr.x, colTop + first * opt.bar, hr.width, c.panels.length * opt.bar), shrunk: false, rides: host.index });
330
467
  continue;
331
468
  }
332
- const r = at.get(c) ?? rect(side === "left" ? x0 : x1, 0, 0, H);
333
- const y0 = c === host ? top : 0;
469
+ const r = at.get(c) ?? rect(side === "left" ? x0 : x1, colTop, 0, CH);
470
+ const y0 = colTop + (c === host ? top : 0);
334
471
  const tabbed = c.show === "tabs";
335
472
  const strip = rect(r.x, y0, r.width, opt.tabs);
336
473
  const shown = tabbed && w !== void 0 ? { strip, active: c.active } : {};
@@ -340,7 +477,7 @@ function layoutPanels(defs, state, stage, options = {}) {
340
477
  items.set(id, { rect: to, kind, layer: 0, hidden: more.hidden ?? false, folded: f, kept: kept.has(id), pinned: false, floating: false, side, column: c.index, ...more.tab ? { tab: more.tab } : {} });
341
478
  };
342
479
  if (tabbed) {
343
- const body = rect(r.x, y0 + opt.tabs, r.width, Math.max(0, H - y0 - opt.tabs));
480
+ const body = rect(r.x, y0 + opt.tabs, r.width, Math.max(0, colBot - y0 - opt.tabs));
344
481
  const shade = folded.has(c.active);
345
482
  const tw = r.width / c.panels.length;
346
483
  c.panels.forEach((id, i) => {
@@ -350,7 +487,7 @@ function layoutPanels(defs, state, stage, options = {}) {
350
487
  });
351
488
  continue;
352
489
  }
353
- const hs = sectionHeights(c, H - y0);
490
+ const hs = sectionHeights(c, colBot - y0);
354
491
  let y = y0;
355
492
  for (const id of c.panels) {
356
493
  const s = hs.get(id);
@@ -360,8 +497,39 @@ function layoutPanels(defs, state, stage, options = {}) {
360
497
  for (const id of c.panels) if (hs.get(id).folded === "auto") notes.push({ code: "section-folded", side, column: c.index, items: [id] });
361
498
  }
362
499
  }
363
- const pages = rect(x0, 0, Math.max(0, x1 - x0), H);
500
+ for (const b of bOrder) {
501
+ const h = gh.heights.get(b);
502
+ if (h !== void 0 && h < b.height) notes.push({ code: "band-shrunk", edge: b.edge, items: b.panels, from: b.height, to: h });
503
+ }
504
+ for (const b of bOrder) if (gh.auto.has(b)) notes.push({ code: b.kept ? "kept-folded" : "band-folded", edge: b.edge, items: b.panels });
505
+ const pw = Math.max(0, x1 - x0);
506
+ const pTop = topB?.mode === "reserved" ? hT : 0;
507
+ const pBot = botB?.mode === "reserved" ? H - hB : H;
508
+ const reserved = {};
509
+ const iTop = pTop + res.top + (topB?.mode === "overlay" ? hT : 0);
510
+ const iBot = pBot - res.bottom - (botB?.mode === "overlay" ? hB : 0);
511
+ if (res.top > 0) reserved.top = rect(x0, pTop, pw, res.top);
512
+ if (res.right > 0) reserved.right = rect(x0 + pw - res.right, iTop, res.right, Math.max(0, iBot - iTop));
513
+ if (res.bottom > 0) reserved.bottom = rect(x0, pBot - res.bottom, pw, res.bottom);
514
+ if (res.left > 0) reserved.left = rect(x0, iTop, res.left, Math.max(0, iBot - iTop));
515
+ const pages = rect(x0 + res.left, pTop + res.top, Math.max(0, pw - res.left - res.right), Math.max(0, pBot - res.bottom - pTop - res.top));
364
516
  if (pages.width < opt.pagesMin) notes.push({ code: "pages-under-min", width: pages.width, min: opt.pagesMin });
517
+ if ((bandsIn.length || res.top + res.bottom > 0) && pages.height < minH - 1e-9) notes.push({ code: "pages-short", height: pages.height, min: minH });
518
+ const bands = {};
519
+ for (const b of bandsIn) {
520
+ const h = bandH(b);
521
+ const full = b.span === "full";
522
+ const over = b.mode === "overlay";
523
+ const y = b.edge === "top" ? over ? res.top : 0 : over ? H - res.bottom - h : H - h;
524
+ const r = full ? rect(0, y, W, h) : rect(x0, y, pw, h);
525
+ const f = b.hand ? "hand" : gh.auto.has(b) ? "auto" : null;
526
+ const hh = gh.heights.get(b);
527
+ bands[b.edge] = { edge: b.edge, rect: r, height: b.height, min: b.min, max: b.max, folded: f, shrunk: hh !== void 0 && hh < b.height, kept: b.kept, mode: b.mode, span: b.span, panels: b.panels };
528
+ const share = r.width / b.panels.length;
529
+ b.panels.forEach(
530
+ (id, i) => items.set(id, { rect: rect(r.x + i * share, r.y, share, h), kind: f ? "bar" : "band", layer: over ? 5 : 0, hidden: false, folded: f, kept: kept.has(id), pinned: false, floating: false, edge: b.edge, index: b.at[i] })
531
+ );
532
+ }
365
533
  state.floats.forEach((f, i) => {
366
534
  if (closed.has(f.id)) return;
367
535
  const shaded = folded.has(f.id);
@@ -390,7 +558,8 @@ function layoutPanels(defs, state, stage, options = {}) {
390
558
  ...clamped ? { clamped: true } : {}
391
559
  });
392
560
  });
393
- return { items, columns: placedCols, rails, notes, pages };
561
+ notes.push(...offNotes);
562
+ return { items, columns: placedCols, rails, notes, pages, bands, reserved };
394
563
  }
395
564
 
396
565
  // src/panels/targets.ts
@@ -400,19 +569,26 @@ function placeRank(place) {
400
569
  switch (place.kind) {
401
570
  case "float":
402
571
  return 3e3;
572
+ case "band":
573
+ return (place.edge === "top" ? 2e3 : 2500) + Math.min(place.index, 499);
403
574
  case "rail":
404
575
  return place.side === "left" ? 0 : 1999;
405
576
  case "column":
406
577
  return place.side === "left" ? 1 + 2 * place.index : 1998 - 2 * place.index;
407
- case "into":
408
- return place.side === "left" ? 2 + 2 * place.index : 1997 - 2 * place.index;
578
+ case "into": {
579
+ const base = place.side === "left" ? 2 + 2 * place.index : 1997 - 2 * place.index;
580
+ return place.at === void 0 ? base : base - 0.5 + Math.min(Math.max(0, place.at), 499) / 1e3;
581
+ }
409
582
  }
410
583
  }
411
- function homePlace(state, id) {
584
+ function homePlace(state, id, options = {}) {
412
585
  const w = whereIs(state, id);
413
586
  if (w.kind === "float") return { kind: "float" };
414
587
  if (w.kind === "none") return null;
415
- 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 };
588
+ if (w.kind === "band") return { kind: "band", edge: w.edge, index: w.index };
589
+ const col = state.sides[w.side][w.index];
590
+ if (col.panels.length === 1) return { kind: "column", side: w.side, index: w.index };
591
+ return options.stackSlots && col.show !== "tabs" ? { kind: "into", side: w.side, index: w.index, at: col.panels.indexOf(id) } : { kind: "into", side: w.side, index: w.index };
416
592
  }
417
593
  var cut = (x0, x1, lo, hi) => [Math.max(x0, lo), Math.min(x1, hi)];
418
594
  function panelTargets(input) {
@@ -425,19 +601,40 @@ function panelTargets(input) {
425
601
  const W = stage.width;
426
602
  const H = stage.height;
427
603
  const R = COLUMN_REACH;
604
+ const bandOpts = opt.bands ?? {};
605
+ const reserve = opt.reserve ?? {};
606
+ const px = (v) => typeof v === "number" && Number.isFinite(v) ? Math.max(0, v) : 0;
428
607
  const out = [];
429
- const add = (place, priority, hit, make, free = false) => {
608
+ const idOf = (place) => {
609
+ switch (place.kind) {
610
+ case "float":
611
+ return "float";
612
+ case "rail":
613
+ return `rail:${place.side}`;
614
+ case "band":
615
+ return s0.bands?.[place.edge] ? `band:${place.edge}:${place.index}` : `band:${place.edge}`;
616
+ case "into":
617
+ return place.at === void 0 ? `into:${place.side}:${place.index}` : `into:${place.side}:${place.index}:${place.at}`;
618
+ default:
619
+ return `${place.kind}:${place.side}:${place.index}`;
620
+ }
621
+ };
622
+ const add = (place, priority, hit, make, free = false, marker) => {
430
623
  if (hit.width <= 0 || hit.height <= 0) return;
431
624
  if (input.allows && !input.allows(id, place)) return;
432
625
  if (place.kind !== "float" && samePlaces(make({ x: 0, y: 0 }, { x: 0, y: 0 }), state, defs)) return;
433
- 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 } : {} });
626
+ out.push({ id: idOf(place), priority, hit, panel: id, place, order: placeRank(place), make, ...free ? { free: true } : {}, ...marker ? { marker } : {} });
434
627
  };
628
+ const fullTop = l0.bands.top?.span === "full" ? l0.bands.top.rect : null;
629
+ const fullBot = l0.bands.bottom?.span === "full" ? l0.bands.bottom.rect : null;
630
+ const top = fullTop ? fullTop.y + fullTop.height : 0;
631
+ const CH = Math.max(0, (fullBot ? fullBot.y : H) - top);
435
632
  for (const side of SIDES) {
436
633
  if (def?.sides && !def.sides.includes(side)) continue;
437
634
  const left = side === "left";
438
635
  const rail = l0.rails[side];
439
636
  const edge = rail ? rail.rect.width : RAIL_EDGE;
440
- const railHit = rail ? rail.rect : left ? rect(0, 0, RAIL_EDGE, H) : rect(W - RAIL_EDGE, 0, RAIL_EDGE, H);
637
+ const railHit = rail ? rail.rect : left ? rect(0, top, RAIL_EDGE, CH) : rect(W - RAIL_EDGE, top, RAIL_EDGE, CH);
441
638
  add({ kind: "rail", side }, 1, railHit, () => {
442
639
  const s = dock(state, id, side, 0, { width });
443
640
  return { ...s, folded: [...s.folded.filter((x) => x !== id), id] };
@@ -447,16 +644,61 @@ function panelTargets(input) {
447
644
  const hi = left ? W : W - edge;
448
645
  const band = (x, outer, i) => {
449
646
  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);
450
- add({ kind: "column", side, index: i }, 2, rect(a, 0, b - a, H), () => dock(state, id, side, i, { width }));
647
+ add({ kind: "column", side, index: i }, 2, rect(a, top, b - a, CH), () => dock(state, id, side, i, { width }));
451
648
  };
452
- if (open2.length === 0) band(left ? l0.pages.x : l0.pages.x + l0.pages.width, true, s0.sides[side].length);
649
+ if (open2.length === 0) band(left ? l0.pages.x - px(reserve.left) : l0.pages.x + l0.pages.width + px(reserve.right), true, s0.sides[side].length);
453
650
  open2.forEach((c, k) => {
454
651
  band(left ? c.rect.x : c.rect.x + c.rect.width, k === 0, c.index);
455
- add({ kind: "into", side, index: c.index }, 2, c.rect, () => dock(state, id, side, c.index, { into: true }));
652
+ const col = s0.sides[side][c.index];
653
+ const into = () => dock(state, id, side, c.index, { into: true });
654
+ const members = input.stackSlots && col.show !== "tabs" ? col.panels.flatMap((p, at) => {
655
+ const r = l0.items.get(p)?.rect;
656
+ return r ? [{ at, rect: r }] : [];
657
+ }) : [];
658
+ if (members.length === 0) {
659
+ add({ kind: "into", side, index: c.index }, 2, c.rect, into);
660
+ return;
661
+ }
662
+ const mid = (r) => r.y + r.height / 2;
663
+ const marker = (make) => {
664
+ const r = layoutPanels(defs, make(), stage, opt).items.get(id)?.rect;
665
+ return rect(c.rect.x, r ? r.y : c.rect.y, c.rect.width, 0);
666
+ };
667
+ const bottom = c.rect.y + c.rect.height;
668
+ members.forEach(({ at, rect: r }, k2) => {
669
+ const prev = members[k2 - 1];
670
+ const y0 = prev ? mid(prev.rect) : c.rect.y;
671
+ const make = () => dock(state, id, side, c.index, { into: true, at });
672
+ add({ kind: "into", side, index: c.index, at }, 2, rect(c.rect.x, y0, c.rect.width, mid(r) - y0), make, false, marker(make));
673
+ });
674
+ const last2 = members.at(-1).rect;
675
+ add({ kind: "into", side, index: c.index }, 2, rect(c.rect.x, mid(last2), c.rect.width, bottom - mid(last2)), into, false, marker(into));
456
676
  });
457
677
  const last = open2.at(-1);
458
678
  if (last) band(left ? last.rect.x + last.rect.width : last.rect.x, false, last.index + 1);
459
679
  }
680
+ const home = whereIs(state, id);
681
+ for (const e of EDGES) {
682
+ if (!bandOpts[e] || def?.sides && !def.sides.includes(e)) continue;
683
+ const pb = l0.bands[e];
684
+ const list = s0.bands?.[e]?.panels ?? [];
685
+ const make = (i) => () => dockBand(state, id, e, i, defs);
686
+ if (!pb) {
687
+ if (home.kind === "band" && home.edge === e && !s0.bands?.[e]) continue;
688
+ const p = l0.pages;
689
+ const h = Math.min(2 * opt.bar, p.height);
690
+ add({ kind: "band", edge: e, index: list.length }, 2, rect(p.x, e === "top" ? p.y : p.y + p.height - h, p.width, h), make(list.length));
691
+ continue;
692
+ }
693
+ const m = pb.panels.length;
694
+ const share = pb.rect.width / m;
695
+ for (let k = 0; k <= m; k++) {
696
+ const i = k < m ? list.indexOf(pb.panels[k]) : list.length;
697
+ const a = pb.rect.x + Math.max(0, k - 0.5) * share;
698
+ const b = pb.rect.x + Math.min(m, k + 0.5) * share;
699
+ add({ kind: "band", edge: e, index: i }, 2, rect(a, pb.rect.y, b - a, pb.rect.height), make(i));
700
+ }
701
+ }
460
702
  if (def?.floats !== false) {
461
703
  const was = state.floats.find((f) => f.id === id);
462
704
  const size = was ? { width: was.rect.width, height: was.rect.height } : floatSize(def).natural;
@@ -498,12 +740,15 @@ function panelStops(targets, options = {}) {
498
740
  stops.push({ id: t.id, place: t.place, state: t.make({ x: 0, y: 0 }, { x: 0, y: 0 }), home: false, order: t.order });
499
741
  }
500
742
  const h = options.home;
501
- 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) });
743
+ const after = h?.place.kind === "float" ? 1 : h?.place.kind === "into" && h.place.at !== void 0 ? 5e-4 : 0.5;
744
+ if (h) stops.push({ id: "home", place: h.place, state: h.state, home: true, order: placeRank(h.place) + after });
502
745
  return stops.sort((a, b) => a.order - b.order).map(({ order: _order, ...s }) => s);
503
746
  }
504
747
 
505
748
  export {
506
749
  SIDES,
750
+ EDGES,
751
+ BAND_SIZE,
507
752
  IDENTITY_VIEW,
508
753
  DEFAULT_PANELS_OPTIONS,
509
754
  FLOAT_HEIGHT,
@@ -512,6 +757,8 @@ export {
512
757
  tick,
513
758
  defOf,
514
759
  floatSize,
760
+ bandSize,
761
+ bandHeight,
515
762
  ownWidth,
516
763
  createPanels,
517
764
  liftPanel,
@@ -520,6 +767,8 @@ export {
520
767
  focus,
521
768
  toggleFold,
522
769
  keep,
770
+ dockBand,
771
+ resizeBand,
523
772
  dock,
524
773
  showTabs,
525
774
  activate,
@@ -541,4 +790,4 @@ export {
541
790
  panelDrop,
542
791
  panelStops
543
792
  };
544
- //# sourceMappingURL=chunk-PWQWD2CS.js.map
793
+ //# sourceMappingURL=chunk-CJA6LRHN.js.map