@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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,145 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@n-uf/hypr-tiling` are documented here.
4
+
5
+ This package uses calendar-aligned versioning (`YY.M.R`), which cannot signal a
6
+ SemVer "major" bump. **Read the per-release notes below for breaking changes** —
7
+ the version number alone does not flag them.
8
+
9
+ ## 26.7.0 — initial public release
10
+
11
+ First published release of `@n-uf/hypr-tiling`, a dynamic tiling layout engine
12
+ for React. This entry is the baseline: earlier working versions were never
13
+ published, so the changelog tracks the package from this release forward.
14
+
15
+ ### Entry points
16
+
17
+ The package exposes three import paths through `package.json#exports`
18
+ (`"sideEffects": false`):
19
+
20
+ - **`@n-uf/hypr-tiling`** (`.`) — the **public API**. A small, hand-authored
21
+ facade of explicit named exports (never `export *`); this is the ONLY
22
+ consumer surface and the one tracked for compatibility.
23
+ - **`@n-uf/hypr-tiling/devtools`** — opt-in observability overlays, on their own
24
+ entry so a renderer-only consumer never bundles them.
25
+ - **`@n-uf/hypr-tiling/engine`** — a `@beta` escape hatch that re-exports the
26
+ engine-grade, framework-free internals (layout-tree reducers, low-level tree
27
+ walkers, keymap and drag-adjacent math) for power users driving the tree
28
+ headlessly. **No stability guarantees** — it may change or disappear in any
29
+ release and is kept off the consumer documentation site.
30
+
31
+ ### Public API surface (`.`)
32
+
33
+ - **`TilingRenderer`** — the layout renderer: controlled layout tree, focus and
34
+ maximize, drag-and-drop with FLIP animation and self-healing recovery,
35
+ multi-select grouping (Alt/Opt+G), pane switching, and per-tile accents. A
36
+ custom `renderTile` receives the clean, debug-free `TilingRenderTileProps`
37
+ contract (tile payload + pane state flags + interaction handlers) — the
38
+ drag/drop observability + debug fields are OFF this surface (they route through
39
+ the internal default pane and `/devtools`).
40
+ - **Custom-pane helper primitives** — optional, unstyled conveniences layered
41
+ over `renderTile` that encode the pane wiring rules so a custom pane can't get
42
+ them wrong: `TilingPaneRoot` (`data-leaf-id` root + focus/hover handlers),
43
+ `TilingDragHandle` (drag pickup + `touch-action: none` + Alt/Opt group toggle),
44
+ `TilingPaneAction` (action button that stops propagation), and `TilingPaneBody`
45
+ (renders children only in `render-content` mode). The raw `renderTile` args
46
+ stay the full escape hatch.
47
+ - **Theming** — `TilingThemeProvider` / `TilingTheme` for token-driven styling.
48
+ - **Layout inspection & mutation** — the layout is a recursive tree of
49
+ `TilingLayoutNode` (`TilingLeafNode` / `TilingSplitNode` / `TilingGroupNode`).
50
+ Read it with `queryTilingLayout` (a `TilingLayoutQuery` view: leaf ids, splits,
51
+ groups, tile order, directional-neighbor lookup). Mutate it declaratively via
52
+ `onLayoutChange` or imperatively by dispatching a typed `TilingCommand` through
53
+ the renderer's `TilingCommandHandle` (gated by `isCommandEnabled`). The raw pure
54
+ reducers (`groupLeaves`, `insertLeafAdjacent`, `updateSplitRatio`, …) are NOT on
55
+ the public entry — they live on `@n-uf/hypr-tiling/engine`.
56
+ - **Interaction & presets** — `TilingInteractionCapabilities`,
57
+ `resolveInteractionCapabilities`, `TILING_DASHBOARD_PRESET`, and the theming
58
+ registry constants. Every interaction is **on by default** and narrowed by
59
+ passing a partial `interaction` prop; the single opt-IN exception is the group
60
+ tab strip's dev/demo "show pane body" checkbox
61
+ (`paneSwitching.showContentToggle`, default `false`), so a consumer that
62
+ renders its own pane content never surfaces an end-user control that blanks it
63
+ and panes paint content at rest with no wiring.
64
+
65
+ ### Consumer theming & chrome contract
66
+
67
+ Every painted pixel is reachable from a consumer-authored `TilingTheme` or
68
+ routed through the consumer `renderTile` — no renderer state paints chrome the
69
+ consumer didn't choose. Defaults stay zero-config (built-in themes,
70
+ `DefaultTilingTile`, the built-in group strip). Concretely:
71
+
72
+ - **`theme` prop on `TilingRendererProps`** — a full consumer-authored
73
+ `TilingTheme`; takes precedence over `themeId`. `TilingTheme.id` is open
74
+ (`TilingThemeId | (string & {})`) so a consumer mints its own theme id;
75
+ `TILING_THEME_REGISTRY` stays keyed by the closed built-in union, and the
76
+ built-in theme switcher (`onThemeChange`, typed `TilingThemeId`) remains
77
+ built-ins-only — consumers running a custom theme simply don't wire it.
78
+ - **`grouping` capability object form** — `boolean | TilingGroupingCapability`
79
+ (`{ enable?, showGroupTabStrip? }`; bare boolean = `{ enable }`,
80
+ `showGroupTabStrip` default `true`). `showGroupTabStrip: false` suppresses
81
+ the built-in per-group tab strip so a consumer paints its own group chrome;
82
+ keyboard group commands stay live. `paneSwitching.showTabStrip` governs the
83
+ TOP-LEVEL tab strip only.
84
+ - **`surface` discriminator on `TilingRenderTileProps`**
85
+ (`TilingRenderSurface`: `"pane" | "drag-ghost" | "drag-cancel"`). The two
86
+ drag surfaces carry the REAL resolved capability display flags (no mid-drag
87
+ silhouette pop) with inert no-op handlers; a custom pane that wants different
88
+ drag chrome branches on `surface`.
89
+ - **`group` context on `TilingRenderTileProps`** —
90
+ `TilingRenderTileGroupContext | null`, populated for a tabbed group's ACTIVE
91
+ member: the `TilingGroupMemberView` member list (leaf/tile ids, resolved
92
+ tile, 1-based `memberNumber`, `isActive`) plus `activateMember` /
93
+ `removeMember` / `ungroup` callbacks that route through the same internal
94
+ command router as the built-in strip and the keyboard layer. `null` for
95
+ loose leaves and drag surfaces.
96
+ - **Cancel fly-back fidelity** — the drag-cancel overlay renders through the
97
+ consumer `renderTile` (`surface: "drag-cancel"`) when one is supplied, so a
98
+ custom skin keeps its own chrome for the whole cancel glide; the built-in
99
+ shell remains the no-`renderTile` fallback.
100
+ - **Hand-authored facade** — 104 public API items. Engine-grade internals are
101
+ physically layered under `engine/` and reached only through the `.` facade (via
102
+ `react/`) or the explicit `./engine` entry. An
103
+ [API Extractor](https://api-extractor.com/) report per entry is checked in
104
+ (`etc/hypr-tiling{,.devtools,.engine}.api.md`); `pnpm api:check` fails CI if the
105
+ `.` surface drifts or an unexported type leaks onto it, and an architectural
106
+ guardrail keeps the `engine/` layer framework-free and blocks deep consumer
107
+ imports.
108
+
109
+ ### Developer / observability tooling — `@n-uf/hypr-tiling/devtools`
110
+
111
+ The observability panel, its seed defaults, and the whole debug/observability
112
+ input surface live on a separate `/devtools` subpath, so a renderer-only consumer
113
+ never pulls them into its bundle:
114
+
115
+ ```ts
116
+ import {
117
+ TilingObservabilityPanel,
118
+ ANIMATION_CONTROL_DEFAULTS,
119
+ TilingRenderer, // the observability-instrumented view of the same renderer
120
+ } from "@n-uf/hypr-tiling/devtools";
121
+ ```
122
+
123
+ The renderer's observability inputs — overlay colors
124
+ (`observabilityColors` / `observabilityColorEnables`), the hit-zone / drop-intent
125
+ debug flags, and the `onDropIntentChange` / `onLiveHitLogChange` /
126
+ `onProjectedOverlayCountChange` telemetry hooks — are collected into
127
+ `TilingRendererObservabilityProps` and kept OFF the consumer `TilingRendererProps`
128
+ contract. `/devtools` exports both those props and the observability-typed view of
129
+ the SAME `TilingRenderer` component that accepts them, plus the debug/observability
130
+ snapshot **types** they reference (`TilingDropIntentDebugState`,
131
+ `TilingLiveHitLogState`, `TilingObservabilityColorConfig`, `TilingPaneHitZone*`,
132
+ …). The consumer `.` surface carries none of them.
133
+
134
+ ### Documentation
135
+
136
+ Guides and a generated API reference are published at
137
+ [hypr-tiling.n-uf.com/docs](https://hypr-tiling.n-uf.com/docs). Every public symbol
138
+ carries TSDoc (coverage enforced in CI), so summaries and examples surface in
139
+ editor hover-docs.
140
+
141
+ ### Tailwind requirement
142
+
143
+ The package ships no CSS — it emits Tailwind utility classes. Add
144
+ `@n-uf/hypr-tiling` to your Tailwind `content` glob or the renderer is unstyled.
145
+ See the README "Tailwind content requirement" section.
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ <h1 align="center">
2
+ <img src="../../assets/hypr-tiling-logo-transparent.png" alt="hypr-tiling" width="48" align="center" />
3
+ @n-uf/hypr-tiling
4
+ </h1>
5
+ <p align="center">Dynamic tiling for React</p>
6
+
7
+ Dynamic tiling for React: a recursive split-tree renderer for drag/drop,
8
+ resizable, keyboard-controlled panes — inspired by
9
+ [Hyprland](https://hypr.land).
10
+
11
+ Reach for it when users need to rearrange dense, multi-panel screens at runtime
12
+ — IDE-like tools, trading and operator consoles, analytics dashboards — while
13
+ your app keeps strict, controlled ownership of the layout state.
14
+
15
+ ## Quick links
16
+
17
+ - Documentation homepage: <https://hypr-tiling.n-uf.com/>
18
+ - Interactive showcase route: <https://hypr-tiling.n-uf.com/showcase>
19
+ - API report index: [`etc/hypr-tiling.api.md`](etc/hypr-tiling.api.md)
20
+ - Repository issues: <https://github.com/n-uf/hypr-tiling/issues>
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pnpm add @n-uf/hypr-tiling react react-dom
26
+ ```
27
+
28
+ ```bash
29
+ npm install @n-uf/hypr-tiling react react-dom
30
+ ```
31
+
32
+ ```bash
33
+ yarn add @n-uf/hypr-tiling react react-dom
34
+ ```
35
+
36
+ `react` and `react-dom` are peer dependencies (version `^19`).
37
+
38
+ ## Compatibility
39
+
40
+ - React: `^19`
41
+ - React DOM: `^19`
42
+ - Tailwind scanning: required (`content` glob or v4 `@source`)
43
+ - Runtime: browser DOM (React renderer package)
44
+
45
+ ### Tailwind content requirement
46
+
47
+ This package **ships no CSS**. The renderer styles itself by emitting Tailwind
48
+ utility class strings (via `clsx` + `tailwind-merge`) — those classes only
49
+ resolve to real styles if Tailwind scans this package's built output and
50
+ generates the matching CSS. If you do not register the package in your Tailwind
51
+ `content` glob, the component renders **completely unstyled**.
52
+
53
+ Add `@n-uf/hypr-tiling` to your Tailwind `content` configuration:
54
+
55
+ ```js
56
+ // tailwind.config.js
57
+ export default {
58
+ content: [
59
+ "./src/**/*.{js,ts,jsx,tsx}",
60
+ "./node_modules/@n-uf/hypr-tiling/dist/**/*.{js,mjs}",
61
+ ],
62
+ // ...
63
+ };
64
+ ```
65
+
66
+ For Tailwind v4 (CSS-first config), declare the same path as a source in your
67
+ stylesheet:
68
+
69
+ ```css
70
+ @import "tailwindcss";
71
+ @source "../node_modules/@n-uf/hypr-tiling/dist/**/*.{js,mjs}";
72
+ ```
73
+
74
+ ## Quick start
75
+
76
+ The renderer is a **controlled component**: you own the layout tree in state and
77
+ apply every change it reports through `onLayoutChange`. Nothing about the layout
78
+ is hidden inside the component — the tree is yours to persist, diff, and restore.
79
+
80
+ ```tsx
81
+ import {
82
+ TilingRenderer,
83
+ DEFAULT_TILING_LAYOUT_CONFIG,
84
+ type TilingLayoutNode,
85
+ type TilingTile,
86
+ } from "@n-uf/hypr-tiling";
87
+ import { useState } from "react";
88
+
89
+ const tiles: TilingTile[] = [
90
+ { id: "a", title: "editor", content: <Editor /> },
91
+ { id: "b", title: "preview", content: <Preview /> },
92
+ ];
93
+
94
+ const initialLayout: TilingLayoutNode = {
95
+ kind: "split",
96
+ id: "root",
97
+ axis: "vertical",
98
+ ratio: 0.5,
99
+ first: { kind: "leaf", id: "l", tileId: "a" },
100
+ second: { kind: "leaf", id: "r", tileId: "b" },
101
+ };
102
+
103
+ export function Workspace(): JSX.Element {
104
+ const [layout, setLayout] = useState<TilingLayoutNode>(initialLayout);
105
+ return (
106
+ <TilingRenderer
107
+ layout={layout}
108
+ tiles={tiles}
109
+ config={DEFAULT_TILING_LAYOUT_CONFIG}
110
+ onLayoutChange={setLayout}
111
+ />
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## Programmatic layout API
117
+
118
+ Because you own the layout tree, you can mutate it from your own code — not just
119
+ through drag/keyboard interaction. The package exports a set of pure layout
120
+ reducers (each takes a layout node and returns a new one, never mutating in
121
+ place) as supported public API. Use these to script layout changes, build custom
122
+ commands, or restore persisted arrangements:
123
+
124
+ | Reducer | Purpose |
125
+ | --- | --- |
126
+ | `findLeafById` | Locate a leaf node within the tree by its id. |
127
+ | `insertLeafAdjacent` | Insert a new leaf next to an existing one along an axis. |
128
+ | `moveLeafToRoot` | Detach a leaf and re-seat it against the layout root. |
129
+ | `moveLeafToSplitContainer` | Move a leaf into a target split container. |
130
+ | `swapLeafTiles` | Exchange the tiles occupying two leaves. |
131
+ | `removeLeafTile` | Remove a leaf and collapse its parent split. |
132
+ | `updateSplitRatio` | Set the ratio of a binary split. |
133
+ | `toggleSplitAxis` | Flip a split between horizontal and vertical. |
134
+ | `setLeafSizing` | Set a leaf's sizing mode (static pixel extent vs. flexible). |
135
+ | `groupLeaves` | Collapse several leaves into one stacked/tabbed group. |
136
+ | `ungroupNode` | Expand a group back into individual leaves. |
137
+ | `collectGroups` | Enumerate the group nodes in a layout. |
138
+ | `isStructurallyValidLayout` | Validate a layout tree's structural invariants. |
139
+
140
+ All reducers are re-exported from the package root:
141
+
142
+ ```ts
143
+ import {
144
+ findLeafById,
145
+ insertLeafAdjacent,
146
+ swapLeafTiles,
147
+ isStructurallyValidLayout,
148
+ } from "@n-uf/hypr-tiling";
149
+ ```
150
+
151
+ Other layout-tree helpers are exported for advanced/internal use, but the
152
+ reducers above are the stable, documented surface for application code.
153
+
154
+ For complete generated API signatures, see
155
+ [`etc/hypr-tiling.api.md`](etc/hypr-tiling.api.md).
156
+
157
+ ## Features
158
+
159
+ - **Drag/drop rearrange** — Hyprland-style live drag; the move commits on
160
+ release, resolving to swap, edge-insert, split-container-insert, or
161
+ group-merge.
162
+ - **Resizable split dividers** — drag dividers, or pin a pane to a measured pixel
163
+ extent (static) versus ratio-distributed (flexible).
164
+ - **Group / stack tabs** — collapse several leaves into one slot as a stacked
165
+ group with a tab strip; only the active member renders.
166
+ - **Maximize** — promote any pane to fill the viewport and restore it back.
167
+ - **Keyboard-driven focus** — directional focus, a pane switcher
168
+ (cycle / jump / overlay), keyboard move-mode, and master/group commands behind
169
+ a remappable keymap.
170
+ - **Theming engine** — two built-in themes (`neon-terminal`, `clean-flat`),
171
+ eight accent hues, and live theme switching with no remount.
172
+ - **Self-healing drag recovery** — a frame-deadline backstop, an idle watchdog,
173
+ transient-style teardown, and a `visibilitychange` reconcile so a drag never
174
+ strands the tree mid-transition.
175
+
176
+ ## Use cases
177
+
178
+ hypr-tiling is built for screens where users live across multiple panels and
179
+ rearrange them as the work demands:
180
+
181
+ - **Dynamic / content sites** — real, SEO-indexable content arranged as tiles
182
+ instead of a single scroll, with docs living in prerendered panes.
183
+ - **Dashboards** — analytics, metrics, and monitoring consoles where several
184
+ resizable panes share one screen.
185
+ - **IDE-like tools** — editor, preview, and terminal workspaces a user splits,
186
+ stacks, and rearranges at runtime.
187
+ - **Trading & operator consoles** — dense, keyboard-driven control surfaces that
188
+ pack many live panels into a fixed viewport.
189
+ - **Admin & data apps** — table, detail, and activity panes side by side, resized
190
+ to fit the task at hand.
191
+ - **Observability & log explorers** — query, results, and trace panes rearranged
192
+ on the fly while chasing an incident.
193
+ - **Web terminals & consoles** — browser-based shells, multiplexed sessions, and
194
+ live log streams split and resized Hyprland-style — the tiling homage made
195
+ literal, in the terminal.
196
+ - **Realtime trading terminals** — Bloomberg-style desks: live charts, order
197
+ books, watchlists, and order entry packed into dense panes that stream and
198
+ rearrange in realtime.
199
+
200
+ ## Roadmap
201
+
202
+ Where hypr-tiling is headed. These are **planned** directions, not shipped
203
+ features today — the library currently renders to the DOM and ships a React
204
+ adapter only. The items below describe where the project is going:
205
+
206
+ - **Framework-agnostic core** — a dependency-free vanilla TypeScript core so the
207
+ tiling engine runs without any framework: the layout tree, the drag/FLIP state
208
+ machine, and the self-healing recovery logic decoupled from React, ready to
209
+ drive any view layer.
210
+ - **First-class adapters for every major framework** — React ships today;
211
+ planned official adapters for Vue, Svelte, Solid, Angular, and standard Web
212
+ Components, each a thin binding over the same vanilla core so behavior stays
213
+ identical across frameworks.
214
+ - **Canvas rendering backend** — an optional canvas / GPU-accelerated render path
215
+ for very high pane counts and animation-heavy scenes where DOM reflow is the
216
+ bottleneck; the semantic DOM path stays the default and canvas is opt-in for
217
+ density.
218
+ - **Rust + WebAssembly core** — porting the hot layout, drag, and geometry math
219
+ to a Rust → WebAssembly core for deterministic, high-frame-rate behavior,
220
+ unlocking more window-manager-like UX: virtual workspaces, snap zones,
221
+ persistent session layouts, fully keyboard-driven tiling, and
222
+ per-monitor-style multi-viewport arrangements.
223
+
224
+ ## Contributing
225
+
226
+ hypr-tiling is built in the open and welcomes collaboration — framework
227
+ adapters, rendering backends, bug reports, and ideas from the roadmap above are
228
+ all welcome. To get involved, email
229
+ [metelin@gmail.com](mailto:metelin@gmail.com).
230
+
231
+ ## Links
232
+
233
+ - Homepage: <https://hypr-tiling.n-uf.com>
234
+ - Showcase: <https://hypr-tiling.n-uf.com/showcase>
235
+ - Repository: <https://github.com/n-uf/hypr-tiling>
236
+ - Issues: <https://github.com/n-uf/hypr-tiling/issues>
237
+
238
+ ## License
239
+
240
+ Source-available under
241
+ [PolyForm Perimeter 1.0.1](https://polyformproject.org/licenses/perimeter/1.0.1)
242
+ — business use allowed, but no competing product built from this software.