@deepseek-ai/dsh-client-ui-layout 0.1.6-alpha.1 → 0.1.7-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/README.i18n.yaml +2 -2
- package/README.md +8 -2
- package/README.zh.md +8 -2
- package/lib/client.js +99 -9
- package/lib/types/client/AppFrame.d.ts +1 -1
- package/lib/types/client/columns.d.ts +3 -1
- package/lib/types/client/index.d.ts +19 -2
- package/lib/types/client/theme-presenter.d.ts +14 -2
- package/package.json +10 -10
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-layout/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: b26b182f5ea2c727411910cc5f85819b9dfbcb2c
|
|
6
|
+
README.zh.md: 58ef066ab05389898dc912500cd6b37f1a07824d
|
package/README.md
CHANGED
|
@@ -29,6 +29,12 @@ The root slot composes the sidebar, main content, and right column. The sidebar
|
|
|
29
29
|
|
|
30
30
|
Global panels occupy the root-scoped `main` keyed slot; `conversation` is the reserved key for the Conversation. `ctx.layout.selectPanel(id)` selects a registered panel, and `null` selects the Conversation without changing the current Session. No global panel is registered by the shipped composition.
|
|
31
31
|
|
|
32
|
+
### Window-chrome seat
|
|
33
|
+
|
|
34
|
+
On macOS desktop (`html[data-platform='darwin']`, set only by the desktop preload) a collapsed sidebar hides its column entirely instead of keeping the rail, and the frame mounts the single root-scoped `shell.leading` seat at its top-left — beside the hiddenInset traffic lights, over every main panel; ui-sidebar occupies it with the reopen and New Session controls. While the seat is mounted the frame publishes `--dsh-frame-leading-clearance`, the inline band the window chrome occupies measured from the frame's left edge; a main panel whose content reaches the top-left corner pads by it so nothing lands under the lights or the controls. The frame also always publishes `--dsh-frame-top-clearance` (48px) on the root element, the constant step below the window's top strip; entry pages in the main panel (plugin manager and similar, not the conversation) pad their top by it, and overlay primitives (portal menus, bottom-anchored overlays, the settings panel) keep it as their top viewport margin — the root-element home lets overlays portalled to `document.body` read it too. The full-width window drag band is 52px; while the Conversation is selected and its header shows the view tab strip, the band deepens to the 76px header block (title row plus tab strip), so blank header space drags while header controls stay clickable.
|
|
35
|
+
|
|
36
|
+
Windows Electron's `data-windows-titlebar` marker reserves the caption height above all columns and removes the collapsed sidebar rail. Only the content area's top-left corner has a 16px radius; the other corners and the internal divider remain square. The frame publishes `--dsh-windows-content-radius` and `--dsh-windows-sidebar-width` for ui-sidebar-right's fullscreen corner and sidebar clearance. Ordinary Web documents do not receive the marker; macOS retains its separate layout.
|
|
37
|
+
|
|
32
38
|
### Theme presentation
|
|
33
39
|
|
|
34
40
|
The presenter consumes resolved theme snapshots and projects them onto the document: `html { color-scheme }` for native UA chrome, `body[data-ds-dark-theme]` from the active color scheme, the theme's alias tokens and `--dsh-content-font-size` as inline variables on body, and one owned `<meta name="theme-color">` whose content follows the computed body background. Disposing the presenter removes its metadata node with its other global writes.
|
|
@@ -43,7 +49,7 @@ The presenter consumes resolved theme snapshots and projects them onto the docum
|
|
|
43
49
|
|
|
44
50
|
`selectPanel(id)` checks the live `main` registry before changing selection; an absent key throws and leaves the current panel intact. `beginNavigation()` returns an abort signal for an asynchronous UI navigation. A later call, a valid panel selection (including repeated selection), or layout disposal aborts that signal without cancelling underlying Session creation. Consumers check the signal before committing navigation or moving drafts.
|
|
45
51
|
|
|
46
|
-
One registration declares
|
|
52
|
+
One registration declares five child slots and binds `ctx.layout` methods `selectPanel`, `toggleSidebar`, `openRightbar(track, fullscreen)`, and `closeRightbar`. One root store separates `panelInfo` selection from `layoutInfo` measurements, width preferences, and presentation reports. `usePanelInfo` subscribes to the stable selection object; AppFrame subscribes to the stable layout object. The `rightbar` owner supplies actual `width`, `viewportWidth`, and normal-presentation eligibility `canShow`; insufficient room causes a deterministic close, never automatic reopening on widening. Fullscreen hides the width handle without releasing a track the occupant retains. AppFrame keeps the column containers mounted. The right column's root controller renders `rightbar.session` through `SessionProvider` only while the Conversation is selected; its unmount report releases the track. The independent title component uses the selected Session title only while the Conversation is visible, with the build-configured product title or localized `common.brand.localBuild` as its fallback; locale revisions update that fallback. The theme presenter is a second effect: pure DOM writes from resolved snapshots — initial state through the getter once, then event-driven only, with no React path. It applies palette, font-size, and token variables before measuring the rendered background as the single color authority. Fullscreen presentation suppresses grid and handle transitions; its occupant reports the new columns only after covering the frame. Fullscreen exit keeps transitions suppressed while the frame installs its destination geometry: close removes the right track, and restore retains it. Subsequent normal geometry actions restore ordinary transitions.
|
|
47
53
|
|
|
48
54
|
</details>
|
|
49
55
|
|
|
@@ -80,7 +86,7 @@ These limits define the current layout behavior. They are current package constr
|
|
|
80
86
|
|
|
81
87
|
- **Panel geometry is transient** — reload restores the sidebar default and the right panel hidden; each dragged width is one frame-wide preference, not a per-Session fact.
|
|
82
88
|
- **Extremely narrow windows** — after the right panel closes, the center may still fall below 400px; the left 56px rail remains.
|
|
83
|
-
- **Track and panel travel on one shared curve** — the frame
|
|
89
|
+
- **Track and panel travel on one shared curve only while animating** — during a discrete open/close the frame sets `data-animating` and its track transition and the occupant's slide read the same duration and easing variables; an occupant that used its own would detach the panel's edge from the conversation's while squeezing. Drags and instant presentation switches run transition-free, so the curve does not cover them.
|
|
84
90
|
- **No scroll anchoring during squeeze reflow** — layout changes may move the reader's viewport.
|
|
85
91
|
|
|
86
92
|
<a id="dev-note"></a>
|
package/README.zh.md
CHANGED
|
@@ -29,6 +29,12 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
全局面板占据 root 作用域的 `main` keyed slot;`conversation` 是为会话界面保留的 key。`ctx.layout.selectPanel(id)` 选中已注册面板,`null` 则选中会话界面,但不改变当前会话。默认组合不注册任何全局面板。
|
|
31
31
|
|
|
32
|
+
### 窗口 chrome 座
|
|
33
|
+
|
|
34
|
+
在 macOS 桌面(`html[data-platform='darwin']`,仅由桌面 preload 设置)下,收起的侧边栏整列隐藏而非保留控制栏,框架在左上角挂载 root 作用域的单一 `shell.leading` 座——位于 hiddenInset 红绿灯旁,覆盖所有主面板;ui-sidebar 以重新打开与 New Session 控件占据该座。座挂载期间框架发布 `--dsh-frame-leading-clearance`:窗口 chrome 占据的行内带宽,自框架左边缘起量;内容抵达左上角的主面板以它做内边距,避免落在红绿灯或控件之下。框架还始终在根元素上发布 `--dsh-frame-top-clearance`(48px):窗口顶带之下的固定下沉量;主面板中的入口型页面(插件管理器等类似页面,非对话)以它做顶部内边距,浮层原语(portal 菜单、底部锚定浮层、设置面板)以它做视口顶部安全边距——放在根元素上让 portal 到 document.body 的浮层也能读到。全宽窗口拖拽带为 52px;选中会话界面且其 header 显示视图 tab 条时,加深至 76px 的 header 块(标题行加 tab 条),header 空白处可拖拽,控件保持可点。
|
|
35
|
+
|
|
36
|
+
Windows Electron 的 `data-windows-titlebar` 标记在所有列上方预留顶栏高度,并移除收起后的侧栏轨道。内容区仅左上角保留 16px 圆角,其余角和内部交界处保持直角。框架发布 `--dsh-windows-content-radius` 和 `--dsh-windows-sidebar-width`,供 ui-sidebar-right 的全屏圆角及侧栏避让使用。普通 Web 文档不会获得该标记;macOS 保留其独立布局。
|
|
37
|
+
|
|
32
38
|
### 主题呈现
|
|
33
39
|
|
|
34
40
|
呈现器消费解析后的主题快照,并投影到 document:`html { color-scheme }` 驱动原生 UA 控件,依据当前配色方案设置 `body[data-ds-dark-theme]`,把主题的别名 token 与 `--dsh-content-font-size` 设为 body 上的内联变量,并持有一个 `<meta name="theme-color">`,其内容随计算后的 body 背景色更新。对呈现器执行 dispose(资源释放)时,它会连同其他全局写入一起移除自己的元数据节点。
|
|
@@ -43,7 +49,7 @@ kind: "package-reference"
|
|
|
43
49
|
|
|
44
50
|
`selectPanel(id)` 在改变选中态前检查实时 `main` 注册表;缺失的 key 会抛错并保留当前面板。`beginNavigation()` 为异步 UI 导航返回 abort signal。后续调用、有效面板选择(包括重复选择)或布局释放会中止该 signal,但不取消底层会话创建。消费方在提交导航或搬移草稿前检查 signal。
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
一次注册声明五个子 slot,并绑定 `ctx.layout` 的 `selectPanel`、`toggleSidebar`、`openRightbar(track, fullscreen)` 与 `closeRightbar`。同一个 root 存储把 `panelInfo` 选中态与 `layoutInfo` 测量、宽度偏好、呈现报告分开。`usePanelInfo` 订阅引用稳定的选中态对象,AppFrame 订阅引用稳定的布局对象。`rightbar` owner 提供实际 `width`、`viewportWidth`,以及表示能否以普通模式呈现的 `canShow`;占用方在空间不足时执行确定性的收起,变宽不自行重新展开。全屏隐藏宽度手柄,但不自行释放占用方要求保留的轨道。AppFrame 保持各列容器挂载。右栏的 root 控制器仅在选中会话界面时,经 `SessionProvider` 渲染 `rightbar.session`;内容卸载时的报告释放轨道。独立的标题组件仅在会话界面可见时使用所选会话标题,以构建配置的产品标题或本地化 `common.brand.localBuild` 为回退值;语言变化会更新该回退值。主题呈现器是第二个 effect:从解析后的快照做纯 DOM 写入——初始状态经 getter 读取一次,此后仅事件驱动,不经过 React。它先应用调色板、字号与 token 变量,再把渲染出的背景测量为唯一的颜色依据。全屏呈现禁用网格和手柄过渡;占用方完全覆盖框架后才报告新的列布局。退出全屏时,框架先保持无过渡并安装目标布局:关闭移除右轨道,恢复保留右轨道。后续普通几何操作恢复正常过渡。
|
|
47
53
|
|
|
48
54
|
</details>
|
|
49
55
|
|
|
@@ -80,7 +86,7 @@ kind: "package-reference"
|
|
|
80
86
|
|
|
81
87
|
- **面板几何是瞬时状态**——重新加载会恢复侧栏默认值并隐藏右侧面板;拖动设置的宽度是整个框架共用的一份偏好,而非每个会话各自的属性。
|
|
82
88
|
- **极窄窗口**——右侧面板关闭后,中栏仍可能小于 400px;左侧 56px 控制栏仍会保留。
|
|
83
|
-
-
|
|
89
|
+
- **轨道与面板仅在动画期间沿同一条曲线运动**——离散开合时框架设置 `data-animating`,其轨道过渡和占用方的滑入读取同一组时长与缓动变量;占用方若自用一套,挤压时面板边缘就会与会话界面的边缘脱开。拖拽和即时呈现切换不带过渡,因此该曲线不覆盖它们。
|
|
84
90
|
- **挤压重排期间无滚动锚定**——布局变化可能移动读者的视口。
|
|
85
91
|
|
|
86
92
|
<a id="dev-note"></a>
|
package/lib/client.js
CHANGED
|
@@ -30,11 +30,13 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
* @param viewport - available frame width in px.
|
|
31
31
|
* @param sidebar - sidebar width preference in px (0 = closed).
|
|
32
32
|
* @param rightbar - requested right panel width in px (0 = no track).
|
|
33
|
+
* @param collapsedWidth - track width of the closed sidebar; the default keeps
|
|
34
|
+
* the icon rail, 0 hides the column entirely (macOS desktop).
|
|
33
35
|
* @returns actual widths after shrinking or removing the right track; only
|
|
34
36
|
* without that track may the center fall below its minimum, down to zero.
|
|
35
37
|
*/
|
|
36
|
-
function computeColumns(viewport, sidebar, rightbar) {
|
|
37
|
-
const s = sidebar === 0 ?
|
|
38
|
+
function computeColumns(viewport, sidebar, rightbar, collapsedWidth = 56) {
|
|
39
|
+
const s = sidebar === 0 ? collapsedWidth : clampWidth(sidebar, 264, 420);
|
|
38
40
|
const available = viewport - s - 400;
|
|
39
41
|
const r = rightbar === 0 || available < 300 ? 0 : Math.min(available, clampWidth(rightbar, 300, viewport * RIGHTBAR_MAX_RATIO));
|
|
40
42
|
return {
|
|
@@ -55,7 +57,7 @@ window.__ModuleLoader__.load({
|
|
|
55
57
|
function DocumentTitle({ useSessions, usePanelInfo, productTitle }) {
|
|
56
58
|
const showSessionTitle = usePanelInfo((info) => info.activePanelId === null);
|
|
57
59
|
const title = useSessions((state) => {
|
|
58
|
-
const current = state.
|
|
60
|
+
const current = Object.values(state.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
59
61
|
return !showSessionTitle || current === void 0 ? void 0 : state.byId[current]?.title;
|
|
60
62
|
});
|
|
61
63
|
(0, react.useEffect)(() => {
|
|
@@ -68,7 +70,7 @@ window.__ModuleLoader__.load({
|
|
|
68
70
|
}
|
|
69
71
|
//#endregion
|
|
70
72
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-layout/src/client/AppFrame.module.css.mjs
|
|
71
|
-
const css = ".pI_x6G_frame{background:var(--dsw-alias-bg-base);height:100%;transition:grid-template-columns var(--ds-transition-duration-slow) var(--ds-ease-in-out);grid-template-rows:
|
|
73
|
+
const css = ".pI_x6G_frame{background:var(--dsw-alias-bg-base);grid-template-rows:100%;height:100%;display:grid;position:relative;overflow:hidden}.pI_x6G_frame[data-animating]{transition:grid-template-columns var(--ds-transition-duration-slow) var(--ds-ease-in-out)}.pI_x6G_frame[data-dragging]{transition:none}[data-windows-titlebar] .pI_x6G_frame{--dsh-windows-content-radius:16px;box-sizing:border-box;padding-top:var(--dsh-windows-titlebar-height);background:var(--dsw-specific-sidebar-fill);grid-template-rows:minmax(0,1fr)}[data-windows-titlebar] .pI_x6G_centerCol{background:var(--dsw-alias-bg-base);border-radius:var(--dsh-windows-content-radius) 0 0 0;corner-shape:round}[data-windows-titlebar] .pI_x6G_frame:before{content:\"\";height:var(--dsh-windows-titlebar-height);background:var(--dsw-specific-sidebar-fill);-webkit-app-region:drag;position:absolute;inset:0 0 auto}[data-windows-titlebar] .pI_x6G_sidebarCol{border-right:none}[data-windows-titlebar] .pI_x6G_handle{top:var(--dsh-windows-titlebar-height)}@media (prefers-reduced-motion:reduce){.pI_x6G_frame[data-animating]{transition:none}}.pI_x6G_sidebarCol{background:var(--dsw-specific-sidebar-fill);border-right:.5px solid var(--dsw-alias-border-l3);min-width:0;overflow:hidden}.pI_x6G_centerCol{flex-direction:column;min-width:0;display:flex;overflow:hidden}[data-platform=darwin] .pI_x6G_frame{background:0 0}html[data-platform=darwin]{--dsh-frame-top-clearance:48px}[data-platform=darwin] .pI_x6G_sidebarCol{background:linear-gradient(to bottom, #7a9bf01a, #7a9bf000 35%, #8f89b800 68%, #8f89b817), color-mix(in srgb, color-mix(in srgb, var(--dsw-specific-sidebar-fill) 97%, #7a9bf0) 40%, transparent);border-right:none}[data-platform=darwin] [data-ds-dark-theme] .pI_x6G_sidebarCol{background:linear-gradient(to bottom, #7a9bf014, #7a9bf000 35%, #8f89b800 68%, #8f89b812), color-mix(in srgb, var(--dsw-specific-sidebar-fill) 50%, transparent)}@media (prefers-reduced-transparency:reduce){[data-platform=darwin] .pI_x6G_sidebarCol,[data-platform=darwin] [data-ds-dark-theme] .pI_x6G_sidebarCol{background:color-mix(in srgb, var(--dsw-specific-sidebar-fill) 90%, transparent)}}[data-platform=darwin] .pI_x6G_centerCol{background:var(--dsw-alias-bg-base);border-left:.5px solid var(--dsw-alias-border-l3)}[data-platform=darwin] .pI_x6G_rightbarCol{background:var(--dsw-alias-bg-base)}[data-platform=darwin] [data-sidebar-collapsed] .pI_x6G_centerCol{border-left:none}[data-platform=darwin] .pI_x6G_frame[data-sidebar-collapsed]{--dsh-frame-leading-clearance:160px}[data-platform=darwin][data-fullscreen] .pI_x6G_frame[data-sidebar-collapsed]{--dsh-frame-leading-clearance:84px}[data-platform=darwin][data-fullscreen] .pI_x6G_leadingSeat{left:12px}.pI_x6G_leadingBand{pointer-events:none;-webkit-app-region:drag;height:52px;position:absolute;top:0;left:0;right:0}.pI_x6G_frame[data-panel-conversation]:has([data-conversation-tabs]) .pI_x6G_leadingBand{height:76px}.pI_x6G_leadingSeat{z-index:15;-webkit-app-region:no-drag;align-items:center;display:flex;position:absolute;top:11px;left:88px}.pI_x6G_handle{cursor:col-resize;z-index:11;touch-action:none;width:8px;margin-left:-4px;position:absolute;top:0;bottom:0}.pI_x6G_frame[data-animating] .pI_x6G_handle{transition:left var(--ds-transition-duration-slow) var(--ds-ease-in-out)}.pI_x6G_frame[data-dragging] .pI_x6G_handle,.pI_x6G_frame[data-rightbar-fullscreen],.pI_x6G_frame[data-rightbar-fullscreen] .pI_x6G_handle,.pI_x6G_frame[data-rightbar-instant],.pI_x6G_frame[data-rightbar-instant] .pI_x6G_handle{transition:none}@media (prefers-reduced-motion:reduce){.pI_x6G_frame[data-animating] .pI_x6G_handle{transition:none}}.pI_x6G_rightbarCol{min-width:0;position:relative;overflow:visible}.pI_x6G_overlayLayer{z-index:20;pointer-events:none;position:absolute;inset:0}.pI_x6G_overlayLayer>*{pointer-events:auto}";
|
|
72
74
|
const tagId = "@deepseek-ai/dsh-client-ui-layout/AppFrame.module.css";
|
|
73
75
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
74
76
|
const tag = document.createElement("style");
|
|
@@ -81,6 +83,8 @@ window.__ModuleLoader__.load({
|
|
|
81
83
|
"centerCol": "pI_x6G_centerCol",
|
|
82
84
|
"frame": "pI_x6G_frame",
|
|
83
85
|
"handle": "pI_x6G_handle",
|
|
86
|
+
"leadingBand": "pI_x6G_leadingBand",
|
|
87
|
+
"leadingSeat": "pI_x6G_leadingSeat",
|
|
84
88
|
"overlayLayer": "pI_x6G_overlayLayer",
|
|
85
89
|
"rightbarCol": "pI_x6G_rightbarCol",
|
|
86
90
|
"sidebarCol": "pI_x6G_sidebarCol"
|
|
@@ -115,6 +119,23 @@ window.__ModuleLoader__.load({
|
|
|
115
119
|
return renderSlot("main", {}, { entryKey: usePanelInfo((info) => info.activePanelId) ?? "conversation" });
|
|
116
120
|
}
|
|
117
121
|
/**
|
|
122
|
+
* Marks the frame while the Conversation is selected — the deepened drag band
|
|
123
|
+
* (AppFrame.module.css) keys off the attribute. A DOM write from a child keeps
|
|
124
|
+
* the frame itself out of the panel subscription: selecting a panel must not
|
|
125
|
+
* re-render the columns.
|
|
126
|
+
*/
|
|
127
|
+
function ConversationMarker({ usePanelInfo, frameRef }) {
|
|
128
|
+
const conversationActive = usePanelInfo((info) => info.activePanelId === null);
|
|
129
|
+
(0, react.useLayoutEffect)(() => {
|
|
130
|
+
const frame = frameRef.current;
|
|
131
|
+
/* v8 ignore next -- the ref is attached by effect time: the marker renders inside the frame div. */
|
|
132
|
+
if (frame === null) return;
|
|
133
|
+
if (conversationActive) frame.setAttribute("data-panel-conversation", "");
|
|
134
|
+
else frame.removeAttribute("data-panel-conversation");
|
|
135
|
+
}, [conversationActive, frameRef]);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
118
139
|
* Right column grid item. Zero-width unless the occupant asked for a track; the
|
|
119
140
|
* occupant's panel is positioned against the column's right edge, which never
|
|
120
141
|
* moves, so it can hang over the centre when there is no track.
|
|
@@ -234,8 +255,10 @@ window.__ModuleLoader__.load({
|
|
|
234
255
|
const sidebarCollapsed = narrow ? !layoutInfo.narrowExpanded : layoutInfo.sidebar === 0;
|
|
235
256
|
const sidebarPreference = sidebarCollapsed ? 0 : layoutInfo.sidebar === 0 ? 280 : layoutInfo.sidebar;
|
|
236
257
|
const rightbarPreference = layoutInfo.rightbar ?? viewport * .45;
|
|
237
|
-
const
|
|
238
|
-
const
|
|
258
|
+
const darwin = document.documentElement.dataset.platform === "darwin";
|
|
259
|
+
const collapsedWidth = darwin || document.documentElement.hasAttribute("data-windows-titlebar") ? 0 : 56;
|
|
260
|
+
const normal = computeColumns(viewport, !layoutInfo.rightbarShown && narrow ? 0 : sidebarPreference, rightbarPreference, collapsedWidth);
|
|
261
|
+
const cols = computeColumns(viewport, sidebarPreference, layoutInfo.rightbarTrack ? rightbarPreference : 0, collapsedWidth);
|
|
239
262
|
const colsRef = (0, react.useRef)(cols);
|
|
240
263
|
colsRef.current = cols;
|
|
241
264
|
const rightbarWidth = (0, react.useRef)(normal.rightbar);
|
|
@@ -243,6 +266,36 @@ window.__ModuleLoader__.load({
|
|
|
243
266
|
const sidebarBase = (0, react.useRef)(0);
|
|
244
267
|
const rightbarBase = (0, react.useRef)(0);
|
|
245
268
|
const [dragging, setDragging] = (0, react.useState)(false);
|
|
269
|
+
const [animating, setAnimating] = (0, react.useState)(0);
|
|
270
|
+
const trackToggle = `${sidebarCollapsed}:${layoutInfo.rightbarTrack}`;
|
|
271
|
+
const previousToggle = (0, react.useRef)(trackToggle);
|
|
272
|
+
const previousViewport = (0, react.useRef)(viewport);
|
|
273
|
+
(0, react.useLayoutEffect)(() => {
|
|
274
|
+
const viewportChanged = previousViewport.current !== viewport;
|
|
275
|
+
previousViewport.current = viewport;
|
|
276
|
+
if (previousToggle.current === trackToggle) return;
|
|
277
|
+
previousToggle.current = trackToggle;
|
|
278
|
+
if (viewportChanged) return;
|
|
279
|
+
setAnimating((token) => token + 1);
|
|
280
|
+
}, [trackToggle, viewport]);
|
|
281
|
+
(0, react.useEffect)(() => {
|
|
282
|
+
if (animating === 0) return;
|
|
283
|
+
const frame = frameRef.current;
|
|
284
|
+
/* v8 ignore next -- the ref is always attached by effect time: the frame div renders unconditionally. */
|
|
285
|
+
if (frame === null) return;
|
|
286
|
+
const settle = () => {
|
|
287
|
+
setAnimating(0);
|
|
288
|
+
};
|
|
289
|
+
const onTransitionEnd = (event) => {
|
|
290
|
+
if (event.target === frame && event.propertyName === "grid-template-columns") settle();
|
|
291
|
+
};
|
|
292
|
+
frame.addEventListener("transitionend", onTransitionEnd);
|
|
293
|
+
const timer = setTimeout(settle, 600);
|
|
294
|
+
return () => {
|
|
295
|
+
frame.removeEventListener("transitionend", onTransitionEnd);
|
|
296
|
+
clearTimeout(timer);
|
|
297
|
+
};
|
|
298
|
+
}, [animating]);
|
|
246
299
|
const onDragEnd = (0, react.useCallback)(() => {
|
|
247
300
|
setDragging(false);
|
|
248
301
|
}, []);
|
|
@@ -261,6 +314,7 @@ window.__ModuleLoader__.load({
|
|
|
261
314
|
actions.setRightbar(rightbarBase.current - dx);
|
|
262
315
|
}, [actions]);
|
|
263
316
|
const productTitle = "DeepSeek Harness";
|
|
317
|
+
const rightbarMax = cols.rightbar === 0 ? 0 : clampWidth(rightbarPreference, 300, viewport * RIGHTBAR_MAX_RATIO);
|
|
264
318
|
const sidebar = (0, react.useMemo)(() => renderSlot("sidebar", {
|
|
265
319
|
collapsed: sidebarCollapsed,
|
|
266
320
|
width: cols.sidebar
|
|
@@ -274,16 +328,30 @@ window.__ModuleLoader__.load({
|
|
|
274
328
|
renderSlot
|
|
275
329
|
}), [usePanelInfo, renderSlot]);
|
|
276
330
|
const overlays = (0, react.useMemo)(() => renderSlot("shell.overlay", {}), [renderSlot]);
|
|
331
|
+
const leading = (0, react.useMemo)(() => renderSlot("shell.leading", {}), [renderSlot]);
|
|
332
|
+
const leadingMounted = darwin && sidebarCollapsed;
|
|
277
333
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
278
334
|
ref: frameRef,
|
|
279
335
|
className: AppFrame_module_css_default.frame,
|
|
280
|
-
style: {
|
|
336
|
+
style: {
|
|
337
|
+
...document.documentElement.hasAttribute("data-windows-titlebar") ? { "--dsh-windows-sidebar-width": `${cols.sidebar}px` } : {},
|
|
338
|
+
gridTemplateColumns: `${cols.sidebar}px minmax(${cols.rightbar === 0 ? 0 : 400}px, 1fr) minmax(0px, ${rightbarMax}px)`
|
|
339
|
+
},
|
|
281
340
|
"data-sidebar-collapsed": sidebarCollapsed || void 0,
|
|
282
341
|
"data-rightbar-collapsed": cols.rightbar === 0 || void 0,
|
|
283
342
|
"data-rightbar-fullscreen": layoutInfo.rightbarFullscreen || void 0,
|
|
284
343
|
"data-rightbar-instant": layoutInfo.rightbarInstant || void 0,
|
|
285
344
|
"data-dragging": dragging || void 0,
|
|
345
|
+
"data-animating": animating > 0 || void 0,
|
|
286
346
|
children: [
|
|
347
|
+
darwin && (0, react_jsx_runtime.jsx)("div", {
|
|
348
|
+
className: AppFrame_module_css_default.leadingBand,
|
|
349
|
+
"data-shell-leading-band": true
|
|
350
|
+
}),
|
|
351
|
+
(0, react_jsx_runtime.jsx)(ConversationMarker, {
|
|
352
|
+
usePanelInfo,
|
|
353
|
+
frameRef
|
|
354
|
+
}),
|
|
287
355
|
(0, react_jsx_runtime.jsx)(DocumentTitle, {
|
|
288
356
|
productTitle,
|
|
289
357
|
useSessions,
|
|
@@ -303,6 +371,11 @@ window.__ModuleLoader__.load({
|
|
|
303
371
|
"data-shell-overlay": true,
|
|
304
372
|
children: overlays
|
|
305
373
|
}),
|
|
374
|
+
leadingMounted && (0, react_jsx_runtime.jsx)("div", {
|
|
375
|
+
className: AppFrame_module_css_default.leadingSeat,
|
|
376
|
+
"data-shell-leading": true,
|
|
377
|
+
children: leading
|
|
378
|
+
}),
|
|
306
379
|
!sidebarCollapsed && (0, react_jsx_runtime.jsx)(DragHandle, {
|
|
307
380
|
side: "sidebar",
|
|
308
381
|
left: cols.sidebar,
|
|
@@ -441,6 +514,14 @@ window.__ModuleLoader__.load({
|
|
|
441
514
|
//#region lib/types/client/theme-presenter.js
|
|
442
515
|
/** Body attribute selecting the dark base palette in the token stylesheets. */
|
|
443
516
|
const DARK_ATTRIBUTE = "data-ds-dark-theme";
|
|
517
|
+
/**
|
|
518
|
+
* Root attribute publishing the theme source (`light`, `dark`, or `system`)
|
|
519
|
+
* for host shells that mirror it into native window chrome (the Electron
|
|
520
|
+
* preload forwards it to `nativeTheme.themeSource` so macOS vibrancy follows
|
|
521
|
+
* the app theme). `system` only when the preference is `system`; a fixed
|
|
522
|
+
* preference (including registered theme ids) publishes its resolved scheme.
|
|
523
|
+
*/
|
|
524
|
+
const THEME_SOURCE_ATTRIBUTE = "data-ds-theme-source";
|
|
444
525
|
/** Body variable carrying the user's content font size in px. */
|
|
445
526
|
const CONTENT_FONT_SIZE_VARIABLE = "--dsh-content-font-size";
|
|
446
527
|
/** Applies theme snapshots to the document; one instance per plugin fiber. */
|
|
@@ -466,6 +547,7 @@ window.__ModuleLoader__.load({
|
|
|
466
547
|
apply(snapshot) {
|
|
467
548
|
const scheme = snapshot.active.colorScheme;
|
|
468
549
|
document.documentElement.style.colorScheme = scheme;
|
|
550
|
+
document.documentElement.setAttribute(THEME_SOURCE_ATTRIBUTE, snapshot.preference === "system" ? "system" : scheme);
|
|
469
551
|
const body = document.body;
|
|
470
552
|
if (scheme === "dark") body.setAttribute(DARK_ATTRIBUTE, "");
|
|
471
553
|
else body.removeAttribute(DARK_ATTRIBUTE);
|
|
@@ -479,9 +561,13 @@ window.__ModuleLoader__.load({
|
|
|
479
561
|
this.themeColorMeta.content = getComputedStyle(body).backgroundColor;
|
|
480
562
|
if (!this.themeColorMeta.isConnected) document.head.append(this.themeColorMeta);
|
|
481
563
|
}
|
|
482
|
-
/**
|
|
564
|
+
/**
|
|
565
|
+
* Retract root color-scheme, the theme-source attribute, the palette
|
|
566
|
+
* attribute, token variables, the font-size axis, and the owned metadata node.
|
|
567
|
+
*/
|
|
483
568
|
dispose() {
|
|
484
569
|
document.documentElement.style.removeProperty("color-scheme");
|
|
570
|
+
document.documentElement.removeAttribute(THEME_SOURCE_ATTRIBUTE);
|
|
485
571
|
const body = document.body;
|
|
486
572
|
body.removeAttribute(DARK_ATTRIBUTE);
|
|
487
573
|
body.style.removeProperty(CONTENT_FONT_SIZE_VARIABLE);
|
|
@@ -500,7 +586,7 @@ window.__ModuleLoader__.load({
|
|
|
500
586
|
];
|
|
501
587
|
/**
|
|
502
588
|
* Client plugin body: provide ctx.layout, then one register() call — AppFrame
|
|
503
|
-
* into 'root' with the
|
|
589
|
+
* into 'root' with the five child-slot declarations, the layout store seat,
|
|
504
590
|
* and the shared root instance supplying commands and the panel-info source.
|
|
505
591
|
* @param ctx - client root context.
|
|
506
592
|
*/
|
|
@@ -540,6 +626,10 @@ window.__ModuleLoader__.load({
|
|
|
540
626
|
"shell.overlay": {
|
|
541
627
|
kind: "list",
|
|
542
628
|
scope: "root"
|
|
629
|
+
},
|
|
630
|
+
"shell.leading": {
|
|
631
|
+
kind: "single",
|
|
632
|
+
scope: "root"
|
|
543
633
|
}
|
|
544
634
|
},
|
|
545
635
|
store
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import type { createLayoutStore } from './stores.ts';
|
|
3
3
|
/** Full composed props: runtime share + child-slot render share + store share. */
|
|
4
|
-
export type AppFrameProps = PropsRuntime<'root'> & PropsRenderSlots<'sidebar' | 'main' | 'rightbar' | 'shell.overlay'> & PropsStore<ReturnType<typeof createLayoutStore>> & PropsLocale<'common'>;
|
|
4
|
+
export type AppFrameProps = PropsRuntime<'root'> & PropsRenderSlots<'sidebar' | 'main' | 'rightbar' | 'shell.overlay' | 'shell.leading'> & PropsStore<ReturnType<typeof createLayoutStore>> & PropsLocale<'common'>;
|
|
5
5
|
/** The three-column frame (see module doc). */
|
|
6
6
|
export declare function AppFrame({ useStore, useSessions, usePanelInfo, actions, renderSlot, t, }: AppFrameProps): import("react").JSX.Element;
|
|
7
7
|
//# sourceMappingURL=AppFrame.d.ts.map
|
|
@@ -42,8 +42,10 @@ export declare function clampWidth(px: number, min: number, max: number): number
|
|
|
42
42
|
* @param viewport - available frame width in px.
|
|
43
43
|
* @param sidebar - sidebar width preference in px (0 = closed).
|
|
44
44
|
* @param rightbar - requested right panel width in px (0 = no track).
|
|
45
|
+
* @param collapsedWidth - track width of the closed sidebar; the default keeps
|
|
46
|
+
* the icon rail, 0 hides the column entirely (macOS desktop).
|
|
45
47
|
* @returns actual widths after shrinking or removing the right track; only
|
|
46
48
|
* without that track may the center fall below its minimum, down to zero.
|
|
47
49
|
*/
|
|
48
|
-
export declare function computeColumns(viewport: number, sidebar: number, rightbar: number): Columns;
|
|
50
|
+
export declare function computeColumns(viewport: number, sidebar: number, rightbar: number, collapsedWidth?: number): Columns;
|
|
49
51
|
//# sourceMappingURL=columns.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Layout plugin, browser half: one register() call contributes AppFrame into
|
|
3
3
|
* the runtime's built-in 'root' slot and, in the same breath, declares the
|
|
4
|
-
*
|
|
4
|
+
* five child slots (declaration = exclusive render authority), seats the
|
|
5
5
|
* layout store (panel geometry), and wires the panel-action service face.
|
|
6
6
|
* ctx.layout selects the main panel and controls column geometry; Session
|
|
7
7
|
* selection belongs to the Session Controller. A second effect seats the theme
|
|
@@ -81,6 +81,23 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
81
81
|
kind: 'list';
|
|
82
82
|
scope: 'root';
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Window-chrome seat at the frame's top-left, over every main panel.
|
|
86
|
+
* Mounted only while the sidebar column is fully hidden (macOS desktop
|
|
87
|
+
* collapse; other platforms keep the rail), so the occupant can assume the
|
|
88
|
+
* frame edge is the window edge and the macOS traffic lights sit before it.
|
|
89
|
+
* OCCUPIED by ui-sidebar's reopen/New Session controls.
|
|
90
|
+
*
|
|
91
|
+
* While the seat is mounted the frame publishes
|
|
92
|
+
* `--dsh-frame-leading-clearance` (the inline inset the seat's band
|
|
93
|
+
* occupies, measured from the frame's left edge); a main panel whose
|
|
94
|
+
* content reaches the top-left corner pads by it so nothing lands under
|
|
95
|
+
* the lights or the controls.
|
|
96
|
+
*/
|
|
97
|
+
'shell.leading': {
|
|
98
|
+
kind: 'single';
|
|
99
|
+
scope: 'root';
|
|
100
|
+
};
|
|
84
101
|
}
|
|
85
102
|
}
|
|
86
103
|
/** Sidebar owner share: live column state from the frame's concession solve. */
|
|
@@ -106,7 +123,7 @@ export interface RightbarOwnerProps {
|
|
|
106
123
|
export declare const inject: string[];
|
|
107
124
|
/**
|
|
108
125
|
* Client plugin body: provide ctx.layout, then one register() call — AppFrame
|
|
109
|
-
* into 'root' with the
|
|
126
|
+
* into 'root' with the five child-slot declarations, the layout store seat,
|
|
110
127
|
* and the shared root instance supplying commands and the panel-info source.
|
|
111
128
|
* @param ctx - client root context.
|
|
112
129
|
*/
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* document — `html { color-scheme }` for native UA chrome (scrollbars, form
|
|
4
4
|
* controls), `body[data-ds-dark-theme]` for the token palette, the active
|
|
5
5
|
* theme's alias-token overrides as inline CSS variables on body, the content
|
|
6
|
-
* font-size axis (`--dsh-content-font-size`),
|
|
6
|
+
* font-size axis (`--dsh-content-font-size`), `html[data-ds-theme-source]`
|
|
7
|
+
* for native-chrome mirroring, and one presenter-owned
|
|
7
8
|
* `meta[name="theme-color"]` for surrounding browser UI. Pure DOM writes, no
|
|
8
9
|
* React involvement; the presenter only ever retracts what it wrote itself,
|
|
9
10
|
* so foreign attributes, metadata, and inline styles survive.
|
|
@@ -11,6 +12,14 @@
|
|
|
11
12
|
import type { ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client';
|
|
12
13
|
/** Body attribute selecting the dark base palette in the token stylesheets. */
|
|
13
14
|
export declare const DARK_ATTRIBUTE = "data-ds-dark-theme";
|
|
15
|
+
/**
|
|
16
|
+
* Root attribute publishing the theme source (`light`, `dark`, or `system`)
|
|
17
|
+
* for host shells that mirror it into native window chrome (the Electron
|
|
18
|
+
* preload forwards it to `nativeTheme.themeSource` so macOS vibrancy follows
|
|
19
|
+
* the app theme). `system` only when the preference is `system`; a fixed
|
|
20
|
+
* preference (including registered theme ids) publishes its resolved scheme.
|
|
21
|
+
*/
|
|
22
|
+
export declare const THEME_SOURCE_ATTRIBUTE = "data-ds-theme-source";
|
|
14
23
|
/** Body variable carrying the user's content font size in px. */
|
|
15
24
|
export declare const CONTENT_FONT_SIZE_VARIABLE = "--dsh-content-font-size";
|
|
16
25
|
/** Applies theme snapshots to the document; one instance per plugin fiber. */
|
|
@@ -31,7 +40,10 @@ export declare class ThemePresenter {
|
|
|
31
40
|
* @param snapshot - resolved theme snapshot from ctx.theme.
|
|
32
41
|
*/
|
|
33
42
|
apply(snapshot: ThemeSnapshot): void;
|
|
34
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Retract root color-scheme, the theme-source attribute, the palette
|
|
45
|
+
* attribute, token variables, the font-size axis, and the owned metadata node.
|
|
46
|
+
*/
|
|
35
47
|
dispose(): void;
|
|
36
48
|
}
|
|
37
49
|
//# sourceMappingURL=theme-presenter.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-layout",
|
|
3
3
|
"description": "Shell plugin: three-column AppFrame with drag handles, ctx.layout viewing-state service (navigation + panels)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -38,19 +38,19 @@
|
|
|
38
38
|
},
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/react": "~18.3.1",
|
|
45
45
|
"react": "^18.2.0",
|
|
46
|
-
"@deepseek-ai/dsh-brand": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-client-
|
|
48
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
49
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-client-
|
|
52
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-theme": "^0.1.
|
|
46
|
+
"@deepseek-ai/dsh-brand": "^0.1.7-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-alpha.1",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.7-alpha.1",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-alpha.1",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.7-alpha.1",
|
|
52
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-theme": "^0.1.7-alpha.1"
|
|
54
54
|
},
|
|
55
55
|
"files": [
|
|
56
56
|
"lib/index.js",
|