@deepseek-ai/dsh-client-ui-dockkit 0.1.5-alpha.1
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +107 -0
- package/README.zh.md +107 -0
- package/lib/components/dockkit.module.css +423 -0
- package/lib/index.js +2924 -0
- package/lib/types/components/DockSurface.d.ts +52 -0
- package/lib/types/components/FloatLayer.d.ts +15 -0
- package/lib/types/components/PaneTree.d.ts +18 -0
- package/lib/types/components/TabMenu.d.ts +16 -0
- package/lib/types/components/TabPanel.d.ts +12 -0
- package/lib/types/components/measure.d.ts +36 -0
- package/lib/types/components/pointer.d.ts +47 -0
- package/lib/types/components/render.d.ts +45 -0
- package/lib/types/contract/adapter.d.ts +90 -0
- package/lib/types/contract/types.d.ts +255 -0
- package/lib/types/engine/constraints.d.ts +61 -0
- package/lib/types/engine/controller.d.ts +191 -0
- package/lib/types/engine/geometry.d.ts +149 -0
- package/lib/types/engine/initial.d.ts +36 -0
- package/lib/types/engine/operations.d.ts +26 -0
- package/lib/types/engine/planner.d.ts +184 -0
- package/lib/types/engine/sequence.d.ts +131 -0
- package/lib/types/engine/tree.d.ts +180 -0
- package/lib/types/index.d.ts +38 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-dockkit/README.md
|
|
5
|
+
README.md: e012295852ad3c957695fcfbd0568a45b3f54856
|
|
6
|
+
README.zh.md: 2423887d60460651a224ffe1cb0fc9b61363c0e2
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Docking layout kit for the dsh web client: a split tree of tabbed panes with invertible operations, planners, a linear history, and the components that render and drive it."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-client-ui-dockkit
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
A docking layout kit: a split tree of tabbed panes with invertible operations, and the components that render and drive it. The Harness Web client is its first embedder; nothing in here knows that.
|
|
13
|
+
|
|
14
|
+
> **Internal engine.** This package is published because the Sidebar links it statically, not as a stable API: its exports — `LayoutState`, `LayoutOp`, the planners, `DockIntents`, `DockLabels`, `DockMode` — may change in any release, and none of them appears in a service interface (`ctx.sidebarRight` exposes operations, never layout snapshots or operation logs).
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [The two layers](#the-two-layers)
|
|
19
|
+
- [Embedding it](#embedding-it)
|
|
20
|
+
- [Interaction rules worth keeping](#interaction-rules-worth-keeping)
|
|
21
|
+
- [Build shape](#build-shape)
|
|
22
|
+
- [Model Experience](#model-experience)
|
|
23
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
24
|
+
- [Dev Note](#dev-note)
|
|
25
|
+
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
<a id="the-two-layers"></a>
|
|
29
|
+
## The two layers
|
|
30
|
+
|
|
31
|
+
**The engine** is pure logic — no UI framework, no DOM, no host concepts.
|
|
32
|
+
|
|
33
|
+
- A normalized recursive split tree: `nodes` keyed by id, `rootId` for the docked root, `floats` bottom-to-top. `PaneId`, `SplitId`, and `TabId` are branded strings: only a `Mint` (or the kit's own DOM round trip) produces one, so a pane, a split, and a tab never stand in for one another or for a bare string. A floating panel is not a second concept — it is a pane whose `host` is `'float'`, capacity one tab, drawn without a tab strip.
|
|
34
|
+
- `applyOp(state, op)` returns the next state **and the operations that undo it**. Inverses are captured when an operation runs, because by undo time the pre-operation state is gone.
|
|
35
|
+
- Every operation carries the ids it creates, so `replay(initial, ops)` reproduces the same tree. The engine reads no clock and no random source.
|
|
36
|
+
- `Sequencer` keeps a linear history with one entry per intent: the operations one gesture or command produced step back and forward together, a run of consecutive focus-only entries steps as one, and a new entry after stepping back drops the forward branch.
|
|
37
|
+
- `planSettle` is the opt-in rule that keeps every docked pane populated after an intent: panes an intent emptied are merged away, and an emptied root pane is reseeded through the embedder's factory. An embedder that wants empty panes simply does not call it.
|
|
38
|
+
- `DockController` is the intent layer and an observable source (`subscribe` + `getSnapshot`, whose reference only changes when the layout does).
|
|
39
|
+
|
|
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, 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
|
+
|
|
42
|
+
<a id="embedding-it"></a>
|
|
43
|
+
## Embedding it
|
|
44
|
+
|
|
45
|
+
Everything host-specific arrives through props:
|
|
46
|
+
|
|
47
|
+
| Contract | Carries |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `DockLabels` | every rendered string, already localized, accessible names included |
|
|
50
|
+
| `TabRenderer` | one tab's body (`renderTab`), and optionally what its chip or panel header shows as a title (`renderTabTitle`, falling back to the record's `title`); the embedder dispatches on `tab.kind` |
|
|
51
|
+
| `DockIntents` | the settled results of every gesture |
|
|
52
|
+
|
|
53
|
+
`DockController` satisfies `DockIntents` as written, so the simplest embedding hands the controller straight to `DockSurface`. An embedder that routes through its own store implements the same method names instead. Two props carry control policy rather than gestures: `canSplit` (surface-wide, the pane budget; disables the split control with `splitPaneDisabled`) and `canAddTab(paneId)` (per pane, omits the add control; leave it out to draw one in every pane). Hiding the add control moves nothing else in the strip. The kit adds one policy of its own, the room rule below, which disables a pane's split control with `splitPaneNarrow`; `onRoom(fits)` reports its readings so an embedder splitting programmatically can honour the same rule.
|
|
54
|
+
|
|
55
|
+
`dropZones="horizontal"` offers two half-pane hints; once budget or width forbids another split, the whole body accepts a move. `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. `hideSplitAtCapacity` hides the split control at the pane budget; its default is false, and a width-blocked control remains disabled.
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
`DockSurface` is the docked area. Chrome around it — a rail, a collapsed presentation, any history controls — belongs to the embedder, which reads `state.expanded` and decides; the kit ships no undo/redo control of its own. Surface-wide controls the embedder does want on the surface go through the `chrome` prop, which the kit places at the far end of the top-right pane's tab strip (the last child of every row split, the first of every column split), so a surface needs no header row of its own. `FloatLayer` owns its own gestures and positions panels in viewport coordinates, so it may be mounted anywhere, including a portal.
|
|
60
|
+
|
|
61
|
+
<a id="interaction-rules-worth-keeping"></a>
|
|
62
|
+
## Interaction rules worth keeping
|
|
63
|
+
|
|
64
|
+
These are not stylistic; each one fixes a defect found in a real browser.
|
|
65
|
+
|
|
66
|
+
- **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.
|
|
67
|
+
- **The chips give way; the strip's end controls never do.** The chip box is the strip's one shrinking part (`flex: 0 1 auto; min-width: 0; overflow: hidden`); the add, split, and chrome controls are `flex: none`, so they keep their width and place in any pane at least as wide as they are (about 130px with the chrome, 72px without). The surface's `min-width: 0` and the pane's `overflow: hidden` stop a body's longest unwrapped line from widening the pane past its box, which is what carried the controls and the body's scrollbar off-screen.
|
|
68
|
+
- **The chip box is not a scroll container.** A horizontal scroller claims press-and-move for itself; tabs shrink, ellipsize, and then clip instead.
|
|
69
|
+
- **A split needs room for two working halves.** A pane splits into equal halves, so each half must hold what cannot shrink: the strip's fixed part — measured as the strip's width minus the chip box and the fill, which is the padding, the gaps, and every control that pane draws (its own chrome included, so the top-right pane asks more) — plus one chip at its minimum — `.tab` declares `min-width: 44px` on a content-box, so its footprint is 44px plus 10px + 5px of padding, 59px, read from a rendered chip's computed style (the stylesheet figure when none can be read); the divider between the halves takes its rendered thickness (4px). A column split, which only an edge drop makes, needs each half to hold the strip (36px) plus a 48px body: one 13px secondary line at 1.6 line-height inside the body's 12px padding. `halvesFit` in `geometry.ts` is the arithmetic; `measure.ts` reads the rectangles after every commit and whenever the surface resizes, because the layout state carries fractions, never pixels, and the engine's planners stay that way. A pane without room keeps its split control, disabled with `splitPaneNarrow`, and offers no edge drop zone for that axis (the release is then not a move). A pane the user narrows afterwards — a divider or the embedder's column dragged — keeps its size: the rule only decides its next split.
|
|
70
|
+
- **Focus lands on click, not on press.** A state change between `pointerdown` and the first `pointermove` rebuilds the pressed subtree, and a replaced element cancels the pointer. It also keeps a drag from recording a redundant focus operation first. Clicks on the chips, the strip's controls, and the embedder's chrome stop at the strip: the intent each reports already decides the active pane, or is the embedder's own, so the pane's click-to-focus records nothing extra. A floating panel's grip and corner report through their gesture the same way — a press released in place is a click that raises the panel, and a drag records only the move or resize, whose operation raises it — while a press on the panel's body raises it directly. A click on the pane that is active already, a click or key on that pane's selected chip, or a press on the panel that is active and on top already, changes nothing and records nothing.
|
|
71
|
+
- **A control nested inside a draggable chip stops its own press.** Otherwise the press starts a drag, captures the pointer, and the nested control's click never lands.
|
|
72
|
+
- **Emphasis takes the platform's accent, never `--dsw-alias-brand-primary`.** This platform binds `brand-primary` to its near-black (light) or near-white (dark) foreground, so a hovered divider, the drop caret, and the drop-zone hint use `--dsw-alias-brand-primary-new-colorprimary-new-color`, as the trajectory views do. A floating panel's border is the same `--dsw-alias-border-l2` hairline whether it is active or not: the active panel is already on top and casts the shadow; a darker frame around it read as a defect.
|
|
73
|
+
|
|
74
|
+
<a id="build-shape"></a>
|
|
75
|
+
## Build shape
|
|
76
|
+
|
|
77
|
+
The package is statically linked: tsdown's `staticLinked` preset emits one browser ESM bundle at `lib/index.js` (every bare specifier stays an import, sourcemaps chain to the sources) and ships the stylesheet under `lib/` at its `src`-relative path, and the Web shell resolves the package name and bundles that artifact itself, so vite stays the only owner of class hashing. One consequence is load-bearing — the kit keeps **one** stylesheet, `dockkit.module.css`, because a consumer de-duplicates injected sheets by file name and a collision would drop one silently.
|
|
78
|
+
|
|
79
|
+
<a id="model-experience"></a>
|
|
80
|
+
## Model Experience
|
|
81
|
+
|
|
82
|
+
None, as the package is a browser-side docking layout engine and component set that registers nothing model-facing.
|
|
83
|
+
|
|
84
|
+
#### KV Cache effect
|
|
85
|
+
|
|
86
|
+
None; this package neither assembles nor sends a provider request.
|
|
87
|
+
|
|
88
|
+
## Known Limitations and Deferred Work
|
|
89
|
+
|
|
90
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
91
|
+
|
|
92
|
+
- **Size semantics are deliberately small**: fractional weights with one minimum-size clamp. No snap, priority, or preferred size, so the cascading-squeeze behaviour of a full splitview is absent.
|
|
93
|
+
- **Touch is untuned.** Gestures are pointer-based and `touch-action` is set where a scroller would otherwise interfere, but no touch-specific tuning has been done.
|
|
94
|
+
- **Accessibility is incomplete**: no `separator` role on dividers and no keyboard route to split, move, or float.
|
|
95
|
+
- **No published stylesheet contract.** Consumers get hashed module class names; the kit exposes no theming API beyond the `--dsw-*` custom properties it reads.
|
|
96
|
+
|
|
97
|
+
<a id="dev-note"></a>
|
|
98
|
+
### Dev Note
|
|
99
|
+
|
|
100
|
+
<details>
|
|
101
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
102
|
+
|
|
103
|
+
None.
|
|
104
|
+
|
|
105
|
+
</details>
|
|
106
|
+
|
|
107
|
+
**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
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "dsh Web 客户端的停靠布局套件:带可逆操作的标签格分裂树、planner、线性历史,以及渲染并驱动它的组件。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-client-ui-dockkit
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
一套停靠布局套件:由带可逆操作的标签格组成的分裂树,以及渲染并驱动它的组件。Harness Web 客户端是它的第一个嵌入方;这里的代码对此一无所知。
|
|
13
|
+
|
|
14
|
+
> **内部引擎。** 本包之所以发布,是因为 Sidebar 以静态链接方式使用它,而非作为稳定 API:它的导出——`LayoutState`、`LayoutOp`、各 planner、`DockIntents`、`DockLabels`、`DockMode`——在任何版本都可能变化,并且没有任何一个出现在服务接口里(`ctx.sidebarRight` 只暴露操作,从不暴露布局快照或操作日志)。
|
|
15
|
+
|
|
16
|
+
## 目录
|
|
17
|
+
|
|
18
|
+
- [两层结构](#the-two-layers)
|
|
19
|
+
- [如何嵌入](#embedding-it)
|
|
20
|
+
- [值得保留的交互规则](#interaction-rules-worth-keeping)
|
|
21
|
+
- [构建形态](#build-shape)
|
|
22
|
+
- [模型体验](#model-experience)
|
|
23
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
24
|
+
- [开发备注](#dev-note)
|
|
25
|
+
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
<a id="the-two-layers"></a>
|
|
29
|
+
## 两层结构
|
|
30
|
+
|
|
31
|
+
**引擎**是纯逻辑——没有 UI 框架、没有 DOM、没有宿主概念。
|
|
32
|
+
|
|
33
|
+
- 一棵归一化的递归分裂树:按 id 索引的 `nodes`、指向停靠根的 `rootId`、自底向上排列的 `floats`。`PaneId`、`SplitId`、`TabId` 是带 brand 的字符串:只有 `Mint`(或库自己的 DOM 往返)能产出,因此 pane、split、tab 三种 id 彼此不可互换,也不能拿裸字符串充数。浮窗不是第二个概念——它就是 `host` 为 `'float'` 的格,容量一个 tab,绘制时不带 tab 条。
|
|
34
|
+
- `applyOp(state, op)` 返回下一状态**以及撤销它的操作**。逆操作在操作执行时捕获,因为到撤销时操作前的状态已经不存在了。
|
|
35
|
+
- 每个操作都携带它创建的 id,因此 `replay(initial, ops)` 能复现同一棵树。引擎不读时钟,也不读随机源。
|
|
36
|
+
- `Sequencer` 维护一条线性历史,每个意图一条记录:一次手势或命令产生的操作一起后退、一起前进,连续的纯焦点记录作为一步,后退后的新记录会丢弃前进分支。
|
|
37
|
+
- `planSettle` 是可选加入的规则,保证意图之后每个停靠格都有内容:被意图清空的格会被并掉,被清空的根格通过嵌入方的工厂重新播种。想要空格的嵌入方只需不调用它。
|
|
38
|
+
- `DockController` 是意图层,也是一个可观察源(`subscribe` + `getSnapshot`,其引用只在布局变化时才变)。
|
|
39
|
+
|
|
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
|
+
|
|
42
|
+
<a id="embedding-it"></a>
|
|
43
|
+
## 如何嵌入
|
|
44
|
+
|
|
45
|
+
一切宿主相关的东西都通过 props 进入:
|
|
46
|
+
|
|
47
|
+
| 契约 | 承载内容 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `DockLabels` | 每一个渲染出来的字符串,已本地化,含无障碍名称 |
|
|
50
|
+
| `TabRenderer` | 一个 tab 的正文(`renderTab`),以及可选的 chip 或浮窗头部显示的标题(`renderTabTitle`,回退到记录的 `title`);嵌入方按 `tab.kind` 分发 |
|
|
51
|
+
| `DockIntents` | 每次手势落定的结果 |
|
|
52
|
+
|
|
53
|
+
`DockController` 原样满足 `DockIntents`,所以最简单的嵌入就是把 controller 直接交给 `DockSurface`。经由自己 store 路由的嵌入方则实现同名方法。有两个 props 承载的是控制策略而非手势:`canSplit`(整面有效,即格预算;用 `splitPaneDisabled` 禁用分栏控件)与 `canAddTab(paneId)`(按格,省略添加控件;不传则每格都画)。隐藏添加控件不会移动 tab 条里的其它任何东西。套件自己再加一条策略,即下文的空间规则,它用 `splitPaneNarrow` 禁用某格的分栏控件;`onRoom(fits)` 上报其读数,让以编程方式分栏的嵌入方能遵守同一规则。
|
|
54
|
+
|
|
55
|
+
`dropZones="horizontal"` 提供左右两个半区提示;预算或宽度不允许再拆时,正文整格接收移动。`minPaneFraction` 控制预览的最小比例,`planResizeSplit` 接受相同最小值以约束提交;Sidebar使用0.2并在自己的store限制两格。通用引擎仍保留原有树与其它分割方向。 `hideSplitAtCapacity` 在达到窗格预算时隐藏分栏控件,默认值为 false;宽度不足的控件仍以禁用状态显示。
|
|
56
|
+
|
|
57
|
+
tab 的 `kind` 是不透明字符串。种子 tab 是工厂(`DockControllerOptions`),因此新格里放什么由嵌入方决定,与本包无关。内容身份是二元组(`kind`、`contentId`):`findContentTab(state, contentId, kind?)` 在任意位置找到展示它的 tab,`findPaneContentTab(state, paneId, contentId, kind?)` 在一个格内找;`planOpenContent` 会聚焦该 tab 而非再开一个,除非被告知 `revealIfOpened: false`;显式的 `index` 把新 tab 放到 tab 条的某个位置而非末尾。
|
|
58
|
+
|
|
59
|
+
`DockSurface` 是停靠区。它周围的 chrome——轨道、折叠形态、任何历史控件——属于嵌入方,由嵌入方读取 `state.expanded` 后自行决定;套件不自带撤销/重做控件。嵌入方确实想放到面上的整面控件通过 `chrome` prop 传入,套件把它放在右上格 tab 条的最末端(每个横向分裂的最后一个子节点、每个纵向分裂的第一个子节点),因此停靠面不需要自己的标题行。`FloatLayer` 拥有自己的手势并以视口坐标定位浮窗,因此可以挂在任何位置,包括 portal 里。
|
|
60
|
+
|
|
61
|
+
<a id="interaction-rules-worth-keeping"></a>
|
|
62
|
+
## 值得保留的交互规则
|
|
63
|
+
|
|
64
|
+
这些不是风格偏好;每一条都修复了在真实浏览器里发现的缺陷。
|
|
65
|
+
|
|
66
|
+
- **手势开始时捕获指针。** 不捕获的话,指针经过的任何滚动容器都可能接管手势,浏览器会将其报告为指针取消和拖动中止。捕获是加固——无论如何都由 window 监听器承载手势,所以没有该 API 的环境照样可用。
|
|
67
|
+
- **chip 让位;tab 条末端的控件永不让位。** chip 盒是 tab 条里唯一会收缩的部分(`flex: 0 1 auto; min-width: 0; overflow: hidden`);添加、分栏与 chrome 控件都是 `flex: none`,因此在任何不窄于它们自身的格里(带 chrome 约 130px,不带约 72px)都保持宽度与位置。停靠面的 `min-width: 0` 与格的 `overflow: hidden` 阻止正文里最长的不换行行把格撑出自己的盒子——正是那种情况把控件和正文滚动条推到了屏幕外。
|
|
68
|
+
- **chip 盒不是滚动容器。** 横向滚动容器会把按下并移动据为己有;tab 转而收缩、省略、然后被裁切。
|
|
69
|
+
- **分栏需要给两个可用的半格留出空间。** 格被等分成两半,因此每一半都必须容得下不可收缩的部分:tab 条的固定部分——按 tab 条宽减去 chip 盒与填充条测得,即内边距、间隙以及该格绘制的每个控件(含它自己的 chrome,所以右上格要求更多)——加上一枚最小尺寸的 chip——`.tab` 在 content-box 上声明 `min-width: 44px`,所以它的足印是 44px 加 10px + 5px 内边距,即 59px,从已渲染 chip 的计算样式读取(读不到时用样式表数值);两半之间的分隔条取其渲染厚度(4px)。纵向分栏只由边缘落下产生,它要求每一半容得下 tab 条(36px)加 48px 正文:正文 12px 内边距内一行 13px、行高 1.6 的次级文字。`geometry.ts` 里的 `halvesFit` 是算术;`measure.ts` 在每次提交后与停靠面尺寸变化时读取矩形,因为布局状态只携带比例、从不携带像素,引擎的 planner 也保持如此。没有空间的格保留分栏控件,以 `splitPaneNarrow` 禁用,并且在该轴上不提供边缘落区(松手就不是移动)。用户随后把格拖窄——拖分隔条或拖嵌入方的列——的格保持原尺寸:规则只决定它的下一次分栏。
|
|
70
|
+
- **焦点落在 click 而不是按下。** 在 `pointerdown` 与第一次 `pointermove` 之间的状态变化会重建被按下的子树,而被替换的元素会取消指针。这也避免拖动先记录一条多余的焦点操作。chip、标签条各控件以及嵌入方 chrome 上的 click 都止于标签条:它们各自上报的意图已决定了活动格,或本就是嵌入方自己的事,所以格自身的点击聚焦不再多记一条。浮动面板的抓手与角柄同样通过手势上报——原地松开的按下是一次 click,抬起面板;真正的拖动只记录移动或缩放,由该操作自己抬起面板——而按在面板主体上则直接抬起它。点击本已活动的格、点击或按键选中该格本已选中的 chip,或按下本已活动且在最上层的面板,什么都不改变,也什么都不记录。
|
|
71
|
+
- **嵌套在可拖动 chip 里的控件要拦住自己的按下。** 否则按下会开始拖动、捕获指针,嵌套控件的 click 就永远落不下。
|
|
72
|
+
- **强调色用平台的强调 token,绝不用 `--dsw-alias-brand-primary`。** 本平台把 `brand-primary` 绑定到近黑(浅色)或近白(深色)的前景色,因此悬停的分隔条、落点光标与落区提示都用 `--dsw-alias-brand-primary-new-colorprimary-new-color`,与轨迹视图一致。浮窗的边框无论是否活动都是同一条 `--dsw-alias-border-l2` 细线:活动浮窗本就在最上层并投下阴影;围它一圈更深的边框读起来像缺陷。
|
|
73
|
+
|
|
74
|
+
<a id="build-shape"></a>
|
|
75
|
+
## 构建形态
|
|
76
|
+
|
|
77
|
+
本包静态链接:tsdown 的 `staticLinked` 预设在 `lib/index.js` 产出一个浏览器 ESM bundle(所有裸说明符保持为 import,sourcemap 链回源码),并把样式表按其相对 `src` 的路径放到 `lib/` 下;Web 外壳按包名解析并自行打包该产物,因此 vite 仍是 class 哈希的唯一拥有者。有一个后果是承重的——套件只保留**一张**样式表 `dockkit.module.css`,因为消费方按文件名去重注入的样式表,撞名会静默丢掉一张。
|
|
78
|
+
|
|
79
|
+
<a id="model-experience"></a>
|
|
80
|
+
## 模型体验
|
|
81
|
+
|
|
82
|
+
None, as the package is a browser-side docking layout engine and component set that registers nothing model-facing.
|
|
83
|
+
|
|
84
|
+
#### KV Cache effect
|
|
85
|
+
|
|
86
|
+
None; this package neither assembles nor sends a provider request.
|
|
87
|
+
|
|
88
|
+
## 已知限制与延期工作
|
|
89
|
+
|
|
90
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
91
|
+
|
|
92
|
+
- **尺寸语义刻意保持精简**:比例权重加一处最小尺寸夹取。没有吸附、优先级或首选尺寸,因此完整 splitview 的级联挤压行为不存在。
|
|
93
|
+
- **触控未调优。** 手势基于 pointer 事件,并在滚动容器可能干扰处设置了 `touch-action`,但没有做过触控专项调优。
|
|
94
|
+
- **无障碍不完整**:分隔条没有 `separator` 角色,也没有键盘路径去分栏、移动或浮出。
|
|
95
|
+
- **没有发布样式表契约。** 消费方拿到的是哈希化的模块类名;套件除读取的 `--dsw-*` 自定义属性外不暴露任何主题 API。
|
|
96
|
+
|
|
97
|
+
<a id="dev-note"></a>
|
|
98
|
+
### 开发备注
|
|
99
|
+
|
|
100
|
+
<details>
|
|
101
|
+
<summary>维护者工作上下文——点击展开</summary>
|
|
102
|
+
|
|
103
|
+
无。
|
|
104
|
+
|
|
105
|
+
</details>
|
|
106
|
+
|
|
107
|
+
**运行时不变量:** 不发布 companion。引擎是作用于纯数据的纯函数,组件只上报意图;操作序列的可逆性与 settle 规则由本包的引擎 spec 直接断言,不提供也不观察任何 cordis 服务。
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Docking-kit styles. One sheet on purpose: a consumer bundle de-duplicates
|
|
3
|
+
* injected stylesheets by `<pluginId>/<basename>`, so a second sheet whose file
|
|
4
|
+
* name matches one in the embedding package would be dropped silently. Keeping
|
|
5
|
+
* a single `dockkit.module.css` makes that collision impossible.
|
|
6
|
+
*
|
|
7
|
+
* Colours come from the embedder's token layer; the kit names no literal. Type
|
|
8
|
+
* follows the embedder's content axis (`--dsh-content-font-size` and its
|
|
9
|
+
* secondary step) so a surface reads at the same size as the page around it.
|
|
10
|
+
* Emphasis — a hovered divider, the drop caret, the drop-zone hint — takes the
|
|
11
|
+
* platform's accent (`--dsw-alias-brand-primary-new-colorprimary-new-color`),
|
|
12
|
+
* not `--dsw-alias-brand-primary`, which this platform binds to its
|
|
13
|
+
* near-black (light) or near-white (dark) foreground.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
.split {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex: 1 1 auto;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
min-height: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.splitRow {
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.splitColumn {
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.splitCell {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-basis: 0;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.divider {
|
|
39
|
+
position: relative;
|
|
40
|
+
flex: none;
|
|
41
|
+
background: var(--dsw-alias-border-l1);
|
|
42
|
+
touch-action: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.splitRow > .divider {
|
|
46
|
+
width: 4px;
|
|
47
|
+
cursor: col-resize;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.splitColumn > .divider {
|
|
51
|
+
height: 4px;
|
|
52
|
+
cursor: row-resize;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.divider:hover {
|
|
56
|
+
background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Both floors: a flex item's minimum is its content's, and a body's longest
|
|
60
|
+
unwrapped line would widen the surface past the embedder's box, carrying the
|
|
61
|
+
strip's controls and the body's scrollbar out of view. */
|
|
62
|
+
.surface {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex: 1 1 auto;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
min-height: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* A body that overflows scrolls inside its pane; nothing escapes the pane. */
|
|
70
|
+
.pane {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex: 1 1 auto;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
min-width: 0;
|
|
75
|
+
min-height: 0;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
border: 1px solid transparent;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.pane[data-dockkit-pane-active] {
|
|
81
|
+
border-color: var(--dsw-alias-border-l2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* One centre line for everything in the strip: every child — chip, add
|
|
85
|
+
control, split control, the embedder's chrome — is 24px tall, and the strip
|
|
86
|
+
centres them, so chip text and control glyphs never sit at different heights.
|
|
87
|
+
A child with another height would break that; keep them at 24px.
|
|
88
|
+
|
|
89
|
+
The strip never clips: the chip box below is its one shrinking part, and
|
|
90
|
+
every control after it is `flex: none`, so a narrow pane costs chips, never
|
|
91
|
+
controls. */
|
|
92
|
+
.tabStrip {
|
|
93
|
+
display: flex;
|
|
94
|
+
flex: none;
|
|
95
|
+
gap: 4px;
|
|
96
|
+
align-items: center;
|
|
97
|
+
height: 36px;
|
|
98
|
+
padding: 0 6px;
|
|
99
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l1);
|
|
100
|
+
touch-action: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* The chips. Shrinks to nothing before any control after it moves; what no
|
|
104
|
+
longer fits is clipped here. Deliberately not a scroller: a horizontal
|
|
105
|
+
scroll container claims a press-and-move as its own gesture and cancels the
|
|
106
|
+
pointer, which would abandon every tab drag in a narrow pane. Tabs shrink
|
|
107
|
+
and ellipsize first. */
|
|
108
|
+
.stripTabs {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex: 0 1 auto;
|
|
111
|
+
gap: 4px;
|
|
112
|
+
align-items: center;
|
|
113
|
+
min-width: 0;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
touch-action: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Takes the free space and gives it all back first: a zero basis shrinks
|
|
119
|
+
nothing, so shortage lands on the chip box alone. */
|
|
120
|
+
.stripFill {
|
|
121
|
+
flex: 1 1 0;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Embedder controls at the strip's end, set off from the kit's own split
|
|
126
|
+
control by a hairline so the two groups read as two groups. */
|
|
127
|
+
.stripChrome {
|
|
128
|
+
display: flex;
|
|
129
|
+
flex: none;
|
|
130
|
+
gap: 2px;
|
|
131
|
+
align-items: center;
|
|
132
|
+
height: 24px;
|
|
133
|
+
margin-left: 2px;
|
|
134
|
+
padding-left: 4px;
|
|
135
|
+
border-left: 0.5px solid var(--dsw-alias-border-l1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.caret {
|
|
139
|
+
flex: none;
|
|
140
|
+
align-self: center;
|
|
141
|
+
width: 2px;
|
|
142
|
+
height: 20px;
|
|
143
|
+
background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.tab {
|
|
147
|
+
position: relative;
|
|
148
|
+
display: flex;
|
|
149
|
+
flex: 0 1 auto;
|
|
150
|
+
gap: 4px;
|
|
151
|
+
align-items: center;
|
|
152
|
+
min-width: 44px;
|
|
153
|
+
max-width: 170px;
|
|
154
|
+
height: 24px;
|
|
155
|
+
padding: 0 5px 0 10px;
|
|
156
|
+
color: var(--dsw-alias-label-secondary);
|
|
157
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
158
|
+
line-height: 1;
|
|
159
|
+
white-space: nowrap;
|
|
160
|
+
border-radius: 12px;
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
touch-action: none;
|
|
163
|
+
user-select: none;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.tabTitle {
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
text-overflow: ellipsis;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.tabClose {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex: none;
|
|
174
|
+
align-items: center;
|
|
175
|
+
justify-content: center;
|
|
176
|
+
width: 16px;
|
|
177
|
+
height: 16px;
|
|
178
|
+
padding: 0;
|
|
179
|
+
color: inherit;
|
|
180
|
+
line-height: 1;
|
|
181
|
+
background: transparent;
|
|
182
|
+
border: none;
|
|
183
|
+
border-radius: 50%;
|
|
184
|
+
corner-shape: round;
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.addTab {
|
|
189
|
+
display: flex;
|
|
190
|
+
flex: none;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
width: 24px;
|
|
194
|
+
height: 24px;
|
|
195
|
+
padding: 0;
|
|
196
|
+
color: var(--dsw-alias-label-secondary);
|
|
197
|
+
line-height: 1;
|
|
198
|
+
background: transparent;
|
|
199
|
+
border: none;
|
|
200
|
+
border-radius: 12px;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.addTab:hover {
|
|
205
|
+
color: var(--dsw-alias-label-primary);
|
|
206
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.tabClose:hover {
|
|
210
|
+
color: var(--dsw-alias-label-primary);
|
|
211
|
+
background: var(--dsw-alias-interactive-bg-hover-solid);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.tab:hover {
|
|
215
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* The active chip is the filled capsule; the rest are bare text. */
|
|
219
|
+
.tabActive {
|
|
220
|
+
color: var(--dsw-alias-label-primary);
|
|
221
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.tabDragging {
|
|
225
|
+
opacity: 0.5;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.iconButton {
|
|
229
|
+
display: flex;
|
|
230
|
+
flex: none;
|
|
231
|
+
align-items: center;
|
|
232
|
+
justify-content: center;
|
|
233
|
+
width: 24px;
|
|
234
|
+
height: 24px;
|
|
235
|
+
padding: 0;
|
|
236
|
+
color: var(--dsw-alias-label-secondary);
|
|
237
|
+
line-height: 1;
|
|
238
|
+
background: transparent;
|
|
239
|
+
border: none;
|
|
240
|
+
border-radius: 4px;
|
|
241
|
+
cursor: pointer;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.iconButton:hover:not(:disabled) {
|
|
245
|
+
color: var(--dsw-alias-label-primary);
|
|
246
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.iconButton:disabled {
|
|
250
|
+
color: var(--dsw-alias-label-tertiary);
|
|
251
|
+
cursor: default;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
* Portalled beside the app root, so its stacking level is stated here: above the
|
|
256
|
+
* floating panels (which the embedder draws at 60), because a menu opened from
|
|
257
|
+
* a tab must never sit under a panel. No z-index token layer exists to draw from
|
|
258
|
+
* yet.
|
|
259
|
+
*/
|
|
260
|
+
.menu {
|
|
261
|
+
position: fixed;
|
|
262
|
+
z-index: 70;
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-direction: column;
|
|
265
|
+
min-width: 96px;
|
|
266
|
+
padding: 4px;
|
|
267
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
268
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
269
|
+
border-radius: 6px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.menuItem {
|
|
273
|
+
padding: 5px 8px;
|
|
274
|
+
color: var(--dsw-alias-label-primary);
|
|
275
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
276
|
+
text-align: left;
|
|
277
|
+
background: transparent;
|
|
278
|
+
border: none;
|
|
279
|
+
border-radius: 4px;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.menuItem:hover {
|
|
284
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.paneBody {
|
|
288
|
+
position: relative;
|
|
289
|
+
flex: 1 1 auto;
|
|
290
|
+
min-width: 0;
|
|
291
|
+
min-height: 0;
|
|
292
|
+
padding: 12px;
|
|
293
|
+
overflow: auto; /* This sheet draws elevated surfaces (the strip and the menu), so a scroller
|
|
294
|
+
inside it rebinds the thumb indirection in a complete pair — a base-surface
|
|
295
|
+
thumb on an elevated ground reads as a smudge. */
|
|
296
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
297
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.empty {
|
|
301
|
+
margin: 0;
|
|
302
|
+
color: var(--dsw-alias-label-tertiary);
|
|
303
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.dockHint {
|
|
307
|
+
position: absolute;
|
|
308
|
+
background: var(--dsw-alias-bg-multi-select);
|
|
309
|
+
border: 1px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color);
|
|
310
|
+
pointer-events: none;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.dockHint[data-dockkit-dock-zone='center'] {
|
|
314
|
+
inset: 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.dockHint[data-dockkit-dock-zone='left'] {
|
|
318
|
+
top: 0;
|
|
319
|
+
bottom: 0;
|
|
320
|
+
left: 0;
|
|
321
|
+
width: 40%;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.dockHint[data-dockkit-dock-zone='right'] {
|
|
325
|
+
top: 0;
|
|
326
|
+
right: 0;
|
|
327
|
+
bottom: 0;
|
|
328
|
+
width: 40%;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
[data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='left'],
|
|
332
|
+
[data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='right'] {
|
|
333
|
+
width: 50%;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
[data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='left']:not([data-dockkit-drop-active]),
|
|
337
|
+
[data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='right']:not([data-dockkit-drop-active]) {
|
|
338
|
+
background: transparent;
|
|
339
|
+
border-color: var(--dsw-alias-border-l2);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.dockHint[data-dockkit-dock-zone='top'] {
|
|
343
|
+
top: 0;
|
|
344
|
+
right: 0;
|
|
345
|
+
left: 0;
|
|
346
|
+
height: 40%;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.dockHint[data-dockkit-dock-zone='bottom'] {
|
|
350
|
+
right: 0;
|
|
351
|
+
bottom: 0;
|
|
352
|
+
left: 0;
|
|
353
|
+
height: 40%;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.float {
|
|
357
|
+
position: fixed;
|
|
358
|
+
display: flex;
|
|
359
|
+
flex-direction: column;
|
|
360
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
361
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
362
|
+
border-radius: 8px;
|
|
363
|
+
box-shadow: 0 8px 24px var(--dsw-alias-bg-mask-drop);
|
|
364
|
+
pointer-events: auto;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* The active panel keeps the same hairline: it is already on top of the z
|
|
368
|
+
order and casts the same shadow, and a heavier or darker frame read as a
|
|
369
|
+
defect. `data-dockkit-float-active` stays on the element for tests. */
|
|
370
|
+
|
|
371
|
+
.floatHeader {
|
|
372
|
+
display: flex;
|
|
373
|
+
flex: none;
|
|
374
|
+
gap: 2px;
|
|
375
|
+
align-items: center;
|
|
376
|
+
height: 28px;
|
|
377
|
+
padding: 0 4px 0 10px;
|
|
378
|
+
border-bottom: 0.5px solid var(--dsw-alias-border-l1);
|
|
379
|
+
cursor: move;
|
|
380
|
+
touch-action: none;
|
|
381
|
+
user-select: none;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.floatTitle {
|
|
385
|
+
flex: 1 1 auto;
|
|
386
|
+
overflow: hidden;
|
|
387
|
+
color: var(--dsw-alias-label-primary);
|
|
388
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
389
|
+
white-space: nowrap;
|
|
390
|
+
text-overflow: ellipsis;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.floatBody {
|
|
394
|
+
flex: 1 1 auto;
|
|
395
|
+
min-height: 0;
|
|
396
|
+
padding: 10px;
|
|
397
|
+
overflow: auto; /* This sheet draws elevated surfaces (the strip and the menu), so a scroller
|
|
398
|
+
inside it rebinds the thumb indirection in a complete pair — a base-surface
|
|
399
|
+
thumb on an elevated ground reads as a smudge. */
|
|
400
|
+
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
|
401
|
+
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.floatResize {
|
|
405
|
+
position: absolute;
|
|
406
|
+
right: 0;
|
|
407
|
+
bottom: 0;
|
|
408
|
+
width: 14px;
|
|
409
|
+
height: 14px;
|
|
410
|
+
cursor: nwse-resize;
|
|
411
|
+
touch-action: none;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.floatResize::after {
|
|
415
|
+
position: absolute;
|
|
416
|
+
right: 3px;
|
|
417
|
+
bottom: 3px;
|
|
418
|
+
width: 6px;
|
|
419
|
+
height: 6px;
|
|
420
|
+
border-right: 2px solid var(--dsw-alias-label-tertiary);
|
|
421
|
+
border-bottom: 2px solid var(--dsw-alias-label-tertiary);
|
|
422
|
+
content: '';
|
|
423
|
+
}
|