@deepseek-ai/dsh-client-ui-dockkit 0.2.2-alpha.2 → 0.2.2-alpha.3

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.i18n.yaml CHANGED
@@ -18,8 +18,8 @@
18
18
  en: 718f52cfce426009
19
19
  zh: e8282e2e3713d242
20
20
  /deepseek-ai-dsh-client-ui-dockkit/embedding-it:
21
- en: 87fab28e0608044e
22
- zh: 2d5f80a367f1852b
21
+ en: 53bbabeb910c6f77
22
+ zh: 51fb4f8fb78cfd02
23
23
  /deepseek-ai-dsh-client-ui-dockkit/interaction-rules-worth-keeping:
24
24
  en: cf2d00ce2b7d5719
25
25
  zh: 5510420f8872e23a
package/README.md CHANGED
@@ -60,6 +60,8 @@ Everything host-specific arrives through props:
60
60
 
61
61
  `dropZones="horizontal"` offers two half-pane hints; once budget or width forbids another split, the whole body accepts a move. A hint is a dashed card inset 8px inside its region, showing the zone's glyph and `labels.dropZone[zone]`; the preview layer covers all tab-body content, while the card under the pointer takes the accent and its neighbour stays a quiet outline. `minPaneFraction` sets the preview minimum, and `planResizeSplit` accepts the same minimum for the committed operation. The Sidebar uses 0.2 and enforces two panes in its own store. The generic engine retains its tree and other split directions. `hideSplitWhenBlocked` hides a blocked split control — pane budget spent or pane too narrow — instead of rendering it disabled; its default is false.
62
62
 
63
+ The split control wrapper exposes `data-dockkit-split-control` for embedder styling, independently of its element tag.
64
+
63
65
  A tab's `kind` is an opaque string. Seeded tabs are factories (`DockControllerOptions`), so what a fresh pane contains is the embedder's decision, not this package's. Content identity is the pair (`kind`, `contentId`): `findContentTab(state, contentId, kind?)` finds the tab showing it anywhere and `findPaneContentTab(state, paneId, contentId, kind?)` within one pane, and `planOpenContent` focuses that tab instead of opening another unless told `revealIfOpened: false`; an explicit `index` seats a new tab at a strip slot rather than at the end.
64
66
 
65
67
  `DockLayout`, used by the Sidebar, keeps each tab in a stable DOM cell across selection, pane moves and floating. It accepts one docked pane or two horizontal panes; CSS Grid resolves their widths. `keepMounted(tab)` retains visited bodies and `active` controls Session visibility without reparenting content.
package/README.zh.md CHANGED
@@ -60,6 +60,8 @@ kind: "package-reference"
60
60
 
61
61
  `dropZones="horizontal"` 提供左右两个半区提示;预算或宽度不允许再拆时,正文整格接收移动。提示是一张内缩 8px 的虚线卡片,显示该落区的图形和 `labels.dropZone[zone]`;预览层覆盖全部 tab 正文,指针所在的卡片取强调色,另一张保持安静的轮廓。`minPaneFraction` 控制预览的最小比例,`planResizeSplit` 接受相同最小值以约束提交;Sidebar使用0.2并在自己的store限制两格。通用引擎仍保留原有树与其它分割方向。 `hideSplitWhenBlocked` 在分栏被阻止时(窗格预算已满或格太窄)直接隐藏分栏控件而不是渲染禁用态,默认值为 false。
62
62
 
63
+ 分栏控件的包裹元素通过 `data-dockkit-split-control` 提供宿主样式入口,不依赖具体元素标签。
64
+
63
65
  tab 的 `kind` 是不透明字符串。种子 tab 是工厂(`DockControllerOptions`),因此新格里放什么由嵌入方决定,与本包无关。内容身份是二元组(`kind`、`contentId`):`findContentTab(state, contentId, kind?)` 在任意位置找到展示它的 tab,`findPaneContentTab(state, paneId, contentId, kind?)` 在一个格内找;`planOpenContent` 会聚焦该 tab 而非再开一个,除非被告知 `revealIfOpened: false`;显式的 `index` 把新 tab 放到 tab 条的某个位置而非末尾。
64
66
 
