@dimina-kit/electron-deck 0.1.0-dev.20260616024534 → 0.1.0-dev.20260616102751
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/README.md +2 -0
- package/dist/dock-react/dock-view.d.ts +51 -0
- package/dist/dock-react/dock-view.d.ts.map +1 -0
- package/dist/dock-react/drag-redock.d.ts +95 -0
- package/dist/dock-react/drag-redock.d.ts.map +1 -0
- package/dist/dock-react/index.d.ts +12 -0
- package/dist/dock-react/index.d.ts.map +1 -0
- package/dist/dock-react/index.js +671 -0
- package/dist/dock-react/index.js.map +1 -0
- package/dist/layout/index.d.ts +13 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +2 -0
- package/dist/layout/model.d.ts +16 -0
- package/dist/layout/model.d.ts.map +1 -0
- package/dist/layout/mutations.d.ts +31 -0
- package/dist/layout/mutations.d.ts.map +1 -0
- package/dist/layout/registry.d.ts +6 -0
- package/dist/layout/registry.d.ts.map +1 -0
- package/dist/layout/serialize.d.ts +10 -0
- package/dist/layout/serialize.d.ts.map +1 -0
- package/dist/layout/types.d.ts +83 -0
- package/dist/layout/types.d.ts.map +1 -0
- package/dist/layout-CZtF0auJ.js +522 -0
- package/dist/layout-CZtF0auJ.js.map +1 -0
- package/dist/types.d.ts +52 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +26 -5
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Dimina devtools 的 host 集成地基。提供:连接层(per-webContents 的
|
|
|
14
14
|
| `defineEvent` / `DeckConfig` 等类型 | `@dimina-kit/electron-deck` |
|
|
15
15
|
| preload bridge `exposeDeckBridge()` | `@dimina-kit/electron-deck/preload` |
|
|
16
16
|
| renderer client `createDeckClient<HS, EV>()` | `@dimina-kit/electron-deck/client` |
|
|
17
|
+
| layout-as-data 引擎(`SplitNode`/`TabGroupNode` 树、`movePanel`/`splitPanel`/`closePanel`/`setActive`/`setSizes` 等 mutation、`serializeLayout`/`parseLayout`/`validateTree`、`createLayoutModel` 单写者可观察模型、panel registry) | `@dimina-kit/electron-deck/layout` |
|
|
18
|
+
| `<DockView>` React 渲染器(把 layout 树渲染成可拖拽 re-dock / tab / 分屏的 docking UI) | `@dimina-kit/electron-deck/dock-react` |
|
|
17
19
|
|
|
18
20
|
## 最小例子
|
|
19
21
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<DockView>` — React renderer for a layout-as-data tree.
|
|
3
|
+
*
|
|
4
|
+
* Pure function of the current model snapshot (+ registry + the two host
|
|
5
|
+
* callbacks). It owns NO layout state: it subscribes to the `LayoutModel`,
|
|
6
|
+
* keeps the latest tree in component state, and re-renders on every emission.
|
|
7
|
+
* All structural targeting is via STABLE `data-*` attributes — see the
|
|
8
|
+
* contract doc-block in `dock-view.test.tsx`.
|
|
9
|
+
*
|
|
10
|
+
* This file lives under `src/dock-react/` (NOT `src/layout/`), so importing
|
|
11
|
+
* react here does not violate the pure-TS layout boundary.
|
|
12
|
+
*/
|
|
13
|
+
import { type ReactNode } from 'react';
|
|
14
|
+
import type { LayoutModel, PanelRegistry } from '../layout/index.js';
|
|
15
|
+
export interface DockViewProps {
|
|
16
|
+
model: LayoutModel;
|
|
17
|
+
registry: PanelRegistry;
|
|
18
|
+
/**
|
|
19
|
+
* Render a DOM panel's body. `opts.active` is `true` iff `panelId` is its tab
|
|
20
|
+
* group's currently-active panel. Under DOM-panel keepalive every DOM panel in
|
|
21
|
+
* a group is rendered (the inactive ones hidden), so this callback is invoked
|
|
22
|
+
* for ALL of them and `opts.active` is RE-EVALUATED on every activation change
|
|
23
|
+
* WITHOUT remounting the kept-alive subtree — letting a host run on-activation
|
|
24
|
+
* side effects (e.g. data refresh) off the false→true `active` edge.
|
|
25
|
+
*/
|
|
26
|
+
renderDomPanel: (panelId: string, opts: {
|
|
27
|
+
active: boolean;
|
|
28
|
+
}) => ReactNode;
|
|
29
|
+
bindNativeSlot: (panelId: string, el: HTMLElement | null) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compute the `defaultSize` percentage for each FLEXIBLE (unconstrained) child,
|
|
33
|
+
* keyed by its ORIGINAL child index. Fixed (px-pinned) children are excluded
|
|
34
|
+
* from the pool entirely so their weight never pollutes the flexible siblings'
|
|
35
|
+
* normalization (FIX E1). `constraints[i]` non-null ⇒ child i is fixed and is
|
|
36
|
+
* absent from the returned map.
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeFlexiblePercentages(sizes: readonly number[], constraints: readonly ({
|
|
39
|
+
fixedPx: number;
|
|
40
|
+
} | null)[] | undefined): Map<number, number>;
|
|
41
|
+
export declare function DockView(props: DockViewProps): ReactNode;
|
|
42
|
+
/** Are two panelId→percentage maps equivalent within `epsilon` percentage
|
|
43
|
+
* points? Both maps must cover EXACTLY the `ids` key set — a missing key OR an
|
|
44
|
+
* extra key (a key present in `a`/`b` but absent from `ids`) counts as NOT
|
|
45
|
+
* equivalent so the sync is not falsely suppressed. A non-finite value
|
|
46
|
+
* (NaN/±Infinity) is likewise NOT equivalent — `typeof NaN === 'number'` and
|
|
47
|
+
* `Math.abs(NaN - x) > eps` is `false`, so without the `Number.isFinite` guard a
|
|
48
|
+
* NaN would slip through as "equivalent" and wrongly suppress a legitimate
|
|
49
|
+
* sync/write-back (N1). EXPORTED (pure) for direct unit coverage. */
|
|
50
|
+
export declare function layoutsEquivalent(a: Record<string, number>, b: Record<string, number>, ids: readonly string[], epsilon?: number): boolean;
|
|
51
|
+
//# sourceMappingURL=dock-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dock-view.d.ts","sourceRoot":"","sources":["../../src/dock-react/dock-view.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAON,KAAK,SAAS,EACd,MAAM,OAAO,CAAA;AAId,OAAO,KAAK,EACX,WAAW,EAGX,aAAa,EAIb,MAAM,oBAAoB,CAAA;AAgB3B,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,WAAW,CAAA;IAClB,QAAQ,EAAE,aAAa,CAAA;IACvB;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,SAAS,CAAA;IACzE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CACjE;AAaD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,WAAW,EAAE,SAAS,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,GAC9D,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAUrB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CA6GxD;AA4GD;;;;;;;qEAOqE;AACrE,wBAAgB,iBAAiB,CAChC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzB,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,OAAO,GAAE,MAAuB,GAC9B,OAAO,CAaT"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `drag-redock` — PURE geometry + descriptor layer for tab drag-to-redock.
|
|
3
|
+
*
|
|
4
|
+
* This module is intentionally REACT-FREE and ELECTRON-FREE so it can run under
|
|
5
|
+
* the node `vitest.config.ts` suite (its spec is `drag-redock.test.ts`). It owns
|
|
6
|
+
* two responsibilities, both pure functions of their inputs:
|
|
7
|
+
*
|
|
8
|
+
* 1. GEOMETRY — `computeDropZone(rect, point)` maps a pointer position over a
|
|
9
|
+
* group's rectangle to one of five drop zones (the four edge bands plus the
|
|
10
|
+
* interior `center`). This is the only place edge-band math lives; the React
|
|
11
|
+
* layer feeds it real `getBoundingClientRect` numbers during dragover.
|
|
12
|
+
*
|
|
13
|
+
* 2. DESCRIPTOR — `dropZoneToMutation(zone, dragged, target)` translates a zone
|
|
14
|
+
* into an engine-NEUTRAL `RedockMutation` intent (`move` | `split`). The
|
|
15
|
+
* descriptor only NAMES the intent; the React layer realizes it against the
|
|
16
|
+
* real tree (a split of an EXISTING panel needs extract-then-split, because
|
|
17
|
+
* `splitPanel` throws if the new panel already exists — see the caller).
|
|
18
|
+
*
|
|
19
|
+
* Keeping this split (geometry/descriptor here, engine application in React)
|
|
20
|
+
* means the geometry is exhaustively unit-testable without a DOM, and the React
|
|
21
|
+
* layer stays a thin gesture binding.
|
|
22
|
+
*/
|
|
23
|
+
/** The five drop zones: four edge bands + the tab-joining interior. */
|
|
24
|
+
export type DropZone = 'left' | 'right' | 'top' | 'bottom' | 'center';
|
|
25
|
+
/**
|
|
26
|
+
* Engine-neutral re-dock INTENT. `move` joins the dragged panel into a tab
|
|
27
|
+
* group; `split` puts it adjacent to a target panel in a new split. This is a
|
|
28
|
+
* pure descriptor — it does NOT itself touch a tree (the caller applies it,
|
|
29
|
+
* extract-then-split'ing for an existing panel).
|
|
30
|
+
*/
|
|
31
|
+
export type RedockMutation = {
|
|
32
|
+
kind: 'move';
|
|
33
|
+
panelId: string;
|
|
34
|
+
destGroupId: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'split';
|
|
37
|
+
atPanelId: string;
|
|
38
|
+
dir: 'row' | 'column';
|
|
39
|
+
side: 'before' | 'after';
|
|
40
|
+
newPanelId: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Classify a pointer position (RELATIVE to the rect's top-left; `(0,0)` is the
|
|
44
|
+
* corner) into a drop zone.
|
|
45
|
+
*
|
|
46
|
+
* Band thickness = `edgeFraction * min(width, height)` — a single symmetric band
|
|
47
|
+
* width derived from the SHORTER side so a wide/short panel doesn't get an
|
|
48
|
+
* absurdly fat horizontal band. A point in NO band is `center` (the tab-join
|
|
49
|
+
* interior). A point in TWO bands (a corner) tie-breaks by NORMALIZED distance
|
|
50
|
+
* to each active edge; on an exact tie HORIZONTAL wins. A point OUTSIDE the rect
|
|
51
|
+
* clamps to the nearest edge zone (per-axis; diagonal corners pick the larger
|
|
52
|
+
* overshoot, horizontal breaking ties).
|
|
53
|
+
*/
|
|
54
|
+
export declare function computeDropZone(rect: {
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
}, point: {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
}, edgeFraction?: number): DropZone;
|
|
61
|
+
/**
|
|
62
|
+
* Translate a drop zone into an engine-neutral re-dock descriptor.
|
|
63
|
+
*
|
|
64
|
+
* center => move the dragged panel into the target's tab group.
|
|
65
|
+
* left/right => split the target panel horizontally (row), dragged before/after.
|
|
66
|
+
* top/bottom => split the target panel vertically (column), dragged before/after.
|
|
67
|
+
*/
|
|
68
|
+
export declare function dropZoneToMutation(zone: DropZone, dragged: string, target: {
|
|
69
|
+
groupId: string;
|
|
70
|
+
panelId: string;
|
|
71
|
+
}): RedockMutation;
|
|
72
|
+
/**
|
|
73
|
+
* A re-dock is a NO-OP when it would not change the tree:
|
|
74
|
+
*
|
|
75
|
+
* 1. The drop targets the dragged panel ITSELF (`dragged === target.panelId`),
|
|
76
|
+
* in ANY zone — you can neither tab a panel onto its own tab nor split a
|
|
77
|
+
* panel around itself. Crucially this also guards the SPLIT case: without
|
|
78
|
+
* it, `extractPanel(dragged)` then `splitPanel(atPanelId = dragged)` removes
|
|
79
|
+
* the very anchor it then splits at and THROWS (the self-collapse / single-
|
|
80
|
+
* panel-self-split bug).
|
|
81
|
+
* 2. A `center` drop into a group the dragged panel ALREADY lives in
|
|
82
|
+
* (`draggedGroupId === target.groupId`) — `movePanel` would re-append it and
|
|
83
|
+
* bump the revision for no visible change (churn).
|
|
84
|
+
*
|
|
85
|
+
* A SPLIT onto a DIFFERENT panel of the dragged panel's own group is still a real
|
|
86
|
+
* re-dock (it splits the group around that sibling), so it is NOT a no-op.
|
|
87
|
+
*
|
|
88
|
+
* `draggedGroupId` is the id of the tab group currently holding `dragged`
|
|
89
|
+
* (`undefined` if it is not in the tree).
|
|
90
|
+
*/
|
|
91
|
+
export declare function isNoopRedock(dragged: string, draggedGroupId: string | undefined, target: {
|
|
92
|
+
groupId: string;
|
|
93
|
+
panelId: string;
|
|
94
|
+
}, zone: DropZone): boolean;
|
|
95
|
+
//# sourceMappingURL=drag-redock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-redock.d.ts","sourceRoot":"","sources":["../../src/dock-react/drag-redock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErE;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACtD;IACD,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,KAAK,GAAG,QAAQ,CAAA;IACrB,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CAClB,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACvC,KAAK,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/B,YAAY,SAAO,GACjB,QAAQ,CAmEV;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC1C,cAAc,CAOhB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC5C,IAAI,EAAE,QAAQ,GACZ,OAAO,CAMT"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dock-react public entry.
|
|
3
|
+
*
|
|
4
|
+
* `<DockView>` renders a docking layout from a `LayoutModel` (observable) +
|
|
5
|
+
* `PanelRegistry` (panelId -> descriptor). See `dock-view.test.tsx` for the
|
|
6
|
+
* `data-*` contract.
|
|
7
|
+
*/
|
|
8
|
+
export { DockView, computeFlexiblePercentages, layoutsEquivalent } from './dock-view.js';
|
|
9
|
+
export type { DockViewProps } from './dock-view.js';
|
|
10
|
+
export { computeDropZone, dropZoneToMutation, isNoopRedock } from './drag-redock.js';
|
|
11
|
+
export type { DropZone, RedockMutation } from './drag-redock.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dock-react/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,QAAQ,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACxF,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAInD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpF,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
|