@pasquelin/panels 0.1.1 → 0.3.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 +181 -0
- package/README.md +13 -0
- package/dist/components/Panels.d.ts +7 -1
- package/dist/core/clamps.d.ts +9 -7
- package/dist/core/components.d.ts +18 -0
- package/dist/core/context.d.ts +13 -3
- package/dist/core/hooks/useArrangement.d.ts +12 -10
- package/dist/core/persistence.d.ts +16 -11
- package/dist/core/store.d.ts +81 -22
- package/dist/core/types.d.ts +28 -4
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +622 -575
- package/llms-full.txt +134 -26
- package/llms.txt +33 -13
- package/package.json +14 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@pasquelin/panels`.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
6
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] — 2026-09-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`fillActions` on `<Panel>`.** Whether a panel's actions take the header's free width or hug
|
|
13
|
+
the close button. The chassis guessed it from "publishes actions and sits in a horizontal
|
|
14
|
+
zone", which is right for a montage bar and wrong for a band holding a list with two buttons —
|
|
15
|
+
and only the project knows which of its panels is which. Left out, the guess stands.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **`reset()` no longer settles against an empty registry.** Called before any panel is declared
|
|
20
|
+
it settled the view EMPTY — and an entry is what stops anything reopening it, so every half
|
|
21
|
+
stayed shut for good.
|
|
22
|
+
- **`<Panel>` fields reach the registry by spread**, not one by one. `fillActions` was the second
|
|
23
|
+
field in two releases to be added to the type and forgotten in the collector.
|
|
24
|
+
|
|
25
|
+
### Tests
|
|
26
|
+
|
|
27
|
+
- The frame's GEOMETRY is covered here at last: a zone drawing nothing takes neither room nor
|
|
28
|
+
handle, a column keeps its divider only between two open halves, an untouched half is flexed
|
|
29
|
+
rather than sized, the band runs under the opposite column and parts its own two halves.
|
|
30
|
+
🛑 These were IA Studio's, and it let them go on migrating — on the assumption that this
|
|
31
|
+
library covered them. It did not.
|
|
32
|
+
- `PanelHeader` keeps what is trailing outside the box that clips: a crowded row loses its
|
|
33
|
+
actions, never its way out.
|
|
34
|
+
|
|
35
|
+
## [0.2.0] — 2026-09-01
|
|
36
|
+
|
|
37
|
+
Two changes to the same idea: **what a half shows is resolved when it is drawn, not written down
|
|
38
|
+
once**. That is what lets a project declare its panels conditionally without losing anything, and
|
|
39
|
+
what makes a second view cost one prop.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Views.** `<Panels view="review">` — a named arrangement. Each view keeps the panels it had
|
|
44
|
+
open to itself; the lengths stay shared, because a column that changed width on the way to
|
|
45
|
+
another view reads as another window. Omitted, everything lands in one view and nothing about
|
|
46
|
+
this is visible. The store gains `view`, `views` and `setView`; the prop is controlled.
|
|
47
|
+
- `DEFAULT_VIEW`, the name a project that never passes one lands in.
|
|
48
|
+
- **`components`**, to draw the chassis' own buttons yourself:
|
|
49
|
+
`<Panels components={{ IconButton: Mine }}>`. The rail's buttons and the close button were the
|
|
50
|
+
one part of the chassis a project could not repaint, so a tooltip — or a shortcut hint, or a
|
|
51
|
+
badge — had nowhere to go but a new prop here. Read once, like `storage`.
|
|
52
|
+
- `shownSpecsIn`, `openOf` and the `Arranged` type, for a project building its own frame on the
|
|
53
|
+
headless core: `shownIn` hands back ids, and every caller was finding the panel again from them.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
|
|
57
|
+
- **`PanelsState.open` and `PanelsState.settled`.** One map, `views`, now holds every
|
|
58
|
+
arrangement including the one in front, and having an entry in it IS the record of having been
|
|
59
|
+
settled. Read the view on screen with `openOf(state)`.
|
|
60
|
+
Keeping the front view apart cost three hand-written merges and a `delete`, and the invariant
|
|
61
|
+
they upheld was tenable by nobody: a cold start straight onto a view the stored file had never
|
|
62
|
+
named took it for settled and drew **an empty chassis** — while reaching the same screen from
|
|
63
|
+
another view worked. One map makes the two paths agree by construction.
|
|
64
|
+
- **`shownIn`, `zoneDraws` and `zoneTakesRoom` take `Arranged`** — `{ registry, view, views }` —
|
|
65
|
+
where they took `{ registry, open }`. Passing the whole state still works.
|
|
66
|
+
- **`undraggedSizeOf(zone, spec)`** takes the leading panel rather than a registry and an id.
|
|
67
|
+
- **`readLayout` and `writeLayout` carry `LayoutState`**, which is now `{ views, lengths }`. That
|
|
68
|
+
type was exported, described persistence, and had stopped being true.
|
|
69
|
+
- **`setView` is no longer on `usePanelsActions`.** Inside React the `view` prop is the one path;
|
|
70
|
+
it remains on the store for a project driving the chassis from outside React.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- **A withdrawn panel no longer empties its half.** It falls back to whatever is still declared
|
|
75
|
+
for that half, and the choice is remembered: declare the panel again and the half is its once
|
|
76
|
+
more. Before, hiding a panel behind a right, a route or a connection lost the arrangement for
|
|
77
|
+
good — while an unknown id read back from storage survived as a half that was neither open nor
|
|
78
|
+
closed. The two cases now answer the same way.
|
|
79
|
+
- **`settle` opens a half without naming a panel.** `ZoneSlots` gains `null` as a third state —
|
|
80
|
+
the key absent is a closed half, `null` an open one that named nobody, an id a real choice.
|
|
81
|
+
Only choices are stored, so an untouched half follows your declarations instead of freezing
|
|
82
|
+
one screen's answer.
|
|
83
|
+
- **`show` on a panel already drawn only focuses it**, rather than writing its name down.
|
|
84
|
+
- **`register` and `unregister` are replaced by `declare(specs)`**, which posts the panels as a
|
|
85
|
+
list. The list IS the order the rail stacks them in, so a panel that goes and comes back
|
|
86
|
+
returns to its place instead of to the end.
|
|
87
|
+
- **Stored layouts are at version 2**, holding one entry per view. A version 1 file is read back
|
|
88
|
+
as the default view — the upgrade costs nobody their layout.
|
|
89
|
+
- **The `view` prop is reconciled on every render**, not on a dependency change. It was
|
|
90
|
+
controlled only when some unrelated prop happened to change identity, so a `defaultOpen`
|
|
91
|
+
written inline and one hoisted into a constant gave opposite contracts.
|
|
92
|
+
- **The layout is written only when the arrangements or the lengths actually moved.** The store
|
|
93
|
+
notifies on every write — a focus, a measure, each `pointermove` of a drag — and each of those
|
|
94
|
+
used to re-serialise the whole file.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- **A view named after anything on `Object.prototype`** — `constructor`, `toString`, `__proto__` —
|
|
99
|
+
was taken for settled and drew an empty chassis, then the first click froze the remaining half
|
|
100
|
+
closed for good. `in` answers for the prototype chain; `Object.hasOwn` does not.
|
|
101
|
+
- **A stored view named `__proto__` was lost, and took the map's prototype with it.** `JSON.parse`
|
|
102
|
+
makes it an own property, and assigning it on a plain object fires the setter — so a view named
|
|
103
|
+
`left` would have inherited an arrangement that was not its own. No global pollution, measured.
|
|
104
|
+
- **`setView` and `reset` now settle on the spot.** Reachable only through a render, a view
|
|
105
|
+
arrived at from outside React — a native menu, a socket — stayed unsettled, and a `reset` asked
|
|
106
|
+
for by a button inside the chassis left the frame blank until some ancestor happened to
|
|
107
|
+
re-render. The reset was not written to disk either, so the arrangement being escaped came back
|
|
108
|
+
on reload.
|
|
109
|
+
- **A project bringing its own `store` was never handed the stored layout**, and overwrote the
|
|
110
|
+
file on the first write — losing its arrangement on every launch for having built the store
|
|
111
|
+
itself.
|
|
112
|
+
- **`view` no longer defaults**, so a project that never passes it keeps `setView` for itself. The
|
|
113
|
+
prop claimed the view on every render, undoing an imperative call at the next unrelated render
|
|
114
|
+
of some ancestor.
|
|
115
|
+
- **Replacing a `solo` panel no longer closes the half beside it.** When the solo panel led by
|
|
116
|
+
fallback rather than by choice, showing anything in that zone rebuilt it from nothing.
|
|
117
|
+
- **A zone's divider is re-clamped even when its own length was never dragged.** `resize` and
|
|
118
|
+
`resplit` write different keys, so parting a column without ever moving its edge left the
|
|
119
|
+
divider past the bottom of a shrunken column, squeezing the first half to nothing.
|
|
120
|
+
- **A `defaultOpen` half written `undefined`** was drawn on the first launch and closed after a
|
|
121
|
+
reload: `JSON` drops the key the three-state semantics rests on.
|
|
122
|
+
- **A band drawing nothing still reserved its height.** Two predicates in `clamps.ts` answered
|
|
123
|
+
"is this zone open?" from the stored arrangement alone, without the registry — so a half left
|
|
124
|
+
open on a panel the project no longer declares counted as drawing. The strip held 240 px under
|
|
125
|
+
an empty band, every drag of the top zone was clamped against those 240, and the focus stayed
|
|
126
|
+
on a zone that had stopped drawing. The question is now asked of `zoneTakesRoom`, which knows
|
|
127
|
+
what is declared.
|
|
128
|
+
|
|
129
|
+
## [0.1.1] — 2026-09-01
|
|
130
|
+
|
|
131
|
+
Everything here is about what leaves the repository. The library itself did not change.
|
|
132
|
+
|
|
133
|
+
### Fixed
|
|
134
|
+
|
|
135
|
+
- **The published types were empty.** `dist/index.d.ts` contained `export { }` — the package
|
|
136
|
+
typechecked in this repository and gave a consumer nothing at all. Declarations are emitted by
|
|
137
|
+
`tsc` now, not by a bundler plugin.
|
|
138
|
+
- **`@pasquelin/panels/dockview` did not exist.** `exports` promised the subpath and the build
|
|
139
|
+
never produced it; importing it failed at install time.
|
|
140
|
+
- **The stylesheet weighed 137 kB.** Dockview's own sheet had been merged into it, so every
|
|
141
|
+
project paid for tabs it may never open. It belongs to Dockview, and the consumer loads it.
|
|
142
|
+
Now 5.2 kB.
|
|
143
|
+
- **The types named `zustand`.** It is bundled, so no consumer has that package installed and
|
|
144
|
+
their typecheck failed with TS2307 on a package that advertises no dependencies. The store's
|
|
145
|
+
surface is declared directly.
|
|
146
|
+
- **The types named a stylesheet path that does not exist**, failing with TS2882 without
|
|
147
|
+
`skipLibCheck`. The sheet has its own entry and `index.ts` no longer pulls it in.
|
|
148
|
+
|
|
149
|
+
### Changed
|
|
150
|
+
|
|
151
|
+
- Document tabs follow IA Studio's design, rule for rule: only the visible tab carries a
|
|
152
|
+
background, tabs take the surfaces' radius and are parted by a margin, the title yields and the
|
|
153
|
+
close button never does, and that button is a disc shown under the pointer.
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
|
|
157
|
+
- `--pnl-surface` (between the panel and its hovered state) and `--pnl-tab`, which the tab strip
|
|
158
|
+
needs and the chassis had no equivalent for.
|
|
159
|
+
|
|
160
|
+
## [0.1.0] — 2026-09-01
|
|
161
|
+
|
|
162
|
+
First release.
|
|
163
|
+
|
|
164
|
+
- Five zones — `left`, `right`, `top`, `bottomLeft`, `bottomRight` — each cut in two halves, with
|
|
165
|
+
icon rails on the edges and a centre that is the project's own.
|
|
166
|
+
- Resize by pointer or keyboard, clamped so the centre keeps its floor whether or not anything
|
|
167
|
+
was ever dragged.
|
|
168
|
+
- The chassis measures its **container**, never the window, so it can live inside a page that
|
|
169
|
+
already exists.
|
|
170
|
+
- Layout persisted to `localStorage` by default, or to any adapter.
|
|
171
|
+
- Headless underneath: every component is built on hooks that render nothing, and each piece is
|
|
172
|
+
exported and replaceable on its own.
|
|
173
|
+
- Every visual value is a CSS custom property, declared at zero specificity so a project's
|
|
174
|
+
palette always wins.
|
|
175
|
+
- Optional `@pasquelin/panels/dockview` entry point for document tabs.
|
|
176
|
+
- No runtime dependencies.
|
|
177
|
+
|
|
178
|
+
[0.3.0]: https://github.com/pasquelin/panels/releases/tag/v0.3.0
|
|
179
|
+
[0.2.0]: https://github.com/pasquelin/panels/releases/tag/v0.2.0
|
|
180
|
+
[0.1.1]: https://github.com/pasquelin/panels/releases/tag/v0.1.1
|
|
181
|
+
[0.1.0]: https://github.com/pasquelin/panels/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -154,6 +154,19 @@ For coding agents: [`llms.txt`](llms.txt) is the mental model, the API and the t
|
|
|
154
154
|
page; [`llms-full.txt`](llms-full.txt) is every English chapter concatenated. Both ship with the
|
|
155
155
|
package, and `pnpm llms` regenerates the second from the first plus `docs/`.
|
|
156
156
|
|
|
157
|
+
## Releasing
|
|
158
|
+
|
|
159
|
+
`main` is production; `develop` is where work lands. A release is a tag:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm version patch # or minor / major — bumps, commits, tags
|
|
163
|
+
git push --follow-tags
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The tag triggers `release.yml`, which checks that it matches `package.json`, replays
|
|
167
|
+
`pnpm validate`, builds, and publishes to npm with a signed provenance attestation — over OIDC,
|
|
168
|
+
with no token anywhere. See [CHANGELOG.md](CHANGELOG.md) for what shipped.
|
|
169
|
+
|
|
157
170
|
## Licence
|
|
158
171
|
|
|
159
172
|
**MIT** — see [LICENSE](LICENSE).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type PanelsProviderProps } from '../core/context';
|
|
3
|
+
import { type PanelsComponents } from '../core/components';
|
|
3
4
|
import { type PanelsLabels } from './labels';
|
|
4
5
|
export type PanelsProps<Id extends string = string> = Omit<PanelsProviderProps<Id>, 'children'> & {
|
|
5
6
|
/** Above the rails, full width. The project's own — the library imposes no chrome. */
|
|
@@ -10,6 +11,11 @@ export type PanelsProps<Id extends string = string> = Omit<PanelsProviderProps<I
|
|
|
10
11
|
railHeader?: ReactNode;
|
|
11
12
|
/** Words the chassis says. English by default; pass your own already translated. */
|
|
12
13
|
labels?: Partial<PanelsLabels>;
|
|
14
|
+
/**
|
|
15
|
+
* Pieces the project draws itself — today the `IconButton` the rail and the panel headers use.
|
|
16
|
+
* Read ONCE, like `storage`: swapping it under a live chassis would re-render every panel.
|
|
17
|
+
*/
|
|
18
|
+
components?: Partial<PanelsComponents>;
|
|
13
19
|
/**
|
|
14
20
|
* Forces the palette. Left out, the chassis follows the reader's system setting — which is
|
|
15
21
|
* what a project wants until it offers a switch of its own.
|
|
@@ -23,7 +29,7 @@ export type PanelsProps<Id extends string = string> = Omit<PanelsProviderProps<I
|
|
|
23
29
|
* The chassis: icon rails stuck to the edges, rounded panels laid over the gutter, a free centre,
|
|
24
30
|
* and the project's own header and footer around them.
|
|
25
31
|
*/
|
|
26
|
-
export declare function Panels<Id extends string = string>({ header, footer, railHeader, labels, theme, className, children, ...provider }: PanelsProps<Id>): import("react").JSX.Element;
|
|
32
|
+
export declare function Panels<Id extends string = string>({ header, footer, railHeader, labels, theme, className, components, children, ...provider }: PanelsProps<Id>): import("react").JSX.Element;
|
|
27
33
|
export declare namespace Panels {
|
|
28
34
|
var Panel: typeof import("./Panel").Panel;
|
|
29
35
|
var Center: typeof import("./Center").Center;
|
package/dist/core/clamps.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Lengths, type
|
|
1
|
+
import { type Lengths, type Zone } from './types';
|
|
2
2
|
/** Smallest a zone may be dragged to before it is worth closing instead. */
|
|
3
3
|
export declare const MIN_SIZE = 140;
|
|
4
4
|
/** Room the centre must keep, whatever the side zones ask for. */
|
|
@@ -24,22 +24,24 @@ export declare const OPPOSITE: Record<Zone, Zone>;
|
|
|
24
24
|
export declare function fitZoneSize(size: number, available: number, opposite: number): number;
|
|
25
25
|
/** Same idea one level down: neither half of a zone may swallow the other. */
|
|
26
26
|
export declare function fitSplit(size: number, available: number): number;
|
|
27
|
-
/** True once either half holds something: an empty zone takes no room at all. */
|
|
28
|
-
export declare function isZoneOpen(open: OpenByZone, zone: Zone): boolean;
|
|
29
|
-
/** The band takes its height as soon as EITHER half holds something: the strip is one strip. */
|
|
30
|
-
export declare function isBandOpen(open: OpenByZone): boolean;
|
|
31
27
|
/**
|
|
32
28
|
* The room a zone currently takes, or zero when it draws nothing. Read while clamping the
|
|
33
29
|
* opposite zone: under-report it and the other side may be dragged over room this one is
|
|
34
30
|
* already drawing in, squeezing the centre past its floor.
|
|
31
|
+
*
|
|
32
|
+
* 🛑 `takesRoom` is asked rather than read off `open`, and that is not a style: whether a half
|
|
33
|
+
* draws depends on the REGISTRY too — one holding a panel the project no longer declares draws
|
|
34
|
+
* whatever is still declared for it, or nothing. This module has no registry, and the two
|
|
35
|
+
* predicates it used to carry answered `true` for a half that drew nothing at all: a band
|
|
36
|
+
* reserving 240 px under an empty strip, and every drag of `top` clamped against those 240.
|
|
35
37
|
*/
|
|
36
|
-
export declare function sizeOf(lengths: Lengths,
|
|
38
|
+
export declare function sizeOf(lengths: Lengths, zone: Zone, takesRoom: (zone: Zone) => boolean, undragged: (zone: Zone) => number): number;
|
|
37
39
|
/**
|
|
38
40
|
* Every stored length, re-clamped to a container of this size. Sizes are persisted, so a layout
|
|
39
41
|
* set on a wide screen would otherwise overflow a narrow one — pushing the panels under the
|
|
40
42
|
* rails and squeezing the centre to nothing.
|
|
41
43
|
*/
|
|
42
|
-
export declare function fitted(lengths: Lengths,
|
|
44
|
+
export declare function fitted(lengths: Lengths, width: number, height: number, takesRoom: (zone: Zone) => boolean, undragged: (zone: Zone) => number): Lengths;
|
|
43
45
|
/**
|
|
44
46
|
* The two side zones, bounded so the centre keeps its floor — whether or not either was ever
|
|
45
47
|
* dragged.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ComponentType } from 'react';
|
|
2
|
+
import { type IconButtonProps } from '../components/IconButton';
|
|
3
|
+
/**
|
|
4
|
+
* The pieces a project may draw itself, where the chassis would otherwise draw them in the
|
|
5
|
+
* middle of a rail it owns.
|
|
6
|
+
*
|
|
7
|
+
* One entry today, and the shape is what lets there be more without breaking anyone. The
|
|
8
|
+
* library keeps its promise by NOT growing props for what a design system owns — a tooltip, a
|
|
9
|
+
* shortcut hint, a badge: those live on the button, and the button is replaceable.
|
|
10
|
+
*/
|
|
11
|
+
export type PanelsComponents = {
|
|
12
|
+
/** The rail's buttons and the panel header's close button. See `IconButtonProps`. */
|
|
13
|
+
IconButton: ComponentType<IconButtonProps>;
|
|
14
|
+
};
|
|
15
|
+
export declare const ComponentsProvider: import("react").Provider<PanelsComponents>;
|
|
16
|
+
/** Defaults outside a chassis, so `<Rail>` and `<PanelFrame>` stand on their own. */
|
|
17
|
+
export declare function usePanelsComponents(): PanelsComponents;
|
|
18
|
+
export declare function withDefaults(components: Partial<PanelsComponents> | undefined): PanelsComponents;
|
package/dist/core/context.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type PanelsState, type PanelsStore } from './store';
|
|
3
3
|
import { type LayoutStorage } from './persistence';
|
|
4
|
-
import type
|
|
4
|
+
import { type OpenByZone } from './types';
|
|
5
5
|
export type PanelsProviderProps<Id extends string> = {
|
|
6
6
|
/**
|
|
7
7
|
* A store built by the project. Pass one to drive the chassis from OUTSIDE React — a socket
|
|
@@ -13,11 +13,21 @@ export type PanelsProviderProps<Id extends string> = {
|
|
|
13
13
|
storageKey?: string;
|
|
14
14
|
/** Where the layout is kept. `localStorage` by default; `null` disables persistence. */
|
|
15
15
|
storage?: LayoutStorage | null;
|
|
16
|
-
/** Which halves start open, overriding "
|
|
16
|
+
/** Which halves start open, overriding "every half something is declared for". */
|
|
17
17
|
defaultOpen?: OpenByZone<Id>;
|
|
18
|
+
/**
|
|
19
|
+
* The view in front. Each view keeps its OWN open panels, so two parts of one application can
|
|
20
|
+
* arrange their columns differently — and find them as they left them on the way back.
|
|
21
|
+
*
|
|
22
|
+
* The lengths are shared on purpose: a column that changed width on the way to another view
|
|
23
|
+
* reads as another window.
|
|
24
|
+
*
|
|
25
|
+
* Left out, everything lands in one view and nothing about this is visible.
|
|
26
|
+
*/
|
|
27
|
+
view?: string;
|
|
18
28
|
children: ReactNode;
|
|
19
29
|
};
|
|
20
|
-
export declare function PanelsProvider<Id extends string = string>({ store, storageKey, storage, defaultOpen, children, }: PanelsProviderProps<Id>): import("react").JSX.Element;
|
|
30
|
+
export declare function PanelsProvider<Id extends string = string>({ store, storageKey, storage, defaultOpen, view, children, }: PanelsProviderProps<Id>): import("react").JSX.Element;
|
|
21
31
|
/** The store this chassis runs on. Throws outside a provider rather than answering nothing. */
|
|
22
32
|
export declare function usePanelsStore<Id extends string = string>(): PanelsStore<Id>;
|
|
23
33
|
/** A slice of the chassis state, subscribed. */
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Arranged } from '../store';
|
|
2
|
+
import { type PanelSpec, type Zone } from '../types';
|
|
2
3
|
/**
|
|
3
|
-
* What every reader of `shownIn` and `zoneDraws` needs: the declared panels, and
|
|
4
|
-
*
|
|
4
|
+
* What every reader of `shownIn` and `zoneDraws` needs: the declared panels, and the view whose
|
|
5
|
+
* arrangement they are about.
|
|
5
6
|
*
|
|
6
|
-
* One hook because four of them had written the same
|
|
7
|
-
*
|
|
8
|
-
* four files.
|
|
7
|
+
* One hook because four of them had written the same selectors and rebuilt the same object by
|
|
8
|
+
* hand — so the shape those functions expect could not change without touching four files.
|
|
9
9
|
*/
|
|
10
|
-
export declare function useArrangement<Id extends string = string>():
|
|
11
|
-
registry: PanelSpec<Id>[];
|
|
12
|
-
open: OpenByZone<Id>;
|
|
13
|
-
};
|
|
10
|
+
export declare function useArrangement<Id extends string = string>(): Arranged<Id>;
|
|
14
11
|
/** What a zone's two halves actually draw — a `solo` panel silences the other. */
|
|
15
12
|
export declare function useShownIn<Id extends string = string>(zone: Zone): {
|
|
16
13
|
primary?: Id;
|
|
17
14
|
secondary?: Id;
|
|
18
15
|
};
|
|
16
|
+
/** The same answer as specs, for a caller that needs more of the panel than its name. */
|
|
17
|
+
export declare function useShownSpecsIn<Id extends string = string>(zone: Zone): {
|
|
18
|
+
primary?: PanelSpec<Id>;
|
|
19
|
+
secondary?: PanelSpec<Id>;
|
|
20
|
+
};
|
|
19
21
|
/** Whether a zone draws at all. An empty one takes neither room nor handle. */
|
|
20
22
|
export declare function useZoneDraws<Id extends string = string>(zone: Zone): boolean;
|
|
21
23
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type LayoutState } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Where a layout is kept. `localStorage` by default, so the library works the moment it is
|
|
4
4
|
* installed; a project that stores elsewhere — a file, an API, `electron-store` — passes its own.
|
|
@@ -13,18 +13,23 @@ export declare const memoryStorage: () => LayoutStorage;
|
|
|
13
13
|
* a layout that cannot be saved is not a reason to take the application down.
|
|
14
14
|
*/
|
|
15
15
|
export declare const browserStorage: () => LayoutStorage;
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Bumped when the stored shape stops being one this build can restore.
|
|
18
|
+
*
|
|
19
|
+
* 2 keeps the views apart. A version 1 file held one arrangement under `open`, which is read
|
|
20
|
+
* back as the view in front — nobody loses a layout to the upgrade.
|
|
21
|
+
*/
|
|
22
|
+
export declare const LAYOUT_VERSION = 2;
|
|
18
23
|
/**
|
|
19
24
|
* Reads a stored layout back, dropping anything this build cannot make sense of. Returns
|
|
20
25
|
* `undefined` rather than a partial answer: a half-read layout is worse than none, since the
|
|
21
26
|
* project's own defaults are a deliberate arrangement and a corrupted one is not.
|
|
27
|
+
*
|
|
28
|
+
* A view the file does not name is simply absent, which is what tells the store to settle it —
|
|
29
|
+
* so arriving straight at a view nobody has arranged yet opens its halves.
|
|
30
|
+
*
|
|
31
|
+
* `view` is the one starting in front, and it only matters for a version 1 file: that one held a
|
|
32
|
+
* single arrangement, and this says which view inherits it.
|
|
22
33
|
*/
|
|
23
|
-
export declare function readLayout<Id extends string>(storage: LayoutStorage, key: string):
|
|
24
|
-
|
|
25
|
-
lengths: Lengths;
|
|
26
|
-
} | undefined;
|
|
27
|
-
export declare function writeLayout<Id extends string>(storage: LayoutStorage, key: string, layout: {
|
|
28
|
-
open: OpenByZone<Id>;
|
|
29
|
-
lengths: Lengths;
|
|
30
|
-
}): void;
|
|
34
|
+
export declare function readLayout<Id extends string>(storage: LayoutStorage, key: string, view?: string): LayoutState<Id> | undefined;
|
|
35
|
+
export declare function writeLayout<Id extends string>(storage: LayoutStorage, key: string, layout: LayoutState<Id>): void;
|
package/dist/core/store.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { type Lengths, type OpenByZone, type PanelSpec, type Slot, type Zone, type ZoneSlots } from './types';
|
|
1
|
+
import { type Lengths, type OpenByZone, type PanelSpec, type LayoutState, type Slot, type Zone, type ZoneSlots } from './types';
|
|
2
2
|
export type PanelsState<Id extends string = string> = {
|
|
3
|
-
/** Panels declared by the project, in the order
|
|
3
|
+
/** Panels declared by the project, in the order it declares them. The rail reads this. */
|
|
4
4
|
registry: PanelSpec<Id>[];
|
|
5
|
-
|
|
5
|
+
/** Name of the view in front. One view is all a project needs until it has two. */
|
|
6
|
+
view: string;
|
|
7
|
+
/**
|
|
8
|
+
* Every view's arrangement — which half of which zone holds what — the one in front included.
|
|
9
|
+
* A view is settled once it has an entry here, which is what `settle` writes and `reset`
|
|
10
|
+
* clears: no second field can then disagree about which views have been opened.
|
|
11
|
+
*/
|
|
12
|
+
views: Record<string, OpenByZone<Id>>;
|
|
6
13
|
lengths: Lengths;
|
|
14
|
+
/**
|
|
15
|
+
* Halves the project wants open whatever the panels say, as last passed to `settle`. Held so
|
|
16
|
+
* that a view arriving later — or a `reset` — is settled the same way the first one was,
|
|
17
|
+
* without waiting for React to hand them over again.
|
|
18
|
+
*/
|
|
19
|
+
defaults?: OpenByZone<Id>;
|
|
7
20
|
/** Last clicked zone: the one whose rail icon gets accented. */
|
|
8
21
|
focusedZone: Zone | null;
|
|
9
22
|
/**
|
|
@@ -11,8 +24,6 @@ export type PanelsState<Id extends string = string> = {
|
|
|
11
24
|
* by itself days later, on an arrangement nobody remembers making, is not a restoration.
|
|
12
25
|
*/
|
|
13
26
|
stashed: Partial<Record<Zone, ZoneSlots<Id>>>;
|
|
14
|
-
/** Whether the opening arrangement has been settled — see `settle`. */
|
|
15
|
-
settled: boolean;
|
|
16
27
|
/**
|
|
17
28
|
* The room the zones and the centre share, as last measured. Held in the store rather than
|
|
18
29
|
* read per component: every zone has to be bounded against the SAME number, and against what
|
|
@@ -22,10 +33,22 @@ export type PanelsState<Id extends string = string> = {
|
|
|
22
33
|
width: number;
|
|
23
34
|
height: number;
|
|
24
35
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The panels the project declares, in the order the rail stacks them. Posted whole rather
|
|
38
|
+
* than one at a time: the list IS the order, so a panel that goes and comes back — behind a
|
|
39
|
+
* right, a route, a connection — returns to its place instead of to the end.
|
|
40
|
+
*/
|
|
41
|
+
declare: (specs: PanelSpec<Id>[]) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Opens each untouched half of the view in front, naming no panel. Runs once per view — and
|
|
44
|
+
* remembers `defaults`, so the views that come later open the same way.
|
|
45
|
+
*/
|
|
28
46
|
settle: (defaults?: OpenByZone<Id>) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Brings another view forward, putting the one in front away as it stands. Its panels come
|
|
49
|
+
* back untouched when it returns; the lengths are shared, so no column changes width.
|
|
50
|
+
*/
|
|
51
|
+
setView: (view: string) => void;
|
|
29
52
|
show: (id: Id) => void;
|
|
30
53
|
close: (zone: Zone, slot: Slot) => void;
|
|
31
54
|
toggle: (id: Id) => void;
|
|
@@ -54,17 +77,50 @@ export type PanelsStore<Id extends string = string> = {
|
|
|
54
77
|
setState: (partial: Partial<PanelsState<Id>> | ((state: PanelsState<Id>) => Partial<PanelsState<Id>>), replace?: false) => void;
|
|
55
78
|
subscribe: (listener: (state: PanelsState<Id>, previous: PanelsState<Id>) => void) => () => void;
|
|
56
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* What the readers below need of the state: the declared panels, and the arrangement of the view
|
|
82
|
+
* in front. Named because five exported functions take it, and `Pick` written out five times was
|
|
83
|
+
* five places to edit the day the shape moved.
|
|
84
|
+
*/
|
|
85
|
+
export type Arranged<Id extends string = string> = {
|
|
86
|
+
registry: PanelSpec<Id>[];
|
|
87
|
+
view: string;
|
|
88
|
+
/**
|
|
89
|
+
* 🛑 Only the entry named by `view` is ever read. Written out rather than `Pick`ed off
|
|
90
|
+
* `PanelsState`, whose own `views` promises EVERY arrangement: a caller handed this one — the
|
|
91
|
+
* hooks build it from the view on screen alone — would have read that promise and believed it.
|
|
92
|
+
*/
|
|
93
|
+
views: Record<string, OpenByZone<Id>>;
|
|
94
|
+
};
|
|
95
|
+
/** The arrangement of the view in front. Empty until that view has been settled. */
|
|
96
|
+
export declare function openOf<Id extends string>(state: Arranged<Id>): OpenByZone<Id>;
|
|
97
|
+
/**
|
|
98
|
+
* Whether that view has been opened before. 🛑 `hasOwn`, never `in`: `'constructor' in {}` is
|
|
99
|
+
* TRUE, so a view named after anything on `Object.prototype` was taken for settled and its
|
|
100
|
+
* chassis drew nothing at all.
|
|
101
|
+
*/
|
|
102
|
+
export declare function isSettled<Id extends string>(state: Arranged<Id>, view: string): boolean;
|
|
57
103
|
/**
|
|
58
104
|
* A panel by its id. Exported because four sites had written this same lookup for want of it,
|
|
59
105
|
* and one of them had already drifted on how it treated `undefined`. It is also the single place
|
|
60
106
|
* to change the day the registry stops being a list and becomes a `Map`.
|
|
107
|
+
*
|
|
108
|
+
* Takes `null` as well, which is a half open on no panel in particular — see `ZoneSlots`.
|
|
61
109
|
*/
|
|
62
|
-
export declare function specOf<Id extends string>(registry: PanelSpec<Id>[], id: Id | undefined): PanelSpec<Id> | undefined;
|
|
110
|
+
export declare function specOf<Id extends string>(registry: PanelSpec<Id>[], id: Id | null | undefined): PanelSpec<Id> | undefined;
|
|
63
111
|
/**
|
|
64
|
-
*
|
|
65
|
-
* Resolved here rather than in each reader, which would contradict it.
|
|
112
|
+
* The SPECS both halves draw, because one can silence the other: a `solo` panel takes the zone
|
|
113
|
+
* WHOLE. Resolved here rather than in each reader, which would contradict it.
|
|
114
|
+
*
|
|
115
|
+
* Rendering it as specs and not as ids is what stops the lookup happening twice: every caller
|
|
116
|
+
* wanted the panel, and each was finding it again from the id this used to hand back.
|
|
66
117
|
*/
|
|
67
|
-
export declare function
|
|
118
|
+
export declare function shownSpecsIn<Id extends string>(state: Arranged<Id>, zone: Zone): {
|
|
119
|
+
primary?: PanelSpec<Id>;
|
|
120
|
+
secondary?: PanelSpec<Id>;
|
|
121
|
+
};
|
|
122
|
+
/** The same answer as ids, for the callers that only name a panel. */
|
|
123
|
+
export declare function shownIn<Id extends string>(state: Arranged<Id>, zone: Zone): {
|
|
68
124
|
primary?: Id;
|
|
69
125
|
secondary?: Id;
|
|
70
126
|
};
|
|
@@ -76,23 +132,26 @@ export declare function shownIn<Id extends string>(state: Pick<PanelsState<Id>,
|
|
|
76
132
|
* half, the top zone was told nothing faced it whenever `bottomRight` happened to be the closed
|
|
77
133
|
* one — and it could then be dragged over the height `bottomLeft` was already drawing in.
|
|
78
134
|
*/
|
|
79
|
-
export declare function zoneTakesRoom<Id extends string>(state:
|
|
135
|
+
export declare function zoneTakesRoom<Id extends string>(state: Arranged<Id>, zone: Zone): boolean;
|
|
80
136
|
/** Whether the zone draws at all — an empty one takes neither room nor handle. */
|
|
81
|
-
export declare function zoneDraws<Id extends string>(state:
|
|
137
|
+
export declare function zoneDraws<Id extends string>(state: Arranged<Id>, zone: Zone): boolean;
|
|
82
138
|
/**
|
|
83
139
|
* The size a zone opens at, given the panel leading it. A panel may ask for more than the zone's
|
|
84
140
|
* own default — a conversation at 260 wraps every sentence onto three lines.
|
|
85
141
|
*
|
|
86
|
-
* Takes the
|
|
87
|
-
*
|
|
142
|
+
* Takes the leading SPEC rather than its id: every caller has already asked `shownSpecsIn`, and
|
|
143
|
+
* finding the panel again here was a second pass over the registry per zone, per drag frame.
|
|
88
144
|
*/
|
|
89
|
-
export declare function undraggedSizeOf<Id extends string>(
|
|
145
|
+
export declare function undraggedSizeOf<Id extends string>(zone: Zone, leading: PanelSpec<Id> | undefined): number;
|
|
90
146
|
export declare const EMPTY_LENGTHS: Lengths;
|
|
91
147
|
export type CreatePanelsStoreOptions<Id extends string> = {
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
/** The view that starts in front. A project with one view never has to name it. */
|
|
149
|
+
view?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Restored layout, if any. A view it carries is taken as settled and `settle` leaves it be — a
|
|
152
|
+
* view it does NOT carry is settled on first sight, so arriving straight at one nobody has
|
|
153
|
+
* arranged yet opens its halves rather than drawing an empty frame.
|
|
154
|
+
*/
|
|
155
|
+
initial?: Partial<LayoutState<Id>>;
|
|
97
156
|
};
|
|
98
157
|
export declare function createPanelsStore<Id extends string = string>(options?: CreatePanelsStoreOptions<Id>): PanelsStore<Id>;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export declare const ZONES: readonly Zone[];
|
|
|
18
18
|
*/
|
|
19
19
|
export type Slot = 'primary' | 'secondary';
|
|
20
20
|
export declare const SLOTS: readonly Slot[];
|
|
21
|
+
/**
|
|
22
|
+
* The view a project that never names one lands in. A view is a named arrangement: two parts of
|
|
23
|
+
* one application each keeping their own open panels, sharing the lengths.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_VIEW = "default";
|
|
21
26
|
/** The band's two halves, in the order they are drawn. */
|
|
22
27
|
export declare const BOTTOM_ZONES: readonly Zone[];
|
|
23
28
|
/** Whether the zone is one of the band's halves, which share a height and a resize handle. */
|
|
@@ -63,9 +68,25 @@ export type PanelSpec<Id extends string = string> = {
|
|
|
63
68
|
opens?: number;
|
|
64
69
|
/** Takes the zone WHOLE: shown, the other half draws nothing. `primary` only. */
|
|
65
70
|
solo?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Lets the actions take the header's free width rather than hug the close button — for a
|
|
73
|
+
* panel whose row is wide and mostly empty, and which carries a whole bar there.
|
|
74
|
+
*
|
|
75
|
+
* Left out, the chassis gives that width to any panel in a horizontal zone that publishes
|
|
76
|
+
* actions. Which is a guess: a band holding a list with two buttons wants them at the end,
|
|
77
|
+
* and only the project knows which of its panels is which.
|
|
78
|
+
*/
|
|
79
|
+
fillActions?: boolean;
|
|
66
80
|
};
|
|
67
|
-
/**
|
|
68
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Which panel each half of each zone currently shows. Three states, not two: the key absent is
|
|
83
|
+
* a CLOSED half, `null` an open one that has named no panel, an id the panel someone chose.
|
|
84
|
+
*
|
|
85
|
+
* That middle state is what lets a project change the panels it declares without the halves
|
|
86
|
+
* following: an unnamed half draws whatever is declared first for it, resolved at render — see
|
|
87
|
+
* `shownIn`. Stored, it also keeps the arrangement honest, since only a real choice is written.
|
|
88
|
+
*/
|
|
89
|
+
export type ZoneSlots<Id extends string = string> = Partial<Record<Slot, Id | null>>;
|
|
69
90
|
export type OpenByZone<Id extends string = string> = Partial<Record<Zone, ZoneSlots<Id>>>;
|
|
70
91
|
export type SizesByZone = Partial<Record<Zone, number>>;
|
|
71
92
|
/**
|
|
@@ -81,8 +102,11 @@ export type Lengths = {
|
|
|
81
102
|
*/
|
|
82
103
|
bandSplit?: number;
|
|
83
104
|
};
|
|
84
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* The layout as it is stored and restored. This is the whole of what persistence carries: one
|
|
107
|
+
* arrangement per view, and the lengths, which every view shares.
|
|
108
|
+
*/
|
|
85
109
|
export type LayoutState<Id extends string = string> = {
|
|
86
|
-
|
|
110
|
+
views: Record<string, OpenByZone<Id>>;
|
|
87
111
|
lengths: Lengths;
|
|
88
112
|
};
|