65
67
  Sidebar 使用的 `DockLayout` 在选中项变化、跨格移动与浮动期间,把每个 tab 保留在稳定的 DOM 容器中。它接受一个停靠格或左右两个格,由 CSS Grid 计算宽度。`keepMounted(tab)` 保留已访问正文,`active` 控制 Session 可见性,不改变内容的父子关系。
package/lib/index.js CHANGED
@@ -2461,30 +2461,6 @@ function TabTitle({ children }) {
2461
2461
  //#endregion
2462
2462
  //#region lib/types/components/TabPanel.js
2463
2463
  /**
2464
- * One pane: its tab strip (drag source, drop target, split control) and the
2465
- * active tab's body with the dock preview overlay. Presentational; every gesture
2466
- * leaves through `PaneCallbacks`, and the body itself comes from `renderTab`.
2467
- *
2468
- * A chip is a capsule carrying one control, its close, shown over its right
2469
- * end while the chip is active, hovered, or focused; the context menu
2470
- * (secondary press) carries the same close plus whatever the embedder appends.
2471
- * A middle click anywhere on the chip, including its close control, closes
2472
- * without selecting the tab, unless another mouse button is still held.
2473
- * The embedder's `canCloseTab`
2474
- * governs all close routes. A pane's lone chip whose close is withheld draws
2475
- * quiet — no capsule, no hover fill — since there is nothing to select against
2476
- * and nothing to do to it.
2477
- * Between neighbouring chips sits a slot: a fixed-width box drawing a
2478
- * hairline, blank beside the active chip, and the drop caret when a drag
2479
- * targets that index, so a caret never widens the row; the two end slots
2480
- * exist only while targeted. The chips sit in their own box, the strip's one
2481
- * shrinking part: in a narrow pane their titles fade at the clipped edge down
2482
- * to the chip's floor and then the chips scroll there, keeping the active one
2483
- * in view, so the add control after them (drawn while the embedder's
2484
- * `canAddTab` allows), the pane's split control, and the embedder's chrome keep
2485
- * their width and their place at the strip's end.
2486
- */
2487
- /**
2488
2464
  * The ic_ds_panel_left_outline_16 frame alone: its outer and inner rounded
2489
2465
  * rectangles as one even-odd ring, without the divider. The glyphs below draw
2490
2466
  * inside it so they read as siblings of the panel controls beside them.
@@ -2735,6 +2711,20 @@ function TabStrip({ state, pane, callbacks, offsets }) {
2735
2711
  const selected = tabId === pane.activeTabId;
2736
2712
  const closable = callbacks.canCloseTab(tabId);
2737
2713
  const quiet = !closable && pane.tabs.length === 1;
2714
+ const onKeyDown = (event) => {
2715
+ if (event.target !== event.currentTarget) return;
2716
+ if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.nativeEvent.isComposing) return;
2717
+ const next = chipToFocus(event.key, pane.tabs, tabId);
2718
+ if (next !== void 0) {
2719
+ event.preventDefault();
2720
+ focusChip(next);
2721
+ return;
2722
+ }
2723
+ if (selects(event.key)) {
2724
+ event.preventDefault();
2725
+ activate(tabId);
2726
+ }
2727
+ };
2738
2728
  return jsxs(Fragment$1, { children: [(index > 0 || stripIndex === index) && jsx("div", {
2739
2729
  className: clsx(css.slot, stripIndex === index && css.slotCaret),
2740
2730
  "data-dockkit-caret": stripIndex === index ? index : void 0
@@ -2766,32 +2756,25 @@ function TabStrip({ state, pane, callbacks, offsets }) {
2766
2756
  event.stopPropagation();
2767
2757
  activate(tabId);
2768
2758
  },
2769
- onKeyDown: (event) => {
2770
- if (event.target !== event.currentTarget) return;
2771
- if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.nativeEvent.isComposing) return;
2772
- const next = chipToFocus(event.key, pane.tabs, tabId);
2773
- if (next !== void 0) {
2774
- event.preventDefault();
2775
- focusChip(next);
2776
- return;
2777
- }
2778
- if (selects(event.key)) {
2759
+ ...callbacks.getTabTriggerProps?.(tab, pane.id, {
2760
+ onKeyDown,
2761
+ tabIndex: selected ? 0 : -1
2762
+ }) ?? {
2763
+ onKeyDown,
2764
+ onContextMenu: (event) => {
2779
2765
  event.preventDefault();
2780
- activate(tabId);
2766
+ event.stopPropagation();
2767
+ const anchor = event.currentTarget;
2768
+ setMenu((current) => current?.tabId === tabId ? void 0 : {
2769
+ tabId,
2770
+ anchor
2771
+ });
2781
2772
  }
2782
2773
  },
2783
- onContextMenu: (event) => {
2784
- event.preventDefault();
2785
- const anchor = event.currentTarget;
2786
- setMenu((current) => current?.tabId === tabId ? void 0 : {
2787
- tabId,
2788
- anchor
2789
- });
2790
- },
2791
2774
  children: [
2792
2775
  jsx(TabTitle, { children: callbacks.renderTabTitle?.(tab) ?? tab.title }),
2793
2776
  closable && jsx(Tooltip, {
2794
- disabled: menu !== void 0,
2777
+ disabled: menu !== void 0 || callbacks.tabMenuOpen === true,
2795
2778
  label: callbacks.labels.closeTab,
2796
2779
  shortcutKeys: callbacks.labels.closeTabKeys,
2797
2780
  side: "bottom",
@@ -2860,6 +2843,7 @@ function TabStrip({ state, pane, callbacks, offsets }) {
2860
2843
  delayMs: 500,
2861
2844
  openOnClick: block !== void 0,
2862
2845
  children: jsx("span", {
2846
+ "data-dockkit-split-control": true,
2863
2847
  tabIndex: block === void 0 ? void 0 : 0,
2864
2848
  "aria-label": block === void 0 ? void 0 : splitBlockedTitle(callbacks.labels, block),
2865
2849
  onClick: (event) => {
@@ -3690,7 +3674,7 @@ function sameSizes(a, b) {
3690
3674
  });
3691
3675
  }
3692
3676
  /** The split tree and the gestures over it. */
3693
- function Surface({ state, canSplit, canAddTab, canCloseTab, intents, labels, renderTab, renderTabTitle, renderTabMenuItems, chrome, onRoom, draw, dropZones = "edges", minPaneFraction = MIN_PANE_FRACTION, hideSplitWhenBlocked = false }) {
3677
+ function Surface({ state, canSplit, canAddTab, canCloseTab, intents, labels, renderTab, renderTabTitle, renderTabMenuItems, getTabTriggerProps, tabMenuOpen, chrome, onRoom, draw, dropZones = "edges", minPaneFraction = MIN_PANE_FRACTION, hideSplitWhenBlocked = false }) {
3694
3678
  const surface = useRef(null);
3695
3679
  const [preview, setPreview] = useState(NO_PREVIEW);
3696
3680
  const [fits, setFits] = useState(NO_FITS);
@@ -3811,6 +3795,8 @@ function Surface({ state, canSplit, canAddTab, canCloseTab, intents, labels, ren
3811
3795
  renderTab,
3812
3796
  renderTabTitle,
3813
3797
  renderTabMenuItems,
3798
+ getTabTriggerProps,
3799
+ tabMenuOpen,
3814
3800
  chromePaneId: topRightPaneId(state),
3815
3801
  chrome
3816
3802
  };
@@ -1,6 +1,6 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ReactNode, HTMLAttributes } from 'react';
2
2
  import type { DockIntents, DockLabels, TabMenuExtras, TabRenderer } from '../contract/adapter.ts';
3
- import type { LayoutState, PaneId, TabId } from '../contract/types.ts';
3
+ import type { LayoutState, PaneId, TabId, TabRecord } from '../contract/types.ts';
4
4
  import type { HalvesFit } from '../engine/geometry.ts';
5
5
  import { type TabRetention } from './TabLayout.tsx';
6
6
  /** What the docked surface needs: the layout, its limits, and the outward contracts. */
@@ -41,7 +41,11 @@ export interface DockSurfaceProps {
41
41
  * slot, and nothing outside that embedder is expected to supply it.
42
42
  */
43
43
  readonly renderTabTitle?: TabRenderer;
44
- /** Extra items for a tab's context menu; omit for the kit's own item only. */
44
+ /** Whether the embedder popup is preparing or visible; suppresses tab tooltips. */
45
+ readonly tabMenuOpen?: boolean | undefined;
46
+ /** Override the kit popup; the embedder owns rendering and dismissal. */
47
+ readonly getTabTriggerProps?: ((tab: TabRecord, paneId: PaneId, handlers?: Pick<HTMLAttributes<HTMLElement>, 'onContextMenu' | 'onKeyDown' | 'tabIndex'>) => Pick<HTMLAttributes<HTMLElement>, 'onContextMenu' | 'onKeyDown' | 'tabIndex'>) | undefined;
48
+ /** Extra rows for the kit popup; ignored when getTabTriggerProps is supplied. */
45
49
  readonly renderTabMenuItems?: TabMenuExtras;
46
50
  /**
47
51
  * Surface-wide controls, drawn at the far end of the top-right pane's tab
@@ -2,9 +2,9 @@
2
2
  * Prop shares the kit's own components pass among themselves. These are internal
3
3
  * to the package — the outward contracts are in `adapter.ts`.
4
4
  */
5
- import type { PointerEvent as ReactPointerEvent, ReactNode } from 'react';
5
+ import type { PointerEvent as ReactPointerEvent, ReactNode, HTMLAttributes } from 'react';
6
6
  import type { DockLabels, TabMenuExtras, TabRenderer } from '../contract/adapter.ts';
7
- import type { PaneId, SplitId, TabId } from '../contract/types.ts';
7
+ import type { PaneId, SplitId, TabId, TabRecord } from '../contract/types.ts';
8
8
  import type { DropTarget } from '../engine/geometry.ts';
9
9
  /** Why a pane's split control is disabled: the pane budget, or too little width for two halves. */
10
10
  export type SplitBlock = 'budget' | 'width';
@@ -37,7 +37,11 @@ export interface PaneCallbacks {
37
37
  readonly renderTab: TabRenderer;
38
38
  /** A chip's or panel header's title content; absent means the record's `title` text. */
39
39
  readonly renderTabTitle: TabRenderer | undefined;
40
- /** Embedder items appended to a tab's context menu; absent means the kit's item only. */
40
+ /** Whether the embedder popup is preparing or visible; suppresses tab tooltips. */
41
+ readonly tabMenuOpen?: boolean | undefined;
42
+ /** Override the kit popup; the embedder owns rendering and dismissal. */
43
+ readonly getTabTriggerProps?: ((tab: TabRecord, paneId: PaneId, handlers?: Pick<HTMLAttributes<HTMLElement>, 'onContextMenu' | 'onKeyDown' | 'tabIndex'>) => Pick<HTMLAttributes<HTMLElement>, 'onContextMenu' | 'onKeyDown' | 'tabIndex'>) | undefined;
44
+ /** Extra rows for the kit popup; ignored when getTabTriggerProps is supplied. */
41
45
  readonly renderTabMenuItems: TabMenuExtras | undefined;
42
46
  /** The pane whose strip hosts the embedder's surface-wide controls. */
43
47
  readonly chromePaneId: PaneId;
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.2.2-alpha.2",
4
+ "version": "0.2.2-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@deepseek-ai/cordis": "~4.0.6-alpha.1",
27
- "@deepseek-ai/dsh-brand": "0.2.2-alpha.2",
28
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.2-alpha.2",
26
+ "@deepseek-ai/cordis": "~4.0.6-alpha.2",
27
+ "@deepseek-ai/dsh-brand": "0.2.2-alpha.3",
28
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.2-alpha.3",
29
29
  "@testing-library/react": "^16.3.3",
30
30
  "react-dom": "^18.2.0",
31
31
  "react": "^18.2.0",
@@ -37,6 +37,6 @@
37
37
  "lib/types/**/*.d.ts"
38
38
  ],
39
39
  "peerDependencies": {
40
- "@deepseek-ai/cordis": "~4.0.6-alpha.1"
40
+ "@deepseek-ai/cordis": "~4.0.6-alpha.2"
41
41
  }
42
42
  }