@deepseek-ai/dsh-client-ui-dockkit 0.1.5-alpha.1 → 0.1.5-alpha.2

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.
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { DockIntents, DockLabels, TabMenuExtras, TabRenderer } from '../contract/adapter.ts';
3
- import type { LayoutState, PaneId } from '../contract/types.ts';
3
+ import type { LayoutState, PaneId, TabId } from '../contract/types.ts';
4
4
  import type { HalvesFit } from '../engine/geometry.ts';
5
5
  /** What the docked surface needs: the layout, its limits, and the outward contracts. */
6
6
  export interface DockSurfaceProps {
@@ -11,8 +11,8 @@ export interface DockSurfaceProps {
11
11
  * split control disabled with `labels.splitPaneNarrow` (see README).
12
12
  */
13
13
  readonly canSplit: boolean;
14
- /** Hide the split control when the pane budget is spent; defaults to false. Width-blocked controls remain disabled. */
15
- readonly hideSplitAtCapacity?: boolean;
14
+ /** Hide a blocked split control — pane budget spent or pane too narrow — instead of rendering it disabled; defaults to false. */
15
+ readonly hideSplitWhenBlocked?: boolean;
16
16
  /** Body drop geometry: all edge bands, or left/right halves with whole-pane moves once splitting is unavailable. */
17
17
  readonly dropZones?: 'edges' | 'horizontal';
18
18
  /** Smallest share a divider may leave a pane; defaults to the kit's fraction. */
@@ -23,6 +23,14 @@ export interface DockSurfaceProps {
23
23
  * end controls where they are and the chips as the only shrinking part.
24
24
  */
25
25
  readonly canAddTab?: (paneId: PaneId) => boolean;
26
+ /**
27
+ * Whether a tab draws its close control and its menu's close item. Called
28
+ * per rendered chip on every render; omit to keep every tab closable.
29
+ * `false` removes both routes without moving the chip: the close control
30
+ * paints over the title's end rather than beside it, so the chip is the same
31
+ * width either way. The menu still opens and carries the embedder's items.
32
+ */
33
+ readonly canCloseTab?: (tabId: TabId) => boolean;
26
34
  readonly intents: DockIntents;
27
35
  readonly labels: DockLabels;
28
36
  readonly renderTab: TabRenderer;
@@ -48,5 +56,5 @@ export interface DockSurfaceProps {
48
56
  readonly onRoom?: (fits: ReadonlyMap<PaneId, HalvesFit>) => void;
49
57
  }
50
58
  /** The split tree and the gestures over it. */
51
- export declare function DockSurface({ state, canSplit, canAddTab, intents, labels, renderTab, renderTabTitle, renderTabMenuItems, chrome, onRoom, dropZones, minPaneFraction, hideSplitAtCapacity, }: DockSurfaceProps): ReactNode;
59
+ export declare function DockSurface({ state, canSplit, canAddTab, canCloseTab, intents, labels, renderTab, renderTabTitle, renderTabMenuItems, chrome, onRoom, dropZones, minPaneFraction, hideSplitWhenBlocked, }: DockSurfaceProps): ReactNode;
52
60
  //# sourceMappingURL=DockSurface.d.ts.map
@@ -1,15 +1,17 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { DockIntents, DockLabels, TabRenderer } from '../contract/adapter.ts';
3
- import type { LayoutState } from '../contract/types.ts';
3
+ import type { LayoutState, TabId } from '../contract/types.ts';
4
4
  /** The layout whose `floats` this layer draws. */
5
5
  export interface FloatLayerProps {
6
6
  readonly state: LayoutState;
7
7
  readonly intents: DockIntents;
8
8
  readonly labels: DockLabels;
9
9
  readonly renderTab: TabRenderer;
10
+ /** Whether a floating tab offers its close control; defaults to true. Called per tab on every render. */
11
+ readonly canCloseTab?: (tabId: TabId) => boolean;
10
12
  /** The panel header's title content; omit to show the record's `title` text (see `DockSurfaceProps`). */
11
13
  readonly renderTabTitle?: TabRenderer;
12
14
  }
13
15
  /** Every floating panel, in z order. */
14
- export declare function FloatLayer({ state, intents, labels, renderTab, renderTabTitle }: FloatLayerProps): ReactNode;
16
+ export declare function FloatLayer({ state, intents, labels, renderTab, renderTabTitle, canCloseTab }: FloatLayerProps): ReactNode;
15
17
  //# sourceMappingURL=FloatLayer.d.ts.map
@@ -5,7 +5,8 @@ export interface TabMenuProps {
5
5
  readonly labels: DockLabels;
6
6
  /** The control that opened the menu; the menu hangs below its left edge. */
7
7
  readonly anchor: HTMLElement;
8
- readonly onClose: () => void;
8
+ /** Close the tab; `undefined` removes the kit's item, leaving the extras only. */
9
+ readonly onClose: (() => void) | undefined;
9
10
  /** Dismiss without acting. */
10
11
  readonly onDismiss: () => void;
11
12
  /** Embedder items, rendered after the kit's own; absent means none. */
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ /** The title span of a strip chip or a floating panel's header chip. */
3
+ export declare function TabTitle({ children }: {
4
+ readonly children: ReactNode;
5
+ }): ReactNode;
6
+ //# sourceMappingURL=TabTitle.d.ts.map
@@ -15,9 +15,13 @@ export declare function paneElements(root: HTMLElement): readonly (readonly [Pan
15
15
  /**
16
16
  * Measure every docked pane under `root`.
17
17
  * @param root - the docked surface's element.
18
+ * @param splitHiddenWhenBlocked - whether the embedder hides blocked split
19
+ * controls (`hideSplitWhenBlocked`); the room rule then leaves the control's
20
+ * footprint out of each strip's fixed part, so the reading cannot flip with
21
+ * the control's visibility (see `PaneMeasure.splitControlWidth`).
18
22
  * @returns each pane's fit, keyed by pane id.
19
23
  */
20
- export declare function measurePaneFits(root: HTMLElement): ReadonlyMap<PaneId, HalvesFit>;
24
+ export declare function measurePaneFits(root: HTMLElement, splitHiddenWhenBlocked?: boolean): ReadonlyMap<PaneId, HalvesFit>;
21
25
  /**
22
26
  * One pane's latest reading. A pane the map does not name has not been
23
27
  * measured and fits: the rule only blocks on a positive reading.
@@ -21,10 +21,12 @@ export interface PaneCallbacks {
21
21
  readonly onDividerPressed: (splitId: SplitId, index: number, event: ReactPointerEvent<HTMLElement>) => void;
22
22
  /** Why a pane cannot split right now, or `undefined` while it can. */
23
23
  readonly splitBlock: (paneId: PaneId) => SplitBlock | undefined;
24
- /** Hide budget-blocked split controls without hiding width-blocked controls. */
25
- readonly hideSplitAtCapacity?: boolean;
24
+ /** Hide blocked split controls instead of rendering them disabled. */
25
+ readonly hideSplitWhenBlocked?: boolean;
26
26
  /** Whether a pane's strip draws the add control. */
27
27
  readonly canAddTab: (paneId: PaneId) => boolean;
28
+ /** Whether a tab draws its close control and its menu's close item. */
29
+ readonly canCloseTab: (tabId: TabId) => boolean;
28
30
  /** Live drop preview, or `undefined` while nothing is being dragged. */
29
31
  readonly dropTarget: DropTarget | undefined;
30
32
  /** Show both horizontal landing regions while a body split is being targeted. */
@@ -30,6 +30,8 @@ export interface DockLabels {
30
30
  readonly dockFloat: string;
31
31
  /** Close a floating panel. */
32
32
  readonly closeFloat: string;
33
+ /** The drop hint's caption for each body zone a dragged tab can land on. */
34
+ readonly dropZone: Readonly<Record<DockZone, string>>;
33
35
  }
34
36
  /**
35
37
  * Renders one tab's body. The embedder dispatches on `tab.kind`, which is the
@@ -42,10 +44,12 @@ export type TabRenderer = (tab: TabRecord) => ReactNode;
42
44
  *
43
45
  * The kit's own item is the close gesture; anything that means something about
44
46
  * the tab's content comes from here. An item that acts MUST call `dismiss`,
45
- * because the menu closes on its own items only.
47
+ * because the menu closes on its own items only. Every rendered item MUST
48
+ * carry `role="menuitem"`: the kit probes for that role to dismiss a menu
49
+ * that would paint empty, so items without it count as an empty menu.
46
50
  * @param tab - the tab whose menu is open.
47
51
  * @param dismiss - close the menu without acting.
48
- * @returns the extra items, or nothing.
52
+ * @returns extra actions with ARIA menuitem, menuitemcheckbox, or menuitemradio roles, or nothing.
49
53
  */
50
54
  export type TabMenuExtras = (tab: TabRecord, dismiss: () => void) => ReactNode;
51
55
  /**
@@ -60,6 +60,17 @@ export interface PaneMeasure {
60
60
  readonly chipsWidth: number;
61
61
  /** Width of the fill: free space, not a control. */
62
62
  readonly fillWidth: number;
63
+ /**
64
+ * Footprint of the rendered split control (its width plus the strip's gap)
65
+ * for embedders that hide blocked split controls: a half too narrow to
66
+ * split hides its own control, so the rule leaves the footprint out of the
67
+ * fixed part. Leaving it out is also what keeps the reading stable — the
68
+ * control hiding sheds the same footprint from the strip, and a reading
69
+ * that counted it would flip with the control's visibility and re-render
70
+ * forever. Absent or 0 keeps the control in the fixed part, for embedders
71
+ * that render a blocked control disabled.
72
+ */
73
+ readonly splitControlWidth?: number;
63
74
  }
64
75
  /** Pixel minimums the room rule holds each half to. */
65
76
  export interface SplitMinimums {
@@ -67,15 +78,16 @@ export interface SplitMinimums {
67
78
  readonly divider: number;
68
79
  /** One chip at its minimum: the smallest strip that still names a tab. */
69
80
  readonly chip: number;
70
- /** The smallest body under a strip: one secondary text line inside the body's padding. */
81
+ /** The smallest body under a strip: one secondary text line inside 12px of the body's own insets. */
71
82
  readonly body: number;
72
83
  }
73
84
  /**
74
85
  * The minimums where no computed style can be read, mirroring
75
- * `dockkit.module.css`: `.splitRow > .divider` is 4px wide; `.tab` is 44px of
76
- * content plus 10px + 5px of padding (content-box), 59px; the body's 12px
77
- * padding above and below one 13px secondary line at 1.6 line-height is 45px,
78
- * held to 48px.
86
+ * `dockkit.module.css`: `.splitRow > .divider` takes no layout width (its
87
+ * hairline is painted over the seam); `.tab` is 80px of content plus
88
+ * 10px + 10px of padding (content-box), 100px; 12px above and below one 13px
89
+ * secondary line at 1.6 line-height — the inset a body draws for itself, as
90
+ * `.empty` does — is 45px, held to 48px.
79
91
  */
80
92
  export declare const SPLIT_MINIMUMS: SplitMinimums;
81
93
  /** Whether a pane's two halves after an equal split would each still work. */
@@ -87,9 +99,10 @@ export interface HalvesFit {
87
99
  }
88
100
  /**
89
101
  * The room rule. After an equal split each half must hold what cannot shrink:
90
- * horizontally the strip's fixed part — its width minus the chip box and the
91
- * fill, which is the padding, the gaps, and every control that pane draws —
92
- * plus one chip at its minimum; vertically the strip plus a minimum body. The
102
+ * horizontally the strip's fixed part — its width minus the chip box, the
103
+ * fill, and `splitControlWidth`, which is the padding, the gaps, and every
104
+ * control a half would still draw — plus one chip at its minimum; vertically
105
+ * the strip plus a minimum body. The
93
106
  * borders are what the pane's box exceeds the strip's by. An unmeasured pane
94
107
  * (no layout, as under jsdom) fits: the rule only blocks on a positive reading.
95
108
  * @param measure - the pane's rectangles.
@@ -128,16 +128,19 @@ export declare function planPlaceTab(state: LayoutState, tabId: TabId, toPaneId:
128
128
  /**
129
129
  * Resolve a tab release on a pane body: the centre moves the tab in, an edge
130
130
  * splits the pane and seats the tab in the new half. A pane's only tab released
131
- * on that pane changes nothing in either zone: the split would empty the pane
132
- * and seat the tab beside where it already was.
131
+ * on that pane's centre changes nothing; released on its edge it splits, and
132
+ * the factory's tab backfills the pane the drag would otherwise empty — without
133
+ * a factory that release also changes nothing, since the split would empty the
134
+ * pane and seat the tab beside where it already was.
133
135
  * @param state - current layout.
134
136
  * @param mint - id source for a pane an edge release creates.
135
137
  * @param tabId - the dragged tab.
136
138
  * @param targetPaneId - pane under the pointer.
137
139
  * @param zone - dock region the pointer released in.
140
+ * @param makeTab - builds the tab that backfills a pane its only tab splits away from.
138
141
  * @returns the operations, or none when the release changes nothing.
139
142
  */
140
- export declare function planDropTab(state: LayoutState, mint: Mint, tabId: TabId, targetPaneId: PaneId, zone: DockZone): readonly LayoutOp[];
143
+ export declare function planDropTab(state: LayoutState, mint: Mint, tabId: TabId, targetPaneId: PaneId, zone: DockZone, makeTab?: TabFactory): readonly LayoutOp[];
141
144
  /**
142
145
  * Take a tab out into a floating panel.
143
146
  * @param state - current layout.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-dockkit",
3
3
  "description": "Docking layout kit: split-tree engine with invertible operations, and the React components that render and drive it (zero cordis)",
4
- "version": "0.1.5-alpha.1",
4
+ "version": "0.1.5-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -22,18 +22,16 @@
22
22
  "./package.json": "./package.json"
23
23
  },
24
24
  "license": "MIT",
25
- "dependencies": {
26
- "clsx": "^2.0.0",
27
- "react": "^18.2.0",
28
- "react-dom": "^18.2.0"
29
- },
30
25
  "devDependencies": {
31
26
  "@testing-library/react": "^16.1.0",
32
27
  "@types/react": "~18.3.1",
33
28
  "@types/react-dom": "~18.3.0",
34
29
  "react-dom": "^18.2.0",
30
+ "react": "^18.2.0",
31
+ "clsx": "^2.0.0",
32
+ "@deepseek-ai/dsh-brand": "^0.1.5-alpha.2",
35
33
  "@deepseek-ai/cordis": "^4.0.2",
36
- "@deepseek-ai/dsh-brand": "^0.1.5-alpha.1"
34
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.2"
37
35
  },
38
36
  "files": [
39
37
  "lib/index.js",