@deepseek-ai/dsh-client-ui-dockkit 0.1.7-alpha.2 → 0.1.7-rc.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.
- package/README.i18n.yaml +39 -5
- package/README.md +8 -0
- package/README.zh.md +8 -0
- package/lib/components/dockkit.module.css +35 -34
- package/lib/index.js +70 -34
- package/lib/types/contract/adapter.d.ts +8 -0
- package/package.json +4 -4
package/README.i18n.yaml
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
|
-
# Bilingual-pair consistency record (docs/i18n/README.md):
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-dockkit/README.md
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/:
|
|
6
|
+
en: 93fc2e9e86fc50f8
|
|
7
|
+
zh: 61c7c38a66935ad6
|
|
8
|
+
/deepseek-ai-dsh-client-ui-dockkit:
|
|
9
|
+
en: 9d34bd85fc9e76b4
|
|
10
|
+
zh: 615d1d2eab451c87
|
|
11
|
+
/deepseek-ai-dsh-client-ui-dockkit/summary:
|
|
12
|
+
en: 3ee4584a73215ef4
|
|
13
|
+
zh: 051170a6d0993fa5
|
|
14
|
+
/deepseek-ai-dsh-client-ui-dockkit/table-of-contents:
|
|
15
|
+
en: 42038fdc402b9c2a
|
|
16
|
+
zh: 99890f8f908624ce
|
|
17
|
+
/deepseek-ai-dsh-client-ui-dockkit/the-two-layers:
|
|
18
|
+
en: ea872ff2c2089037
|
|
19
|
+
zh: b3ec3a218eacd5c8
|
|
20
|
+
/deepseek-ai-dsh-client-ui-dockkit/embedding-it:
|
|
21
|
+
en: 789c879dd83f463a
|
|
22
|
+
zh: 37f2dc45f9588810
|
|
23
|
+
/deepseek-ai-dsh-client-ui-dockkit/interaction-rules-worth-keeping:
|
|
24
|
+
en: 7e5c1e9a9f9f244f
|
|
25
|
+
zh: 7e3a8fcef86bc7dd
|
|
26
|
+
/deepseek-ai-dsh-client-ui-dockkit/build-shape:
|
|
27
|
+
en: 21216ba5798d7f5f
|
|
28
|
+
zh: ac505fdcd5c114d4
|
|
29
|
+
/deepseek-ai-dsh-client-ui-dockkit/model-experience:
|
|
30
|
+
en: cdebb91645623401
|
|
31
|
+
zh: fe262db316714fa8
|
|
32
|
+
/deepseek-ai-dsh-client-ui-dockkit/model-experience/kv-cache-effect:
|
|
33
|
+
en: ca75c51c89c2c9b0
|
|
34
|
+
zh: f57cc48a1b5cbfa9
|
|
35
|
+
/deepseek-ai-dsh-client-ui-dockkit/known-limitations-and-deferred-work:
|
|
36
|
+
en: 4323810ca5a87e6c
|
|
37
|
+
zh: 3a24af2ea4467ab7
|
|
38
|
+
/deepseek-ai-dsh-client-ui-dockkit/known-limitations-and-deferred-work/dev-note:
|
|
39
|
+
en: 9eda9e1ea3785ca7
|
|
40
|
+
zh: b734eb644b27e474
|
package/README.md
CHANGED
|
@@ -39,6 +39,8 @@ A docking layout kit: a split tree of tabbed panes with invertible operations, a
|
|
|
39
39
|
|
|
40
40
|
**The components** render a layout snapshot and report settled intents — one per gesture, never a drag frame. A drag previews in local state while the gesture's own facts stay in its closure; on release the net result leaves through one `DockIntents` call — a strip release reports the caret slot as drawn, the dragged chip counted, and `planPlaceTab` turns that into the reorder or the move. That is what lets an embedder record exactly one history entry per gesture. The strip follows the WAI-ARIA tabs pattern with manual activation: the selected chip is in the tab order; Left and Right (wrapping), Home, and End move focus between chips without selecting; Enter or Space selects the focused chip through the same intent as a click. A chip is a capsule carrying one control, its close; the context menu (a secondary press on the chip) carries the same close plus the embedder's items — a menu that would hold no item at all never shows — and renders in a portal positioned against the chip because the chip box clips its overflow on purpose (see below). After the chips sits the add control, which asks the embedder (`DockIntents.addTab`) to seat its seeded tab; the embedder's `canAddTab(paneId)` decides per pane whether the control is drawn at all. Copying a tab has no kit control — it is the embedder's API — and floating is the drag released clear of the surface.
|
|
41
41
|
|
|
42
|
+
Tab context menus show the close action name without a shortcut hint: they act on the clicked tab, while shortcuts can target another focused pane. An unmodified Escape dismisses the foreground tab menu without closing the tab and returns focus from a menu item to its tab without drawing a focus outline. Tab and arrow navigation retain the tab’s visible focus indicator. Composition and held-key repeats do not dismiss it.
|
|
43
|
+
|
|
42
44
|
<a id="embedding-it"></a>
|
|
43
45
|
## Embedding it
|
|
44
46
|
|
|
@@ -63,6 +65,8 @@ A tab's `kind` is an opaque string. Seeded tabs are factories (`DockControllerOp
|
|
|
63
65
|
<a id="interaction-rules-worth-keeping"></a>
|
|
64
66
|
## Interaction rules worth keeping
|
|
65
67
|
|
|
68
|
+
The split control accepts localized tooltip text, separate effective keys through `splitPaneKeys`, and an ARIA combination from its embedder. Close controls receive their keys through `closeTabKeys`. A disabled control has a keyboard-focusable wrapper that explains the pane-budget or width restriction. Docked and floating pane containers can receive programmatic focus without entering the normal tab sequence or drawing a focus outline; their controls retain their own keyboard focus indicators. Tab navigation and selection use unmodified keys and leave composition input to its owner.
|
|
69
|
+
|
|
66
70
|
These are not stylistic; each one fixes a defect found in a real browser.
|
|
67
71
|
|
|
68
72
|
- **Capture the pointer** when a gesture starts. Without it any scroll container the pointer crosses can claim the gesture, which the browser reports as a cancelled pointer and an abandoned drag. Capture is hardening — the window listeners carry the gesture either way, so an environment without the API still works.
|
|
@@ -99,11 +103,15 @@ None; this package neither assembles nor sends a provider request.
|
|
|
99
103
|
<a id="dev-note"></a>
|
|
100
104
|
### Dev Note
|
|
101
105
|
|
|
106
|
+
Menus use the shared `MenuSurface` material, including the macOS backing for background blur; custom content follows the [menu rules](../../../docs/web-styling.md#component-rules).
|
|
107
|
+
|
|
102
108
|
<details>
|
|
103
109
|
<summary>Working context for maintainers — click to expand</summary>
|
|
104
110
|
|
|
105
111
|
None.
|
|
106
112
|
|
|
113
|
+
The embedder supplies effective close keycaps and ARIA combinations for tab and floating close controls. Close controls expose the same tooltip on hover and keyboard focus. Tab close tooltips stay hidden while their pane's context menu is open.
|
|
114
|
+
|
|
107
115
|
</details>
|
|
108
116
|
|
|
109
117
|
**Runtime invariant:** No companion is published. The engine is pure functions over plain data and the components report intents only; the operation sequence's invertibility and the settle rule are asserted directly by this package's engine specs, and no cordis service is provided or observed.
|
package/README.zh.md
CHANGED
|
@@ -39,6 +39,8 @@ kind: "package-reference"
|
|
|
39
39
|
|
|
40
40
|
**组件**渲染布局快照并上报已落定的意图——每次手势一条,绝不上报拖动帧。拖动过程中在本地状态里预览,手势自身的事实留在它的闭包里;松手时净结果通过一次 `DockIntents` 调用离开——在标签条上松手上报的是按绘制顺序数出的插入槽位(被拖的 chip 也计入),由 `planPlaceTab` 换算成重排或移动。正是这一点让嵌入方能为每次手势记录恰好一条历史。标签条遵循 WAI-ARIA tabs 模式的手动激活:选中的 chip 在 Tab 键序里;左右方向键(循环)、Home、End 只在 chip 之间移动焦点而不选中;Enter 或空格选中当前聚焦的 chip,走与点击相同的意图。chip 是一个胶囊,携带唯一的控件——它的关闭按钮;上下文菜单(在 chip 上的次键按下)携带同样的关闭项加上嵌入方的条目——一个连一项都没有的菜单绝不展示——并渲染在按 chip 定位的 portal 里,因为 chip 盒会故意裁掉溢出(见下文)。chip 之后是添加控件,它请嵌入方(`DockIntents.addTab`)安放其种子 tab;嵌入方的 `canAddTab(paneId)` 按格决定是否绘制该控件。复制 tab 没有套件控件——那是嵌入方的 API——而浮出就是把拖动松手在停靠区之外。
|
|
41
41
|
|
|
42
|
+
标签上下文菜单只显示关闭操作名,不显示快捷键:菜单操作针对被点击的标签,快捷键则可能作用于其他焦点分栏。无修饰键的 Esc 关闭前台标签菜单而不关闭标签,并将菜单项的焦点交还标签,不绘制焦点外轮廓线。Tab 和方向键导航保留标签页的可见焦点提示。输入法组合输入和长按重复事件不关闭菜单。
|
|
43
|
+
|
|
42
44
|
<a id="embedding-it"></a>
|
|
43
45
|
## 如何嵌入
|
|
44
46
|
|
|
@@ -63,6 +65,8 @@ Sidebar 使用的 `DockLayout` 在选中项变化、跨格移动与浮动期间
|
|
|
63
65
|
<a id="interaction-rules-worth-keeping"></a>
|
|
64
66
|
## 值得保留的交互规则
|
|
65
67
|
|
|
68
|
+
分栏控件接收嵌入方传入的本地化提示文本、`splitPaneKeys` 独立按键及 ARIA 组合。关闭控件通过 `closeTabKeys` 接收按键。禁用控件由可键盘聚焦的容器承载,说明分栏数量或宽度限制。停靠与浮动面板容器支持程序设置焦点,不进入正常 Tab 顺序,也不绘制焦点外框;内部控件保留各自的键盘焦点提示。标签导航和选择只使用无修饰键组合,输入法按键交回其所属输入区域。
|
|
69
|
+
|
|
66
70
|
这些不是风格偏好;每一条都修复了在真实浏览器里发现的缺陷。
|
|
67
71
|
|
|
68
72
|
- **手势开始时捕获指针。** 不捕获的话,指针经过的任何滚动容器都可能接管手势,浏览器会将其报告为指针取消和拖动中止。捕获是加固——无论如何都由 window 监听器承载手势,所以没有该 API 的环境照样可用。
|
|
@@ -99,11 +103,15 @@ Sidebar 使用的 `DockLayout` 在选中项变化、跨格移动与浮动期间
|
|
|
99
103
|
<a id="dev-note"></a>
|
|
100
104
|
### 开发备注
|
|
101
105
|
|
|
106
|
+
菜单采用共享 `MenuSurface` 材质,包括用于背景模糊的 macOS 底层;自定义内容遵循[菜单规则](../../../docs/web-styling.zh.md#component-rules)。
|
|
107
|
+
|
|
102
108
|
<details>
|
|
103
109
|
<summary>维护者工作上下文——点击展开</summary>
|
|
104
110
|
|
|
105
111
|
无。
|
|
106
112
|
|
|
113
|
+
嵌入方为标签和浮动面板的关闭控件提供有效关闭键帽及 ARIA 组合。关闭控件在悬停和键盘聚焦时显示相同 Tooltip。分栏的上下文菜单打开时,该分栏的标签关闭 Tooltip 保持隐藏。
|
|
114
|
+
|
|
107
115
|
</details>
|
|
108
116
|
|
|
109
117
|
**运行时不变量:** 不发布 companion。引擎是作用于纯数据的纯函数,组件只上报意图;操作序列的可逆性与 settle 规则由本包的引擎 spec 直接断言,不提供也不观察任何 Cordis 服务。
|
|
@@ -215,6 +215,11 @@
|
|
|
215
215
|
overflow: hidden;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
.pane:focus,
|
|
219
|
+
.float:focus {
|
|
220
|
+
outline: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
218
223
|
/* A 38px row whose 28px content band sits at its bottom: chips fill the band,
|
|
219
224
|
so they end flush with the strip's bottom edge, and the 28px add control, the
|
|
220
225
|
28px split control, and the embedder's 28px chrome centre on the band's one
|
|
@@ -297,16 +302,17 @@
|
|
|
297
302
|
margin-left: 4px;
|
|
298
303
|
}
|
|
299
304
|
|
|
300
|
-
/* macOS desktop: an embedder may pin this surface
|
|
301
|
-
|
|
302
|
-
in
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
non-visible boxes when it collects
|
|
305
|
+
/* macOS desktop: an embedder may pin this surface at the window's top edge (the
|
|
306
|
+
right sidebar does, in both presentations). The strip row is marked
|
|
307
|
+
`data-window-drag` in markup (TabPanel), so ui-web base.css's single darwin
|
|
308
|
+
rule drags it over its own box; the chip run, the strip-end chrome, the pane
|
|
309
|
+
body, and the split divider subtract their whole boxes so tabs, controls,
|
|
310
|
+
content, and the resize grab stay usable wherever they overlap that row. The
|
|
311
|
+
divider must be named here: it is a plain div, so ui-web base.css's
|
|
312
|
+
interactive-element subtraction never matches it. A floating pane subtracts its
|
|
313
|
+
own box the same way, so the strip row never drags the window over it. Hidden
|
|
314
|
+
surfaces contribute nothing: Blink skips non-visible boxes when it collects
|
|
315
|
+
draggable regions. */
|
|
310
316
|
:global(html[data-platform='darwin']) .stripTabs,
|
|
311
317
|
:global(html[data-platform='darwin']) .stripChrome,
|
|
312
318
|
:global(html[data-platform='darwin']) .paneBody,
|
|
@@ -334,8 +340,8 @@
|
|
|
334
340
|
background: var(--dsw-alias-border-l4);
|
|
335
341
|
}
|
|
336
342
|
|
|
337
|
-
/* The active chip is a filled
|
|
338
|
-
slots beside it go blank, so the row reads as the
|
|
343
|
+
/* The active chip is a filled tab and needs no rule against it: the two
|
|
344
|
+
slots beside it go blank, so the row reads as the tab between bare
|
|
339
345
|
chips. A caret in either slot still draws. */
|
|
340
346
|
.tabActive + .slot:not(.slotCaret)::before,
|
|
341
347
|
.slot:not(.slotCaret):has(+ .tabActive)::before {
|
|
@@ -348,7 +354,7 @@
|
|
|
348
354
|
background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
|
|
349
355
|
}
|
|
350
356
|
|
|
351
|
-
/* The chip: a 28px
|
|
357
|
+
/* The chip: a 28px tab whose title runs to the right inset. The close
|
|
352
358
|
control is not in the flow — it sits over the title's last 14px and shows
|
|
353
359
|
while the chip is active, hovered, or holds focus — so a chip is the same
|
|
354
360
|
width with and without it and nothing shifts on hover. */
|
|
@@ -365,7 +371,7 @@
|
|
|
365
371
|
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
366
372
|
line-height: 1;
|
|
367
373
|
white-space: nowrap;
|
|
368
|
-
border-radius:
|
|
374
|
+
border-radius: var(--dsw-radius-sm);
|
|
369
375
|
cursor: pointer;
|
|
370
376
|
touch-action: none;
|
|
371
377
|
user-select: none;
|
|
@@ -422,7 +428,7 @@
|
|
|
422
428
|
line-height: 1;
|
|
423
429
|
background: transparent;
|
|
424
430
|
border: none;
|
|
425
|
-
border-radius:
|
|
431
|
+
border-radius: var(--dsw-radius-sm);
|
|
426
432
|
cursor: pointer;
|
|
427
433
|
opacity: 0;
|
|
428
434
|
/* Hidden means untouchable too: without this a touch press on the chip's
|
|
@@ -439,7 +445,7 @@
|
|
|
439
445
|
pointer-events: auto;
|
|
440
446
|
}
|
|
441
447
|
|
|
442
|
-
/* The chip's height and corner, so it reads as one more
|
|
448
|
+
/* The chip's height and corner, so it reads as one more tab in the row. */
|
|
443
449
|
.addTab {
|
|
444
450
|
display: flex;
|
|
445
451
|
flex: none;
|
|
@@ -452,7 +458,7 @@
|
|
|
452
458
|
line-height: 1;
|
|
453
459
|
background: transparent;
|
|
454
460
|
border: none;
|
|
455
|
-
border-radius:
|
|
461
|
+
border-radius: var(--dsw-radius-sm);
|
|
456
462
|
cursor: pointer;
|
|
457
463
|
}
|
|
458
464
|
|
|
@@ -470,13 +476,13 @@
|
|
|
470
476
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
471
477
|
}
|
|
472
478
|
|
|
473
|
-
/* The active chip is the filled
|
|
479
|
+
/* The active chip is the filled tab, in the tag fill; the rest are bare text. */
|
|
474
480
|
.tabActive {
|
|
475
481
|
color: var(--dsw-alias-label-primary);
|
|
476
482
|
background: var(--dsw-alias-markdown-tag);
|
|
477
483
|
}
|
|
478
484
|
|
|
479
|
-
/* A pane's lone unclosable chip is a label, not a choice: no
|
|
485
|
+
/* A pane's lone unclosable chip is a label, not a choice: no tab, no
|
|
480
486
|
hover fill — the primary ink on the pane's own ground. Its title never
|
|
481
487
|
yields to a close control, so it keeps the clipped fade alone. */
|
|
482
488
|
.tab.tabQuiet,
|
|
@@ -514,7 +520,7 @@
|
|
|
514
520
|
line-height: 1;
|
|
515
521
|
background: transparent;
|
|
516
522
|
border: none;
|
|
517
|
-
border-radius:
|
|
523
|
+
border-radius: var(--dsw-radius-sm);
|
|
518
524
|
cursor: pointer;
|
|
519
525
|
}
|
|
520
526
|
|
|
@@ -544,11 +550,8 @@
|
|
|
544
550
|
display: flex;
|
|
545
551
|
flex-direction: column;
|
|
546
552
|
min-width: 96px;
|
|
547
|
-
padding:
|
|
548
|
-
background: var(--dsw-specific-menu);
|
|
549
|
-
backdrop-filter: var(--dsw-menu-backdrop-filter);
|
|
553
|
+
padding: 4px;
|
|
550
554
|
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
551
|
-
border-radius: 8px;
|
|
552
555
|
}
|
|
553
556
|
|
|
554
557
|
.menu:not(:has([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio'])) {
|
|
@@ -562,7 +565,7 @@
|
|
|
562
565
|
text-align: left;
|
|
563
566
|
background: transparent;
|
|
564
567
|
border: none;
|
|
565
|
-
border-radius:
|
|
568
|
+
border-radius: var(--dsw-radius-md);
|
|
566
569
|
cursor: pointer;
|
|
567
570
|
}
|
|
568
571
|
|
|
@@ -667,7 +670,7 @@
|
|
|
667
670
|
overflow: hidden;
|
|
668
671
|
color: var(--dsw-alias-label-tertiary);
|
|
669
672
|
border: 1.5px dashed var(--dsw-alias-border-l2);
|
|
670
|
-
border-radius:
|
|
673
|
+
border-radius: var(--dsw-radius-lg);
|
|
671
674
|
transition: color 120ms ease-out, background-color 120ms ease-out, border-color 120ms ease-out;
|
|
672
675
|
animation: dockHintIn 140ms ease-out;
|
|
673
676
|
}
|
|
@@ -744,7 +747,7 @@
|
|
|
744
747
|
to its own corners. */
|
|
745
748
|
overflow: hidden;
|
|
746
749
|
background: var(--dsw-alias-bg-layer-2);
|
|
747
|
-
border-radius:
|
|
750
|
+
border-radius: var(--dsw-radius-panel);
|
|
748
751
|
/* The menu's shadow with the stroke rebound one step lighter than the
|
|
749
752
|
default (l2): the panel reads as the same kind of raised surface, and the
|
|
750
753
|
shadow's hairline outlines it, so the frame draws no border of its own. */
|
|
@@ -801,20 +804,18 @@
|
|
|
801
804
|
touch-action: none;
|
|
802
805
|
}
|
|
803
806
|
|
|
804
|
-
/* The grip
|
|
805
|
-
|
|
806
|
-
while the pointer is over the panel — an idle panel keeps a clean corner —
|
|
807
|
-
and `:active` keeps it lit while a drag holds the pointer capture. */
|
|
807
|
+
/* The grip follows the floating panel's corner with a 4px inset. Pointer
|
|
808
|
+
hover and an active drag reveal it. */
|
|
808
809
|
.floatResize::after {
|
|
809
810
|
content: '';
|
|
810
811
|
position: absolute;
|
|
811
812
|
right: 4px;
|
|
812
813
|
bottom: 4px;
|
|
813
|
-
width:
|
|
814
|
-
height:
|
|
814
|
+
width: calc(var(--dsw-radius-panel) - 4px);
|
|
815
|
+
height: calc(var(--dsw-radius-panel) - 4px);
|
|
815
816
|
border-right: 1.5px solid var(--dsw-alias-label-caption);
|
|
816
817
|
border-bottom: 1.5px solid var(--dsw-alias-label-caption);
|
|
817
|
-
border-bottom-right-radius:
|
|
818
|
+
border-bottom-right-radius: calc(var(--dsw-radius-panel) - 4px);
|
|
818
819
|
opacity: 0;
|
|
819
820
|
transition: opacity 120ms ease-out;
|
|
820
821
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Children, Fragment as Fragment$1, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import { IconCloseFillRegular, IconCloseOutlineRegular, IconPanelLeftOutlineRegular, IconPlusOutlineRegular, Tooltip } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
4
|
+
import { IconCloseFillRegular, IconCloseOutlineRegular, IconPanelLeftOutlineRegular, IconPlusOutlineRegular, MenuSurface, Tooltip, focusWithoutRing, modalSelector, observeComposition } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import css from "./components/dockkit.module.css";
|
|
7
7
|
//#region lib/types/engine/tree.js
|
|
@@ -2230,7 +2230,7 @@ function useGesture(reset) {
|
|
|
2230
2230
|
* released clear of the surface. A menu that would hold no item at all renders
|
|
2231
2231
|
* no popup, so a secondary press on a chip with nothing to offer shows nothing.
|
|
2232
2232
|
* Presentational — it renders what its props supply and dismisses itself on
|
|
2233
|
-
* outside presses.
|
|
2233
|
+
* outside presses or an unmodified Escape, returning menu focus to its tab.
|
|
2234
2234
|
*
|
|
2235
2235
|
* It renders in a portal, positioned against the control that opened it. The tab
|
|
2236
2236
|
* strip clips its overflow on purpose (so it never becomes a scroll container
|
|
@@ -2264,17 +2264,35 @@ function TabMenu({ labels, anchor, onClose, onDismiss, extras }) {
|
|
|
2264
2264
|
const menu = self.current;
|
|
2265
2265
|
/* v8 ignore next -- the ref is attached by effect time: the menu renders unconditionally. */
|
|
2266
2266
|
if (menu === null) return void 0;
|
|
2267
|
+
const document = menu.ownerDocument;
|
|
2268
|
+
const composition = observeComposition(document);
|
|
2269
|
+
const onKeyDown = (event) => {
|
|
2270
|
+
if (composition.guards(event) || event.defaultPrevented || event.key !== "Escape" || event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) return;
|
|
2271
|
+
if ([...document.querySelectorAll(modalSelector)].at(-1) !== menu) return;
|
|
2272
|
+
event.preventDefault();
|
|
2273
|
+
if (event.repeat) return;
|
|
2274
|
+
const restoreFocus = menu.contains(document.activeElement);
|
|
2275
|
+
onDismiss();
|
|
2276
|
+
if (restoreFocus) focusWithoutRing(anchor);
|
|
2277
|
+
};
|
|
2278
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
2267
2279
|
const onPointerDown = (event) => {
|
|
2268
2280
|
if (event.target instanceof Node && menu.contains(event.target)) return;
|
|
2269
2281
|
onDismiss();
|
|
2270
2282
|
};
|
|
2271
2283
|
window.addEventListener("pointerdown", onPointerDown, true);
|
|
2272
2284
|
return () => {
|
|
2285
|
+
composition.dispose();
|
|
2286
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
2273
2287
|
window.removeEventListener("pointerdown", onPointerDown, true);
|
|
2274
2288
|
};
|
|
2275
|
-
}, [
|
|
2289
|
+
}, [
|
|
2290
|
+
anchor,
|
|
2291
|
+
onDismiss,
|
|
2292
|
+
hasItems
|
|
2293
|
+
]);
|
|
2276
2294
|
if (!hasItems) return null;
|
|
2277
|
-
return createPortal(jsxs(
|
|
2295
|
+
return createPortal(jsxs(MenuSurface, {
|
|
2278
2296
|
className: css.menu,
|
|
2279
2297
|
ref: self,
|
|
2280
2298
|
role: "menu",
|
|
@@ -2565,6 +2583,7 @@ function TabStrip({ state, pane, callbacks }) {
|
|
|
2565
2583
|
className: css.tabStrip,
|
|
2566
2584
|
role: "tablist",
|
|
2567
2585
|
"data-dockkit-strip": pane.id,
|
|
2586
|
+
"data-window-drag": state.floats.length === 0 ? true : void 0,
|
|
2568
2587
|
children: [
|
|
2569
2588
|
jsxs("div", {
|
|
2570
2589
|
ref: stripTabs,
|
|
@@ -2600,6 +2619,7 @@ function TabStrip({ state, pane, callbacks }) {
|
|
|
2600
2619
|
},
|
|
2601
2620
|
onKeyDown: (event) => {
|
|
2602
2621
|
if (event.target !== event.currentTarget) return;
|
|
2622
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.nativeEvent.isComposing) return;
|
|
2603
2623
|
const next = chipToFocus(event.key, pane.tabs, tabId);
|
|
2604
2624
|
if (next !== void 0) {
|
|
2605
2625
|
event.preventDefault();
|
|
@@ -2621,19 +2641,27 @@ function TabStrip({ state, pane, callbacks }) {
|
|
|
2621
2641
|
},
|
|
2622
2642
|
children: [
|
|
2623
2643
|
jsx(TabTitle, { children: callbacks.renderTabTitle?.(tab) ?? tab.title }),
|
|
2624
|
-
closable && jsx(
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
"
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
callbacks.
|
|
2635
|
-
|
|
2636
|
-
|
|
2644
|
+
closable && jsx(Tooltip, {
|
|
2645
|
+
disabled: menu !== void 0,
|
|
2646
|
+
label: callbacks.labels.closeTab,
|
|
2647
|
+
shortcutKeys: callbacks.labels.closeTabKeys,
|
|
2648
|
+
side: "bottom",
|
|
2649
|
+
delayMs: 500,
|
|
2650
|
+
children: jsx("button", {
|
|
2651
|
+
type: "button",
|
|
2652
|
+
className: css.tabClose,
|
|
2653
|
+
"aria-label": callbacks.labels.closeTab,
|
|
2654
|
+
"aria-keyshortcuts": callbacks.labels.closeTabShortcut,
|
|
2655
|
+
"data-dockkit-tab-close": tabId,
|
|
2656
|
+
onPointerDown: (event) => {
|
|
2657
|
+
event.stopPropagation();
|
|
2658
|
+
},
|
|
2659
|
+
onClick: (event) => {
|
|
2660
|
+
event.stopPropagation();
|
|
2661
|
+
callbacks.onCloseTab(tabId);
|
|
2662
|
+
},
|
|
2663
|
+
children: jsx(IconCloseFillRegular, { size: 14 })
|
|
2664
|
+
})
|
|
2637
2665
|
}),
|
|
2638
2666
|
menu?.tabId === tabId && jsx(TabMenu, {
|
|
2639
2667
|
labels: callbacks.labels,
|
|
@@ -2677,23 +2705,27 @@ function TabStrip({ state, pane, callbacks }) {
|
|
|
2677
2705
|
"data-dockkit-strip-fill": true
|
|
2678
2706
|
}),
|
|
2679
2707
|
!(callbacks.hideSplitWhenBlocked && block !== void 0) && jsx(Tooltip, {
|
|
2680
|
-
label: callbacks.labels.splitPane,
|
|
2708
|
+
label: block === void 0 ? callbacks.labels.splitPane : splitBlockedTitle(callbacks.labels, block),
|
|
2709
|
+
shortcutKeys: callbacks.labels.splitPaneKeys,
|
|
2681
2710
|
side: "bottom",
|
|
2682
2711
|
delayMs: 500,
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2712
|
+
children: jsx("span", {
|
|
2713
|
+
tabIndex: block === void 0 ? void 0 : 0,
|
|
2714
|
+
"aria-label": block === void 0 ? void 0 : splitBlockedTitle(callbacks.labels, block),
|
|
2715
|
+
children: jsx("button", {
|
|
2716
|
+
type: "button",
|
|
2717
|
+
className: css.iconButton,
|
|
2718
|
+
"aria-label": callbacks.labels.splitPane,
|
|
2719
|
+
"aria-keyshortcuts": callbacks.labels.splitPaneShortcut,
|
|
2720
|
+
disabled: block !== void 0,
|
|
2721
|
+
"data-dockkit-split-button": pane.id,
|
|
2722
|
+
"data-dockkit-split-blocked": block,
|
|
2723
|
+
onClick: (event) => {
|
|
2724
|
+
event.stopPropagation();
|
|
2725
|
+
callbacks.onSplitPane(pane.id);
|
|
2726
|
+
},
|
|
2727
|
+
children: jsx(SplitGlyph, {})
|
|
2728
|
+
})
|
|
2697
2729
|
})
|
|
2698
2730
|
}),
|
|
2699
2731
|
pane.id === callbacks.chromePaneId && callbacks.chrome !== void 0 && jsx("div", {
|
|
@@ -2735,6 +2767,7 @@ function TabPanel({ state, pane, callbacks }) {
|
|
|
2735
2767
|
return jsxs("section", {
|
|
2736
2768
|
className: css.pane,
|
|
2737
2769
|
"data-dockkit-pane": pane.id,
|
|
2770
|
+
tabIndex: -1,
|
|
2738
2771
|
"data-dockkit-pane-active": state.activePaneId === pane.id || void 0,
|
|
2739
2772
|
onClick: () => {
|
|
2740
2773
|
if (state.activePaneId !== pane.id) callbacks.onFocusPane(pane.id);
|
|
@@ -2909,13 +2942,14 @@ function FloatHeader({ paneId, tab, labels, intents, renderTabTitle, canCloseTab
|
|
|
2909
2942
|
})
|
|
2910
2943
|
}),
|
|
2911
2944
|
(canCloseTab?.(tab.id) ?? true) && jsx(Tooltip, {
|
|
2912
|
-
label: labels.
|
|
2945
|
+
label: labels.closeTab,
|
|
2946
|
+
shortcutKeys: labels.closeTabKeys,
|
|
2913
2947
|
side: "bottom",
|
|
2914
2948
|
delayMs: 500,
|
|
2915
2949
|
children: jsx("button", {
|
|
2916
2950
|
type: "button",
|
|
2917
2951
|
className: css.iconButton,
|
|
2918
|
-
"aria-label": labels.
|
|
2952
|
+
"aria-label": labels.closeTab,
|
|
2919
2953
|
"data-dockkit-float-close": paneId,
|
|
2920
2954
|
onPointerDown: (event) => {
|
|
2921
2955
|
event.stopPropagation();
|
|
@@ -2940,6 +2974,7 @@ function FloatLayer({ state, intents, labels, renderTab, renderTabTitle, canClos
|
|
|
2940
2974
|
return jsxs("div", {
|
|
2941
2975
|
className: css.float,
|
|
2942
2976
|
"data-dockkit-float": paneId,
|
|
2977
|
+
tabIndex: -1,
|
|
2943
2978
|
"data-dockkit-float-active": state.activePaneId === paneId || void 0,
|
|
2944
2979
|
style: {
|
|
2945
2980
|
left: live.x,
|
|
@@ -3038,6 +3073,7 @@ function TabHost({ state, callbacks, intents, tab, pane, column, floats, focusRe
|
|
|
3038
3073
|
},
|
|
3039
3074
|
children: jsxs("section", {
|
|
3040
3075
|
ref: host,
|
|
3076
|
+
tabIndex: -1,
|
|
3041
3077
|
className: clsx(css.tabHost, floating ? css.float : css.pane),
|
|
3042
3078
|
"aria-hidden": !visible || void 0,
|
|
3043
3079
|
"data-dockkit-content": tab.id,
|
|
@@ -18,12 +18,20 @@ export interface DockLabels {
|
|
|
18
18
|
readonly emptyPane: string;
|
|
19
19
|
/** The split control, while splitting is allowed. */
|
|
20
20
|
readonly splitPane: string;
|
|
21
|
+
/** ARIA combination from the effective binding, when bound. */
|
|
22
|
+
readonly splitPaneShortcut?: string | undefined;
|
|
23
|
+
/** Effective split keys shown as separate tooltip keycaps. */
|
|
24
|
+
readonly splitPaneKeys?: readonly string[] | undefined;
|
|
21
25
|
/** The split control, once the pane budget is spent. */
|
|
22
26
|
readonly splitPaneDisabled: string;
|
|
23
27
|
/** The split control, while the pane is too narrow for two working halves. */
|
|
24
28
|
readonly splitPaneNarrow: string;
|
|
25
29
|
/** Destroy a tab: the chip's close control and the menu's close item. */
|
|
26
30
|
readonly closeTab: string;
|
|
31
|
+
/** Effective close combination, when bound. */
|
|
32
|
+
readonly closeTabShortcut?: string | undefined;
|
|
33
|
+
/** Effective close keys shown as separate tooltip keycaps. */
|
|
34
|
+
readonly closeTabKeys?: readonly string[] | undefined;
|
|
27
35
|
/** The strip's add control, which seats the embedder's seeded tab. */
|
|
28
36
|
readonly addTab: string;
|
|
29
37
|
/** Send a floating panel back into the docked tree. */
|
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.7-
|
|
4
|
+
"version": "0.1.7-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"react-dom": "^18.2.0",
|
|
30
30
|
"react": "^18.2.0",
|
|
31
31
|
"clsx": "^2.0.0",
|
|
32
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-
|
|
33
|
-
"@deepseek-ai/
|
|
34
|
-
"@deepseek-ai/
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
|
|
33
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
34
|
+
"@deepseek-ai/dsh-brand": "0.1.7-rc.2"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"lib/index.js",
|