@n-uf/hypr-tiling 26.7.0

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,314 @@
1
+ export { G as GroupLeavesOptions, M as MULTI_SELECT_GROUP_MIN_MEMBERS, T as TILING_KEYMAP_DEFAULTS, a as TilingKeymapActionGuards, c as canGroupMultiSelection, b as chordRequiresModifier, d as collectGroups, e as collectSplitNodes, f as commandRequiredCapability, g as findLeafByDirection, h as findLeafById, i as groupLeaves, j as hasAnyModifier, k as insertLeafAdjacent, l as isCssEasing, m as isResizeAxisEnabled, n as isStructurallyValidLayout, o as keyboardActionToCommand, p as matchKeyChord, q as matchKeymapAction, r as moveLeafToRoot, s as moveLeafToSplitContainer, t as pruneMultiSelection, u as readLeafNodeIds, v as removeLeafTile, w as resolveDragEasing, x as resolveKeymap, y as resolveMaximizeToggle, z as resolveMultiSelectGroupCommand, A as resolveMultiSelectGroupHost, B as setLeafSizing, C as siblingSubtreeForLeaf, D as swapLeafTiles, E as tileOrderByLeafId, F as toggleLeafMultiSelection, H as toggleSplitAxis, I as ungroupNode, J as updateSplitRatio } from './drag-easing-KxPPNNZT.mjs';
2
+ import { r as TilingLeafDropZone, s as TilingDropAction, t as TilingSplitAxis, j as TilingDropIntentTuningState, u as ResolvedTilingKeymap, v as TilingKeyBinding, w as TilingKeyboardEventLike, x as TilingCommand, T as TilingLayoutNode, y as TilingDimension, z as TilingPaneSizing, A as TilingPaneSizingMode } from './tiling-renderer-kTlSm4H4.mjs';
3
+ export { B as BASELINE_DRAG_HOP_DURATION_MS, I as INSTANT_DRAG_DURATION_MS, C as TilingAccentHue, D as TilingKeyboardAction, E as TilingKeyboardModifierState, F as TilingMoveModeState, G as TilingPaneSwitcherState, H as accentHue } from './tiling-renderer-kTlSm4H4.mjs';
4
+ import 'react';
5
+
6
+ /** A pane's four directional insert edges (the drop zones excluding `"center"`). */
7
+ type TilingEdgeZone = Exclude<TilingLeafDropZone, "center">;
8
+ /** Base drop hit-zone geometry: center swap fraction, floor, and hysteresis. */
9
+ interface TilingDropIntentBaseConfig {
10
+ /**
11
+ * Fraction of each pane axis spanned by the center (swap) rectangle. Acts as
12
+ * the SYMMETRIC value: it sizes both axes unless a per-axis override
13
+ * (`centerRatioX` / `centerRatioY`) is supplied for that axis.
14
+ */
15
+ centerRatio: number;
16
+ /** Per-axis HORIZONTAL (width) swap-zone fraction; falls back to `centerRatio`. */
17
+ centerRatioX?: number;
18
+ /** Per-axis VERTICAL (height) swap-zone fraction; falls back to `centerRatio`. */
19
+ centerRatioY?: number;
20
+ /** Floor for the center rectangle extent so tiny panes keep a usable swap zone. */
21
+ centerMinPx: number;
22
+ /** Boundary stickiness (pane-local px) used to suppress sub-pixel flicker. */
23
+ hysteresisPx: number;
24
+ }
25
+ /**
26
+ * The resolver's full drop-intent result for one pane hover: the resolved zone
27
+ * and action plus the geometry/diagnostic fields that explain how it was
28
+ * reached. Emitted (as {@link TilingDropIntentDebugState}) to `onDropIntentChange`.
29
+ */
30
+ interface TilingDropIntentState {
31
+ /** The hovered leaf this resolution targets. */
32
+ leafId: string;
33
+ /** The resolved drop zone under the cursor. */
34
+ zone: TilingLeafDropZone;
35
+ /** The resolved drop action. */
36
+ action: TilingDropAction;
37
+ /** The nearest edge the cursor points toward (pre-fallback). */
38
+ dominantEdge: TilingEdgeZone;
39
+ /** The edge finally selected after fallbacks, or `null`. */
40
+ finalEdge: TilingEdgeZone | null;
41
+ /** Why the dominant edge was replaced by a fallback, or `null`. */
42
+ fallbackReason: string | null;
43
+ /** Why the action was blocked, or `null` when unblocked. */
44
+ blockedReason: string | null;
45
+ /** Split axes from the root to the target (containment path). */
46
+ axisPath: ReadonlyArray<TilingSplitAxis>;
47
+ /** Edge threshold fraction in effect. */
48
+ edgeThresholdRatio: number;
49
+ /** Center rectangle width (CSS px). */
50
+ centerRectWidthPx: number;
51
+ /** Center rectangle height (CSS px). */
52
+ centerRectHeightPx: number;
53
+ /** Cursor distance to the center rectangle (CSS px). */
54
+ centerDistancePx: number;
55
+ /** Cursor distance to the nearest edge (CSS px). */
56
+ nearestEdgeDistancePx: number;
57
+ /** Pane-local cursor X (CSS px). */
58
+ paneLocalX: number;
59
+ /** Pane-local cursor Y (CSS px). */
60
+ paneLocalY: number;
61
+ /** The ancestor split chosen for a split-container insert, or `null`. */
62
+ targetSplitId: string | null;
63
+ /** Which child slot of that split the insert targets, or `null`. */
64
+ targetSplitPlacement: "first" | "second" | null;
65
+ /** The edge zone selected on the chosen split, or `null`. */
66
+ selectedSplitZone: TilingEdgeZone | null;
67
+ /** Cursor distance to the selected split edge (CSS px), or `null`. */
68
+ selectedSplitDistancePx: number | null;
69
+ /** Reasons candidate splits were rejected during resolution. */
70
+ rejectedSplitReasons: ReadonlyArray<string>;
71
+ /** The tuning knobs that shaped this resolution. */
72
+ tuning: TilingDropIntentTuningState;
73
+ }
74
+ /**
75
+ * The default drop hit-zone geometry: a `0.34` center swap fraction, a `24`px
76
+ * center floor, and `6`px boundary hysteresis. The baseline
77
+ * `dropHitZoneGeometry` capability resolves to these values.
78
+ */
79
+ declare const TILING_DROP_INTENT_CONFIG: TilingDropIntentBaseConfig;
80
+
81
+ /**
82
+ * The resolved hover target the FSM tracks. Structurally a `TilingDropIntentState`
83
+ * (the resolver output the renderer already produces), so the renderer can store
84
+ * the full resolved intent verbatim and the candidate-tree derivation / commit
85
+ * both read the SAME object the preview render reads — no second resolution path.
86
+ */
87
+ type DragResolvedTarget = TilingDropIntentState;
88
+
89
+ /**
90
+ * Public keyboard-binding registry (HT-API-COMMAND-KEYBOARD-SURFACE, half B).
91
+ *
92
+ * The chord→command registration surface — the Hyprland `bind` analog. Distinct
93
+ * from the fixed `TilingKeymap` chord overrides (which only RE-CHORD the built-in
94
+ * action set): a binding maps an ARBITRARY chord to an ARBITRARY `TilingCommand`,
95
+ * so a consumer can wire any shortcut to any tiler action (including programmatic
96
+ * commands like swap / split-ratio).
97
+ *
98
+ * Pure + DOM-less. The renderer's keydown resolves a command in this order:
99
+ * consumer bindings (first match wins, so they augment / override defaults) →
100
+ * default keymap bindings (unless `replaceDefaults`) → the jump-to-pane Alt+1..9
101
+ * family (kept in the keymap path since the digit is dynamic) → capability gate.
102
+ *
103
+ * Cross-ref: `_agent/command-keyboard-api-design.md` §3; `commands.ts`
104
+ * (`keyboardActionToCommand` + `isCommandEnabled`); `pane-switching.ts`
105
+ * (`matchKeyChord` + `matchKeymapAction`).
106
+ */
107
+
108
+ /**
109
+ * The command of the FIRST binding whose chord matches `event` (exact modifier
110
+ * match on the physical `event.code`), or `null` when none match. First-match
111
+ * semantics make binding order significant: an earlier binding wins a chord
112
+ * collision. Capability gating is applied by the caller (`isCommandEnabled`),
113
+ * not here — this is pure chord resolution.
114
+ */
115
+ declare function matchKeyBinding(event: TilingKeyboardEventLike, bindings: ReadonlyArray<TilingKeyBinding>): TilingCommand | null;
116
+ /**
117
+ * The default chord→command bindings derived from a resolved keymap — the
118
+ * built-in action set expressed as the binding registry (so a consumer can
119
+ * introspect / extend it). The jump-to-pane Alt+1..9 family is intentionally
120
+ * EXCLUDED: its digit is dynamic, so it stays in the `matchKeymapAction` path
121
+ * rather than being enumerated as nine static bindings.
122
+ */
123
+ declare function defaultKeyBindings(keymap: ResolvedTilingKeymap): ReadonlyArray<TilingKeyBinding>;
124
+
125
+ /**
126
+ * MRU focus history — the pure model behind the "focus current-or-last" toggle
127
+ * (HT-NAV-MRU-FOCUS-TOGGLE; Hyprland `focuscurrentorlast` analog).
128
+ *
129
+ * hypr-tiling cycles a fixed reading-order ring (`resolveCycledPaneId`) with no
130
+ * memory of WHICH pane was focused before the current one. This module adds a
131
+ * most-recently-used stack so a consumer (or the `focus-current-or-last`
132
+ * command) can jump back to the previously-focused pane and toggle between the
133
+ * two — independent of tree order.
134
+ *
135
+ * Pure + DOM-less (the renderer holds the `FocusHistory` in a ref and pushes on
136
+ * every focus change); unit-tested in the `node` jest environment.
137
+ *
138
+ * Cross-ref: `_agent/command-keyboard-api-design.md` §4;
139
+ * `_agent/comparative-analysis/parity-report.md` §5 #8 (MRU focus-toggle gap);
140
+ * `_agent/programme-debt/D-a11y-navigation.md` (HT-NAV-MRU-FOCUS-TOGGLE).
141
+ */
142
+ /**
143
+ * An ordered most-recently-used focus list. `entries` is oldest-first /
144
+ * MOST-RECENT-LAST; the final entry is the currently-focused pane once it has
145
+ * been pushed. Each leaf id appears at most once (a re-focus moves it to the
146
+ * end rather than duplicating).
147
+ */
148
+ interface FocusHistory {
149
+ /** Leaf ids in MRU order: oldest-first, most-recently-focused last. */
150
+ readonly entries: ReadonlyArray<string>;
151
+ }
152
+ /** Default cap on retained history entries (older entries are dropped from the front). */
153
+ declare const FOCUS_HISTORY_DEFAULT_LIMIT: number;
154
+ /** An empty focus history (no panes focused yet). */
155
+ declare const EMPTY_FOCUS_HISTORY: FocusHistory;
156
+ /**
157
+ * Push `leafId` as the most-recent focus. If it already appears it is MOVED to
158
+ * the end (de-dupe, not duplicate), so the history stays a clean MRU ordering.
159
+ * The list is capped to `limit` by dropping the oldest (front) entries. A
160
+ * `limit <= 0` clamps to `1` (the current focus is always retained).
161
+ */
162
+ declare function pushFocusHistory(history: FocusHistory, leafId: string, limit?: number): FocusHistory;
163
+ /**
164
+ * Resolve the "current-or-last" target: the most-recent entry that is NOT
165
+ * `currentLeafId` (the previously-focused distinct pane). Returns `null` when
166
+ * there is no such pane (empty history, or only the current pane is recorded).
167
+ *
168
+ * `currentLeafId == null` returns the most-recent entry outright (no current
169
+ * focus to skip). Because focusing the returned pane pushes it to most-recent,
170
+ * repeated toggles bounce between the two panes naturally.
171
+ */
172
+ declare function resolveFocusCurrentOrLast(history: FocusHistory, currentLeafId: string | null): string | null;
173
+ /**
174
+ * Drop history entries whose leaf id is no longer present in the live tree
175
+ * (`validLeafIds`), preserving MRU order. Called after a layout change so a
176
+ * removed pane is never returned by `resolveFocusCurrentOrLast`.
177
+ */
178
+ declare function pruneFocusHistory(history: FocusHistory, validLeafIds: ReadonlyArray<string>): FocusHistory;
179
+
180
+ /** Resolve the sizing mode for a single dimension; undefined defaults to flexible. */
181
+ declare function resolveSizingMode(sizing: TilingPaneSizing | undefined, dimension: TilingDimension): TilingPaneSizingMode;
182
+ /** True when the node is content-sized (static) in the given dimension. */
183
+ declare function isStaticInDimension(node: TilingLayoutNode, dimension: TilingDimension): boolean;
184
+ /**
185
+ * True when the node is static ALONG the split's main axis → it is content-sized
186
+ * along that axis, excluded from the split's ratio, and removes the divider on
187
+ * that boundary.
188
+ */
189
+ declare function isStaticAlongSplitAxis(node: TilingLayoutNode, axis: TilingSplitAxis): boolean;
190
+ /**
191
+ * True when the node is static on the split's CROSS axis → it content-sizes on
192
+ * the cross axis (no stretch) but still participates in the split-axis ratio.
193
+ */
194
+ declare function isStaticOnCrossAxis(node: TilingLayoutNode, axis: TilingSplitAxis): boolean;
195
+ /**
196
+ * True when the layout tree contains ANY node declared static in either
197
+ * dimension — a generic whole-tree predicate. NOTE: this is no longer the drag
198
+ * gate. The drag/rearrange gate is now PER-SUBTREE (`drop-validity.ts`
199
+ * `collectStaticGatedLeafIds`) and the footprint geometry is static-aware
200
+ * (`leaf-geometry.ts`), so a pinned static pane no longer freezes the whole tree
201
+ * (HT-SIZING-STATIC-DRAG-GATING). Retained as a reusable predicate.
202
+ */
203
+ declare function layoutContainsStaticPane(node: TilingLayoutNode): boolean;
204
+ /** Inputs deciding whether a split boundary renders a draggable resize divider. */
205
+ interface SplitBoundaryStaticFlags {
206
+ /** Whether divider resize is enabled for this boundary. */
207
+ resizeEnabled: boolean;
208
+ /** Whether the first child is static along the split axis. */
209
+ firstStaticAlongAxis: boolean;
210
+ /** Whether the second child is static along the split axis. */
211
+ secondStaticAlongAxis: boolean;
212
+ }
213
+ /**
214
+ * A resize divider is placed ONLY between two boundaries that are both flexible
215
+ * along the split axis. If resize is disabled, or either adjacent child is
216
+ * static along the split axis, no draggable handle is rendered there.
217
+ */
218
+ declare function shouldRenderSplitDivider({ resizeEnabled, firstStaticAlongAxis, secondStaticAlongAxis, }: SplitBoundaryStaticFlags): boolean;
219
+ /** One child's ratio + static-along-axis flag, input to ratio renormalization. */
220
+ interface FlexibleRatioChild {
221
+ /** The child's declared split-axis ratio. */
222
+ ratio: number;
223
+ /** Whether the child is static along the split axis (weight 0 in distribution). */
224
+ staticAlongAxis: boolean;
225
+ }
226
+ /**
227
+ * Renormalize split-axis ratios over the FLEXIBLE children only. Static children
228
+ * receive weight 0 (content-sized, excluded from distribution); flexible children
229
+ * share `1.0` proportionally to their declared ratios. When the flexible ratios
230
+ * sum to zero (or there are no positive ratios), flexible children split evenly.
231
+ */
232
+ declare function renormalizeFlexibleRatios(children: ReadonlyArray<FlexibleRatioChild>): number[];
233
+
234
+ /**
235
+ * Custom-rendered drag cursor (interaction tier "c"). A single `position: fixed`
236
+ * sibling of the cursor-following ghost (`DragPaneOverlay`) that REPLACES the OS
237
+ * cursor during an active live drag, transform-pinned to the pointer in the same
238
+ * coalesced rAF/render path as the ghost so it never lags the hardware cursor.
239
+ *
240
+ * This module is the PURE, DOM-less core: it maps the drag FSM's resolved target
241
+ * (`DragResolvedTarget` = the resolver's `TilingDropIntentState`) plus the drop
242
+ * validity onto a small SEMANTIC presentation descriptor — the OPERATION the
243
+ * release would perform (grab / insert / swap) or its rejection (invalid) — NOT
244
+ * an edge direction. The found slot itself already shows where the pane lands;
245
+ * the cursor only confirms what kind of drop is under the pointer. The renderer's
246
+ * `DragCursorOverlay` consumes these outputs and owns the actual SVG/Tailwind +
247
+ * reduced-motion transitions.
248
+ */
249
+ /**
250
+ * The semantic cursor states, driven by the drag FSM + resolver operation type +
251
+ * validity (NOT by edge direction):
252
+ * - `grab` — dragging with no committable target (ghost free-following) OR
253
+ * hovering the drag source itself; neutral "carrying" look.
254
+ * - `insert` — a committable edge-insert slot is resolved; a "drop/place here"
255
+ * target affordance in the valid accent color. No direction — the found slot
256
+ * already shows where the pane lands.
257
+ * - `swap` — a committable center/swap target is resolved; a distinct exchange
258
+ * indicator in the valid accent color (swap is a different operation).
259
+ * - `invalid` — the hovered target is rejected (`blockedReason` set); a
260
+ * `not-allowed`-style indicator in the warning color.
261
+ */
262
+ type DragCursorKind = "grab" | "insert" | "swap" | "invalid";
263
+ /** Color tone the cursor adopts, derived from drop validity. */
264
+ type DragCursorTone = "neutral" | "valid" | "invalid";
265
+ /** The semantic presentation of the custom drag cursor: its state plus its tone. */
266
+ interface DragCursorPresentation {
267
+ /** The semantic operation/validity state the cursor conveys (drives the glyph). */
268
+ kind: DragCursorKind;
269
+ /** Validity tone the cursor adopts (drives the color). */
270
+ tone: DragCursorTone;
271
+ }
272
+ /**
273
+ * Derive the custom cursor's semantic presentation from the FSM-resolved target +
274
+ * the drag source. Pure (no DOM, no time) so the operation+validity → kind
275
+ * mapping is unit-testable. Precedence mirrors the commit gate
276
+ * (`isCommittableTarget`) so the cursor's "valid" look is shown for EXACTLY the
277
+ * targets a release would commit:
278
+ * - no target / self-target → `grab` (neutral).
279
+ * - committable `swap` (center) → `swap` (valid).
280
+ * - committable `edge-insert` (a resolved, valid edge) → `insert` (valid).
281
+ * - otherwise, a rejected hovered target (`blockedReason` set) → `invalid`.
282
+ * - any remaining non-committable, non-blocked hover → `grab` (neutral).
283
+ */
284
+ declare function resolveDragCursorPresentation(resolvedTarget: DragResolvedTarget | null, sourceLeafId: string): DragCursorPresentation;
285
+ /** A pointer point in viewport (client) coordinates. */
286
+ interface DragCursorPoint {
287
+ /** Client X (CSS px). */
288
+ x: number;
289
+ /** Client Y (CSS px). */
290
+ y: number;
291
+ }
292
+ /** Viewport edge bounds (client coords) used to clamp the pinned cursor point. */
293
+ interface DragCursorViewportBounds {
294
+ /** Left edge (client X). */
295
+ left: number;
296
+ /** Top edge (client Y). */
297
+ top: number;
298
+ /** Right edge (client X). */
299
+ right: number;
300
+ /** Bottom edge (client Y). */
301
+ bottom: number;
302
+ }
303
+ /**
304
+ * Clamp the pinned pointer point so the custom cursor element stays fully within
305
+ * the viewport at the edges — the analog of the ghost overlay's off-viewport
306
+ * clamp. `marginPx` reserves room for the cursor element's own extent (so the
307
+ * badge at the right/bottom edge is not clipped). When the bounds are narrower
308
+ * than `2 * marginPx` on an axis (degenerate / tiny viewport), the point snaps
309
+ * to that axis's midpoint rather than inverting. Pure so the clamp is testable
310
+ * without a DOM.
311
+ */
312
+ declare function clampCursorPointToViewport(point: DragCursorPoint, bounds: DragCursorViewportBounds, marginPx: number): DragCursorPoint;
313
+
314
+ export { type DragCursorKind, type DragCursorPoint, type DragCursorPresentation, type DragCursorTone, type DragCursorViewportBounds, type DragResolvedTarget, EMPTY_FOCUS_HISTORY, FOCUS_HISTORY_DEFAULT_LIMIT, type FlexibleRatioChild, type FocusHistory, type SplitBoundaryStaticFlags, TILING_DROP_INTENT_CONFIG, TilingDimension, type TilingDropIntentBaseConfig, type TilingDropIntentState, type TilingEdgeZone, TilingKeyboardEventLike, clampCursorPointToViewport, defaultKeyBindings, isStaticAlongSplitAxis, isStaticInDimension, isStaticOnCrossAxis, layoutContainsStaticPane, matchKeyBinding, pruneFocusHistory, pushFocusHistory, renormalizeFlexibleRatios, resolveDragCursorPresentation, resolveFocusCurrentOrLast, resolveSizingMode, shouldRenderSplitDivider };
@@ -0,0 +1,314 @@
1
+ export { G as GroupLeavesOptions, M as MULTI_SELECT_GROUP_MIN_MEMBERS, T as TILING_KEYMAP_DEFAULTS, a as TilingKeymapActionGuards, c as canGroupMultiSelection, b as chordRequiresModifier, d as collectGroups, e as collectSplitNodes, f as commandRequiredCapability, g as findLeafByDirection, h as findLeafById, i as groupLeaves, j as hasAnyModifier, k as insertLeafAdjacent, l as isCssEasing, m as isResizeAxisEnabled, n as isStructurallyValidLayout, o as keyboardActionToCommand, p as matchKeyChord, q as matchKeymapAction, r as moveLeafToRoot, s as moveLeafToSplitContainer, t as pruneMultiSelection, u as readLeafNodeIds, v as removeLeafTile, w as resolveDragEasing, x as resolveKeymap, y as resolveMaximizeToggle, z as resolveMultiSelectGroupCommand, A as resolveMultiSelectGroupHost, B as setLeafSizing, C as siblingSubtreeForLeaf, D as swapLeafTiles, E as tileOrderByLeafId, F as toggleLeafMultiSelection, H as toggleSplitAxis, I as ungroupNode, J as updateSplitRatio } from './drag-easing-5WbK3T82.js';
2
+ import { r as TilingLeafDropZone, s as TilingDropAction, t as TilingSplitAxis, j as TilingDropIntentTuningState, u as ResolvedTilingKeymap, v as TilingKeyBinding, w as TilingKeyboardEventLike, x as TilingCommand, T as TilingLayoutNode, y as TilingDimension, z as TilingPaneSizing, A as TilingPaneSizingMode } from './tiling-renderer-kTlSm4H4.js';
3
+ export { B as BASELINE_DRAG_HOP_DURATION_MS, I as INSTANT_DRAG_DURATION_MS, C as TilingAccentHue, D as TilingKeyboardAction, E as TilingKeyboardModifierState, F as TilingMoveModeState, G as TilingPaneSwitcherState, H as accentHue } from './tiling-renderer-kTlSm4H4.js';
4
+ import 'react';
5
+
6
+ /** A pane's four directional insert edges (the drop zones excluding `"center"`). */
7
+ type TilingEdgeZone = Exclude<TilingLeafDropZone, "center">;
8
+ /** Base drop hit-zone geometry: center swap fraction, floor, and hysteresis. */
9
+ interface TilingDropIntentBaseConfig {
10
+ /**
11
+ * Fraction of each pane axis spanned by the center (swap) rectangle. Acts as
12
+ * the SYMMETRIC value: it sizes both axes unless a per-axis override
13
+ * (`centerRatioX` / `centerRatioY`) is supplied for that axis.
14
+ */
15
+ centerRatio: number;
16
+ /** Per-axis HORIZONTAL (width) swap-zone fraction; falls back to `centerRatio`. */
17
+ centerRatioX?: number;
18
+ /** Per-axis VERTICAL (height) swap-zone fraction; falls back to `centerRatio`. */
19
+ centerRatioY?: number;
20
+ /** Floor for the center rectangle extent so tiny panes keep a usable swap zone. */
21
+ centerMinPx: number;
22
+ /** Boundary stickiness (pane-local px) used to suppress sub-pixel flicker. */
23
+ hysteresisPx: number;
24
+ }
25
+ /**
26
+ * The resolver's full drop-intent result for one pane hover: the resolved zone
27
+ * and action plus the geometry/diagnostic fields that explain how it was
28
+ * reached. Emitted (as {@link TilingDropIntentDebugState}) to `onDropIntentChange`.
29
+ */
30
+ interface TilingDropIntentState {
31
+ /** The hovered leaf this resolution targets. */
32
+ leafId: string;
33
+ /** The resolved drop zone under the cursor. */
34
+ zone: TilingLeafDropZone;
35
+ /** The resolved drop action. */
36
+ action: TilingDropAction;
37
+ /** The nearest edge the cursor points toward (pre-fallback). */
38
+ dominantEdge: TilingEdgeZone;
39
+ /** The edge finally selected after fallbacks, or `null`. */
40
+ finalEdge: TilingEdgeZone | null;
41
+ /** Why the dominant edge was replaced by a fallback, or `null`. */
42
+ fallbackReason: string | null;
43
+ /** Why the action was blocked, or `null` when unblocked. */
44
+ blockedReason: string | null;
45
+ /** Split axes from the root to the target (containment path). */
46
+ axisPath: ReadonlyArray<TilingSplitAxis>;
47
+ /** Edge threshold fraction in effect. */
48
+ edgeThresholdRatio: number;
49
+ /** Center rectangle width (CSS px). */
50
+ centerRectWidthPx: number;
51
+ /** Center rectangle height (CSS px). */
52
+ centerRectHeightPx: number;
53
+ /** Cursor distance to the center rectangle (CSS px). */
54
+ centerDistancePx: number;
55
+ /** Cursor distance to the nearest edge (CSS px). */
56
+ nearestEdgeDistancePx: number;
57
+ /** Pane-local cursor X (CSS px). */
58
+ paneLocalX: number;
59
+ /** Pane-local cursor Y (CSS px). */
60
+ paneLocalY: number;
61
+ /** The ancestor split chosen for a split-container insert, or `null`. */
62
+ targetSplitId: string | null;
63
+ /** Which child slot of that split the insert targets, or `null`. */
64
+ targetSplitPlacement: "first" | "second" | null;
65
+ /** The edge zone selected on the chosen split, or `null`. */
66
+ selectedSplitZone: TilingEdgeZone | null;
67
+ /** Cursor distance to the selected split edge (CSS px), or `null`. */
68
+ selectedSplitDistancePx: number | null;
69
+ /** Reasons candidate splits were rejected during resolution. */
70
+ rejectedSplitReasons: ReadonlyArray<string>;
71
+ /** The tuning knobs that shaped this resolution. */
72
+ tuning: TilingDropIntentTuningState;
73
+ }
74
+ /**
75
+ * The default drop hit-zone geometry: a `0.34` center swap fraction, a `24`px
76
+ * center floor, and `6`px boundary hysteresis. The baseline
77
+ * `dropHitZoneGeometry` capability resolves to these values.
78
+ */
79
+ declare const TILING_DROP_INTENT_CONFIG: TilingDropIntentBaseConfig;
80
+
81
+ /**
82
+ * The resolved hover target the FSM tracks. Structurally a `TilingDropIntentState`
83
+ * (the resolver output the renderer already produces), so the renderer can store
84
+ * the full resolved intent verbatim and the candidate-tree derivation / commit
85
+ * both read the SAME object the preview render reads — no second resolution path.
86
+ */
87
+ type DragResolvedTarget = TilingDropIntentState;
88
+
89
+ /**
90
+ * Public keyboard-binding registry (HT-API-COMMAND-KEYBOARD-SURFACE, half B).
91
+ *
92
+ * The chord→command registration surface — the Hyprland `bind` analog. Distinct
93
+ * from the fixed `TilingKeymap` chord overrides (which only RE-CHORD the built-in
94
+ * action set): a binding maps an ARBITRARY chord to an ARBITRARY `TilingCommand`,
95
+ * so a consumer can wire any shortcut to any tiler action (including programmatic
96
+ * commands like swap / split-ratio).
97
+ *
98
+ * Pure + DOM-less. The renderer's keydown resolves a command in this order:
99
+ * consumer bindings (first match wins, so they augment / override defaults) →
100
+ * default keymap bindings (unless `replaceDefaults`) → the jump-to-pane Alt+1..9
101
+ * family (kept in the keymap path since the digit is dynamic) → capability gate.
102
+ *
103
+ * Cross-ref: `_agent/command-keyboard-api-design.md` §3; `commands.ts`
104
+ * (`keyboardActionToCommand` + `isCommandEnabled`); `pane-switching.ts`
105
+ * (`matchKeyChord` + `matchKeymapAction`).
106
+ */
107
+
108
+ /**
109
+ * The command of the FIRST binding whose chord matches `event` (exact modifier
110
+ * match on the physical `event.code`), or `null` when none match. First-match
111
+ * semantics make binding order significant: an earlier binding wins a chord
112
+ * collision. Capability gating is applied by the caller (`isCommandEnabled`),
113
+ * not here — this is pure chord resolution.
114
+ */
115
+ declare function matchKeyBinding(event: TilingKeyboardEventLike, bindings: ReadonlyArray<TilingKeyBinding>): TilingCommand | null;
116
+ /**
117
+ * The default chord→command bindings derived from a resolved keymap — the
118
+ * built-in action set expressed as the binding registry (so a consumer can
119
+ * introspect / extend it). The jump-to-pane Alt+1..9 family is intentionally
120
+ * EXCLUDED: its digit is dynamic, so it stays in the `matchKeymapAction` path
121
+ * rather than being enumerated as nine static bindings.
122
+ */
123
+ declare function defaultKeyBindings(keymap: ResolvedTilingKeymap): ReadonlyArray<TilingKeyBinding>;
124
+
125
+ /**
126
+ * MRU focus history — the pure model behind the "focus current-or-last" toggle
127
+ * (HT-NAV-MRU-FOCUS-TOGGLE; Hyprland `focuscurrentorlast` analog).
128
+ *
129
+ * hypr-tiling cycles a fixed reading-order ring (`resolveCycledPaneId`) with no
130
+ * memory of WHICH pane was focused before the current one. This module adds a
131
+ * most-recently-used stack so a consumer (or the `focus-current-or-last`
132
+ * command) can jump back to the previously-focused pane and toggle between the
133
+ * two — independent of tree order.
134
+ *
135
+ * Pure + DOM-less (the renderer holds the `FocusHistory` in a ref and pushes on
136
+ * every focus change); unit-tested in the `node` jest environment.
137
+ *
138
+ * Cross-ref: `_agent/command-keyboard-api-design.md` §4;
139
+ * `_agent/comparative-analysis/parity-report.md` §5 #8 (MRU focus-toggle gap);
140
+ * `_agent/programme-debt/D-a11y-navigation.md` (HT-NAV-MRU-FOCUS-TOGGLE).
141
+ */
142
+ /**
143
+ * An ordered most-recently-used focus list. `entries` is oldest-first /
144
+ * MOST-RECENT-LAST; the final entry is the currently-focused pane once it has
145
+ * been pushed. Each leaf id appears at most once (a re-focus moves it to the
146
+ * end rather than duplicating).
147
+ */
148
+ interface FocusHistory {
149
+ /** Leaf ids in MRU order: oldest-first, most-recently-focused last. */
150
+ readonly entries: ReadonlyArray<string>;
151
+ }
152
+ /** Default cap on retained history entries (older entries are dropped from the front). */
153
+ declare const FOCUS_HISTORY_DEFAULT_LIMIT: number;
154
+ /** An empty focus history (no panes focused yet). */
155
+ declare const EMPTY_FOCUS_HISTORY: FocusHistory;
156
+ /**
157
+ * Push `leafId` as the most-recent focus. If it already appears it is MOVED to
158
+ * the end (de-dupe, not duplicate), so the history stays a clean MRU ordering.
159
+ * The list is capped to `limit` by dropping the oldest (front) entries. A
160
+ * `limit <= 0` clamps to `1` (the current focus is always retained).
161
+ */
162
+ declare function pushFocusHistory(history: FocusHistory, leafId: string, limit?: number): FocusHistory;
163
+ /**
164
+ * Resolve the "current-or-last" target: the most-recent entry that is NOT
165
+ * `currentLeafId` (the previously-focused distinct pane). Returns `null` when
166
+ * there is no such pane (empty history, or only the current pane is recorded).
167
+ *
168
+ * `currentLeafId == null` returns the most-recent entry outright (no current
169
+ * focus to skip). Because focusing the returned pane pushes it to most-recent,
170
+ * repeated toggles bounce between the two panes naturally.
171
+ */
172
+ declare function resolveFocusCurrentOrLast(history: FocusHistory, currentLeafId: string | null): string | null;
173
+ /**
174
+ * Drop history entries whose leaf id is no longer present in the live tree
175
+ * (`validLeafIds`), preserving MRU order. Called after a layout change so a
176
+ * removed pane is never returned by `resolveFocusCurrentOrLast`.
177
+ */
178
+ declare function pruneFocusHistory(history: FocusHistory, validLeafIds: ReadonlyArray<string>): FocusHistory;
179
+
180
+ /** Resolve the sizing mode for a single dimension; undefined defaults to flexible. */
181
+ declare function resolveSizingMode(sizing: TilingPaneSizing | undefined, dimension: TilingDimension): TilingPaneSizingMode;
182
+ /** True when the node is content-sized (static) in the given dimension. */
183
+ declare function isStaticInDimension(node: TilingLayoutNode, dimension: TilingDimension): boolean;
184
+ /**
185
+ * True when the node is static ALONG the split's main axis → it is content-sized
186
+ * along that axis, excluded from the split's ratio, and removes the divider on
187
+ * that boundary.
188
+ */
189
+ declare function isStaticAlongSplitAxis(node: TilingLayoutNode, axis: TilingSplitAxis): boolean;
190
+ /**
191
+ * True when the node is static on the split's CROSS axis → it content-sizes on
192
+ * the cross axis (no stretch) but still participates in the split-axis ratio.
193
+ */
194
+ declare function isStaticOnCrossAxis(node: TilingLayoutNode, axis: TilingSplitAxis): boolean;
195
+ /**
196
+ * True when the layout tree contains ANY node declared static in either
197
+ * dimension — a generic whole-tree predicate. NOTE: this is no longer the drag
198
+ * gate. The drag/rearrange gate is now PER-SUBTREE (`drop-validity.ts`
199
+ * `collectStaticGatedLeafIds`) and the footprint geometry is static-aware
200
+ * (`leaf-geometry.ts`), so a pinned static pane no longer freezes the whole tree
201
+ * (HT-SIZING-STATIC-DRAG-GATING). Retained as a reusable predicate.
202
+ */
203
+ declare function layoutContainsStaticPane(node: TilingLayoutNode): boolean;
204
+ /** Inputs deciding whether a split boundary renders a draggable resize divider. */
205
+ interface SplitBoundaryStaticFlags {
206
+ /** Whether divider resize is enabled for this boundary. */
207
+ resizeEnabled: boolean;
208
+ /** Whether the first child is static along the split axis. */
209
+ firstStaticAlongAxis: boolean;
210
+ /** Whether the second child is static along the split axis. */
211
+ secondStaticAlongAxis: boolean;
212
+ }
213
+ /**
214
+ * A resize divider is placed ONLY between two boundaries that are both flexible
215
+ * along the split axis. If resize is disabled, or either adjacent child is
216
+ * static along the split axis, no draggable handle is rendered there.
217
+ */
218
+ declare function shouldRenderSplitDivider({ resizeEnabled, firstStaticAlongAxis, secondStaticAlongAxis, }: SplitBoundaryStaticFlags): boolean;
219
+ /** One child's ratio + static-along-axis flag, input to ratio renormalization. */
220
+ interface FlexibleRatioChild {
221
+ /** The child's declared split-axis ratio. */
222
+ ratio: number;
223
+ /** Whether the child is static along the split axis (weight 0 in distribution). */
224
+ staticAlongAxis: boolean;
225
+ }
226
+ /**
227
+ * Renormalize split-axis ratios over the FLEXIBLE children only. Static children
228
+ * receive weight 0 (content-sized, excluded from distribution); flexible children
229
+ * share `1.0` proportionally to their declared ratios. When the flexible ratios
230
+ * sum to zero (or there are no positive ratios), flexible children split evenly.
231
+ */
232
+ declare function renormalizeFlexibleRatios(children: ReadonlyArray<FlexibleRatioChild>): number[];
233
+
234
+ /**
235
+ * Custom-rendered drag cursor (interaction tier "c"). A single `position: fixed`
236
+ * sibling of the cursor-following ghost (`DragPaneOverlay`) that REPLACES the OS
237
+ * cursor during an active live drag, transform-pinned to the pointer in the same
238
+ * coalesced rAF/render path as the ghost so it never lags the hardware cursor.
239
+ *
240
+ * This module is the PURE, DOM-less core: it maps the drag FSM's resolved target
241
+ * (`DragResolvedTarget` = the resolver's `TilingDropIntentState`) plus the drop
242
+ * validity onto a small SEMANTIC presentation descriptor — the OPERATION the
243
+ * release would perform (grab / insert / swap) or its rejection (invalid) — NOT
244
+ * an edge direction. The found slot itself already shows where the pane lands;
245
+ * the cursor only confirms what kind of drop is under the pointer. The renderer's
246
+ * `DragCursorOverlay` consumes these outputs and owns the actual SVG/Tailwind +
247
+ * reduced-motion transitions.
248
+ */
249
+ /**
250
+ * The semantic cursor states, driven by the drag FSM + resolver operation type +
251
+ * validity (NOT by edge direction):
252
+ * - `grab` — dragging with no committable target (ghost free-following) OR
253
+ * hovering the drag source itself; neutral "carrying" look.
254
+ * - `insert` — a committable edge-insert slot is resolved; a "drop/place here"
255
+ * target affordance in the valid accent color. No direction — the found slot
256
+ * already shows where the pane lands.
257
+ * - `swap` — a committable center/swap target is resolved; a distinct exchange
258
+ * indicator in the valid accent color (swap is a different operation).
259
+ * - `invalid` — the hovered target is rejected (`blockedReason` set); a
260
+ * `not-allowed`-style indicator in the warning color.
261
+ */
262
+ type DragCursorKind = "grab" | "insert" | "swap" | "invalid";
263
+ /** Color tone the cursor adopts, derived from drop validity. */
264
+ type DragCursorTone = "neutral" | "valid" | "invalid";
265
+ /** The semantic presentation of the custom drag cursor: its state plus its tone. */
266
+ interface DragCursorPresentation {
267
+ /** The semantic operation/validity state the cursor conveys (drives the glyph). */
268
+ kind: DragCursorKind;
269
+ /** Validity tone the cursor adopts (drives the color). */
270
+ tone: DragCursorTone;
271
+ }
272
+ /**
273
+ * Derive the custom cursor's semantic presentation from the FSM-resolved target +
274
+ * the drag source. Pure (no DOM, no time) so the operation+validity → kind
275
+ * mapping is unit-testable. Precedence mirrors the commit gate
276
+ * (`isCommittableTarget`) so the cursor's "valid" look is shown for EXACTLY the
277
+ * targets a release would commit:
278
+ * - no target / self-target → `grab` (neutral).
279
+ * - committable `swap` (center) → `swap` (valid).
280
+ * - committable `edge-insert` (a resolved, valid edge) → `insert` (valid).
281
+ * - otherwise, a rejected hovered target (`blockedReason` set) → `invalid`.
282
+ * - any remaining non-committable, non-blocked hover → `grab` (neutral).
283
+ */
284
+ declare function resolveDragCursorPresentation(resolvedTarget: DragResolvedTarget | null, sourceLeafId: string): DragCursorPresentation;
285
+ /** A pointer point in viewport (client) coordinates. */
286
+ interface DragCursorPoint {
287
+ /** Client X (CSS px). */
288
+ x: number;
289
+ /** Client Y (CSS px). */
290
+ y: number;
291
+ }
292
+ /** Viewport edge bounds (client coords) used to clamp the pinned cursor point. */
293
+ interface DragCursorViewportBounds {
294
+ /** Left edge (client X). */
295
+ left: number;
296
+ /** Top edge (client Y). */
297
+ top: number;
298
+ /** Right edge (client X). */
299
+ right: number;
300
+ /** Bottom edge (client Y). */
301
+ bottom: number;
302
+ }
303
+ /**
304
+ * Clamp the pinned pointer point so the custom cursor element stays fully within
305
+ * the viewport at the edges — the analog of the ghost overlay's off-viewport
306
+ * clamp. `marginPx` reserves room for the cursor element's own extent (so the
307
+ * badge at the right/bottom edge is not clipped). When the bounds are narrower
308
+ * than `2 * marginPx` on an axis (degenerate / tiny viewport), the point snaps
309
+ * to that axis's midpoint rather than inverting. Pure so the clamp is testable
310
+ * without a DOM.
311
+ */
312
+ declare function clampCursorPointToViewport(point: DragCursorPoint, bounds: DragCursorViewportBounds, marginPx: number): DragCursorPoint;
313
+
314
+ export { type DragCursorKind, type DragCursorPoint, type DragCursorPresentation, type DragCursorTone, type DragCursorViewportBounds, type DragResolvedTarget, EMPTY_FOCUS_HISTORY, FOCUS_HISTORY_DEFAULT_LIMIT, type FlexibleRatioChild, type FocusHistory, type SplitBoundaryStaticFlags, TILING_DROP_INTENT_CONFIG, TilingDimension, type TilingDropIntentBaseConfig, type TilingDropIntentState, type TilingEdgeZone, TilingKeyboardEventLike, clampCursorPointToViewport, defaultKeyBindings, isStaticAlongSplitAxis, isStaticInDimension, isStaticOnCrossAxis, layoutContainsStaticPane, matchKeyBinding, pruneFocusHistory, pushFocusHistory, renormalizeFlexibleRatios, resolveDragCursorPresentation, resolveFocusCurrentOrLast, resolveSizingMode, shouldRenderSplitDivider };