@crazx/dsh-client-ui-layout 0.1.5-alpha.1.zw.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +92 -0
- package/README.zh.md +92 -0
- package/lib/client.js +560 -0
- package/lib/index.js +6 -0
- package/lib/types/client/AppFrame.d.ts +7 -0
- package/lib/types/client/DocumentTitle.d.ts +15 -0
- package/lib/types/client/columns.d.ts +49 -0
- package/lib/types/client/index.d.ts +135 -0
- package/lib/types/client/service.d.ts +95 -0
- package/lib/types/client/stores.d.ts +62 -0
- package/lib/types/client/theme-presenter.d.ts +37 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +63 -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-layout/README.md
|
|
5
|
+
README.md: 0e353e21f39ba8a84e661f1c540db2c2424bcbfa
|
|
6
|
+
README.zh.md: 16471fdabb46bbfb35158ef85aa2544db4ceae93
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Shell layout for the Web GUI: the three-column AppFrame whose right column is a track for an edge-anchored panel, the panel-geometry service, and theme presentation; for users and maintainers of the window chrome."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-client-ui-layout
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
This package provides the Web GUI's three-column AppFrame, edge-column widths, and `ctx.layout` presentation control. The right column concedes space before the center; its occupant renders fullscreen while the frame retains the wide-screen track underneath. The theme presenter owns color scheme, alias tokens, content font size, and document metadata. Layout state resets on reload.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
The root slot composes the sidebar, conversation, and right column. The sidebar spans 264–420px, defaults to 280px, and retains a 56px rail when collapsed; below 1024px it collapses automatically, and opening the right panel collapses a manually expanded sidebar. The right panel first opens at 45% of the viewport, then retains the user's pixel preference, capped at 70%. To protect 400px for the center, the frame first reduces the right panel to 300px, then reports insufficient room so its occupant closes it, and only then compresses the center further. Dragging has no transition delay; the right handle is absent while closed or fullscreen.
|
|
29
|
+
|
|
30
|
+
### Theme presentation
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
<a id="understand-the-implementation"></a>
|
|
37
|
+
## Understand the implementation
|
|
38
|
+
|
|
39
|
+
<details>
|
|
40
|
+
<summary>Implementation internals — click to expand</summary>
|
|
41
|
+
|
|
42
|
+
One registration declares four child slots and binds `ctx.layout` methods `toggleSidebar`, `openRightbar(track, fullscreen)`, and `closeRightbar`. The store owns the single frame-width measurement, width preferences, and the occupant's presentation report. 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 always mounts the conversation and right columns; a connected Session renders through `SessionProvider`, and without one the right column is an empty zero-width track. It projects the selected Session title over the build-configured product title or the localized `common.brand.localBuild` fallback, so locale revisions update document metadata with the root entry. 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.
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="further-exploration"></a>
|
|
49
|
+
## Further Exploration
|
|
50
|
+
|
|
51
|
+
Read these pages when the layout surface is not enough. They move from the frame to the columns it renders and the theme it presents.
|
|
52
|
+
|
|
53
|
+
- [ui-sidebar](../ui-sidebar/README.md) — occupies the `sidebar` column and its seats.
|
|
54
|
+
- [ui-conversation](../ui-conversation/README.md) — occupies the `conversation` column.
|
|
55
|
+
- [ui-sidebar-right](../ui-sidebar-right/README.md) — occupies the `rightbar` column with one docking surface per session.
|
|
56
|
+
- [ui-theme](../ui-theme/README.md) — the theme seam whose resolved snapshots the presenter consumes.
|
|
57
|
+
- [Web client architecture](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) — how browser plugin rows load and register slots.
|
|
58
|
+
|
|
59
|
+
-----
|
|
60
|
+
|
|
61
|
+
<a id="model-experience"></a>
|
|
62
|
+
## Model Experience
|
|
63
|
+
|
|
64
|
+
None, as the layout shell manages browser viewing state; nothing here reaches a model request.
|
|
65
|
+
|
|
66
|
+
#### KV Cache effect
|
|
67
|
+
|
|
68
|
+
None; this package neither assembles nor sends a provider request.
|
|
69
|
+
|
|
70
|
+
## Known Limitations and Deferred Work
|
|
71
|
+
|
|
72
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
These limits define the current layout behavior. They are current package constraints, not a general window-manager comparison or a task backlog.
|
|
76
|
+
|
|
77
|
+
- **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.
|
|
78
|
+
- **Extremely narrow windows** — after the right panel closes, the center may still fall below 400px; the left 56px rail remains.
|
|
79
|
+
- **Track and panel travel on one shared curve** — the frame's 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.
|
|
80
|
+
- **No scroll anchoring during squeeze reflow** — layout changes may move the reader's viewport.
|
|
81
|
+
|
|
82
|
+
<a id="dev-note"></a>
|
|
83
|
+
### Dev Note
|
|
84
|
+
|
|
85
|
+
<details>
|
|
86
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
87
|
+
|
|
88
|
+
None.
|
|
89
|
+
|
|
90
|
+
</details>
|
|
91
|
+
|
|
92
|
+
**Runtime invariant:** No companion is published. The shell viewing-state store behind ctx.layout emits no cordis events; clamp and track sequencing is asserted directly by this package's columns and service specs.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Web GUI 的外壳布局:三栏 AppFrame——其右栏是贴边面板的轨道——面板几何服务与主题呈现;供窗口外观的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-client-ui-layout
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
本包提供 Web GUI 的三栏 AppFrame、左右栏宽度与 `ctx.layout` 呈现控制。右栏先让步以保护中栏空间,全屏由占用方呈现,框架保留宽屏底层轨道。主题呈现器负责配色、别名 token、正文字号与 document 元数据;布局状态在刷新后重置。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
本插件在 root slot 组合侧栏、会话与右栏。左栏为264~420px,默认280px,收起后保留56px;窗口低于1024px时自动收起,打开右栏也会收起手动展开的左栏。右栏首次打开使用窗口宽度的45%,之后保留用户像素偏好,上限为70%;中栏不足400px时先把右栏压到300px,仍不足则通知占用方收起,最后才继续压缩中栏。拖拽跟手且无过渡延迟,关闭或全屏时不显示右栏拖拽区。
|
|
29
|
+
|
|
30
|
+
### 主题呈现
|
|
31
|
+
|
|
32
|
+
呈现器消费解析后的主题快照,并投影到 document:`html { color-scheme }` 驱动原生 UA 控件,依据当前配色方案设置 `body[data-ds-dark-theme]`,把主题的别名 token 与 `--dsh-content-font-size` 设为 body 上的内联变量,并持有一个 `<meta name="theme-color">`,其内容随计算后的 body 背景色更新。释放呈现器时,它会连同其他全局写入一起移除自己的元数据节点。
|
|
33
|
+
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
<a id="understand-the-implementation"></a>
|
|
37
|
+
## 理解实现
|
|
38
|
+
|
|
39
|
+
<details>
|
|
40
|
+
<summary>实现细节——点击展开</summary>
|
|
41
|
+
|
|
42
|
+
一次注册声明四个子slot并绑定 `ctx.layout` 的 `toggleSidebar`、`openRightbar(track, fullscreen)` 与 `closeRightbar`。store持有唯一的frame宽度测量、左右栏偏好及占用方报告的呈现状态。`rightbar` 的owner参数为实际 `width`、`viewportWidth` 与普通呈现的 `canShow`;占用方在空间不足时执行确定性的收起,变宽不自行重新展开。全屏隐藏宽度手柄,但不自行释放占用方要求保留的轨道。AppFrame 始终挂载会话与右栏;已连接 Session 经 `SessionProvider` 渲染,没有 Session 时右栏是一条空的零宽轨道。它把所选 Session 标题投影到构建配置的产品标题或本地化 `common.brand.localBuild` 回退值之上,因此 locale revision 会随根 entry 一起更新文档元数据。主题呈现器是第二个 effect:从解析后的快照做纯 DOM 写入——初始状态经 getter 读取一次,此后仅事件驱动,不经过 React。它先应用调色板、字号与 token 变量,再把渲染出的背景测量为唯一的颜色依据。 全屏呈现禁用网格和手柄过渡;占用方完全覆盖框架后才报告新的列宽。 退出全屏时,框架先保持无过渡并安装目标布局:关闭移除右轨道,恢复保留右轨道。后续普通几何操作恢复正常过渡。
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="further-exploration"></a>
|
|
49
|
+
## 进一步探索
|
|
50
|
+
|
|
51
|
+
当布局面不够用时阅读以下页面。它们从框架进入它所渲染的栏与它所呈现的主题。
|
|
52
|
+
|
|
53
|
+
- [ui-sidebar](../ui-sidebar/README.zh.md)——占据 `sidebar` 栏及其座位。
|
|
54
|
+
- [ui-conversation](../ui-conversation/README.zh.md)——占据 `conversation` 栏。
|
|
55
|
+
- [ui-sidebar-right](../ui-sidebar-right/README.zh.md)——以每会话一个停靠面占据 `rightbar` 栏。
|
|
56
|
+
- [ui-theme](../ui-theme/README.zh.md)——呈现器消费其解析快照的主题 seam。
|
|
57
|
+
- [Web 客户端架构](../../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md)——浏览器插件行如何加载并注册槽位。
|
|
58
|
+
|
|
59
|
+
-----
|
|
60
|
+
|
|
61
|
+
<a id="model-experience"></a>
|
|
62
|
+
## 模型体验
|
|
63
|
+
|
|
64
|
+
无。布局外壳管理浏览器查看状态;这里没有任何内容进入模型请求。
|
|
65
|
+
|
|
66
|
+
#### KV Cache 影响
|
|
67
|
+
|
|
68
|
+
无;该包既不组装也不发送提供方请求。
|
|
69
|
+
|
|
70
|
+
## 已知限制与延期工作
|
|
71
|
+
|
|
72
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
这些限制界定了当前布局行为。它们是当前包约束,不是通用窗口管理器对比或任务积压。
|
|
76
|
+
|
|
77
|
+
- **面板几何是瞬时状态**——重新加载会恢复侧栏默认值并隐藏右侧面板;每个拖出的宽度都是一份框架级偏好,不是按 Session 的事实。
|
|
78
|
+
- **极窄窗口**——右栏关闭后,中栏仍可能小于400px;左侧56px控制栏保留。
|
|
79
|
+
- **轨道与面板沿同一条曲线运动**——框架的轨道过渡和占位方的滑入读取同一组时长与缓动变量;占位方若自用一套,挤压时面板边缘就会与对话边缘脱开。
|
|
80
|
+
- **挤压重排期间无滚动锚定**——布局变化可能移动读者的视口。
|
|
81
|
+
|
|
82
|
+
<a id="dev-note"></a>
|
|
83
|
+
### 开发备注
|
|
84
|
+
|
|
85
|
+
<details>
|
|
86
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
87
|
+
|
|
88
|
+
无。
|
|
89
|
+
|
|
90
|
+
</details>
|
|
91
|
+
|
|
92
|
+
**运行时不变式:** 不发布伴生入口。`ctx.layout` 后的 viewing-state store 不发出 Cordis 事件;clamp 与轨道的时序由本包的 columns 与 service 规格直接断言。
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@deepseek-ai/dsh-client-ui-layout",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
10
|
+
/** Viewport width below which the sidebar auto-collapses to the rail (deepsuite
|
|
11
|
+
* LG breakpoint); a manual toggle below it re-expands over the squeezed center
|
|
12
|
+
* (stores.ts narrowExpanded). */
|
|
13
|
+
const SIDEBAR_AUTO_COLLAPSE = 1024;
|
|
14
|
+
/** Maximum normal right panel width as a fraction of the frame. */
|
|
15
|
+
const RIGHTBAR_MAX_RATIO = .7;
|
|
16
|
+
/** First-open right panel preference as a fraction of the frame. */
|
|
17
|
+
const RIGHTBAR_DEFAULT_RATIO = .45;
|
|
18
|
+
/**
|
|
19
|
+
* Clamp a panel width into its contract range.
|
|
20
|
+
* @param px - requested width.
|
|
21
|
+
* @param min - range lower bound.
|
|
22
|
+
* @param max - range upper bound.
|
|
23
|
+
* @returns the clamped width.
|
|
24
|
+
*/
|
|
25
|
+
function clampWidth(px, min, max) {
|
|
26
|
+
return Math.min(max, Math.max(min, Math.round(px)));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Solve the three column widths for one viewport frame.
|
|
30
|
+
* @param viewport - available frame width in px.
|
|
31
|
+
* @param sidebar - sidebar width preference in px (0 = closed).
|
|
32
|
+
* @param rightbar - requested right panel width in px (0 = no track).
|
|
33
|
+
* @returns actual widths after shrinking or removing the right track; only
|
|
34
|
+
* without that track may the center fall below its minimum, down to zero.
|
|
35
|
+
*/
|
|
36
|
+
function computeColumns(viewport, sidebar, rightbar) {
|
|
37
|
+
const s = sidebar === 0 ? 56 : clampWidth(sidebar, 264, 420);
|
|
38
|
+
const available = viewport - s - 400;
|
|
39
|
+
const r = rightbar === 0 || available < 300 ? 0 : Math.min(available, clampWidth(rightbar, 300, viewport * RIGHTBAR_MAX_RATIO));
|
|
40
|
+
return {
|
|
41
|
+
sidebar: s,
|
|
42
|
+
center: Math.max(0, viewport - s - r),
|
|
43
|
+
rightbar: r
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region lib/types/client/DocumentTitle.js
|
|
48
|
+
/**
|
|
49
|
+
* Project the selected durable session title into the browser title and
|
|
50
|
+
* restore the build-selected product title when unmounted.
|
|
51
|
+
* @param props - Selected session title projection.
|
|
52
|
+
* @returns No rendered content.
|
|
53
|
+
*/
|
|
54
|
+
function DocumentTitle({ title, productTitle }) {
|
|
55
|
+
(0, react.useEffect)(() => {
|
|
56
|
+
document.title = title === void 0 ? productTitle : `${title} — ${productTitle}`;
|
|
57
|
+
return () => {
|
|
58
|
+
document.title = productTitle;
|
|
59
|
+
};
|
|
60
|
+
}, [productTitle, title]);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-layout/src/client/AppFrame.module.css.mjs
|
|
65
|
+
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:auto minmax(0,1fr);display:grid;position:relative;overflow:hidden}.pI_x6G_toolbarRow{grid-area:1/1/auto/-1;min-width:0;min-height:0}.pI_x6G_sidebarCol{background:var(--dsw-specific-sidebar-fill);border-right:.5px solid var(--dsw-alias-border-l3);grid-row:2;min-width:0;overflow:hidden}.pI_x6G_centerCol{flex-direction:column;grid-row:2;min-width:0;display:flex;overflow:hidden}.pI_x6G_frame[data-dragging]{transition:none}@media (prefers-reduced-motion:reduce){.pI_x6G_frame{transition:none}}.pI_x6G_handle{cursor:col-resize;z-index:11;touch-action:none;width:8px;transition:left var(--ds-transition-duration-slow) var(--ds-ease-in-out);margin-left:-4px;position:absolute;top:0;bottom:0}.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_handle{transition:none}}.pI_x6G_rightbarCol{grid-row:2;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}";
|
|
66
|
+
const tagId = "@deepseek-ai/dsh-client-ui-layout/AppFrame.module.css";
|
|
67
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
68
|
+
const tag = document.createElement("style");
|
|
69
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-layout";
|
|
70
|
+
tag.dataset.pluginCss = tagId;
|
|
71
|
+
tag.textContent = css;
|
|
72
|
+
document.head.appendChild(tag);
|
|
73
|
+
}
|
|
74
|
+
var AppFrame_module_css_default = {
|
|
75
|
+
"centerCol": "pI_x6G_centerCol",
|
|
76
|
+
"frame": "pI_x6G_frame",
|
|
77
|
+
"handle": "pI_x6G_handle",
|
|
78
|
+
"overlayLayer": "pI_x6G_overlayLayer",
|
|
79
|
+
"rightbarCol": "pI_x6G_rightbarCol",
|
|
80
|
+
"sidebarCol": "pI_x6G_sidebarCol",
|
|
81
|
+
"toolbarRow": "pI_x6G_toolbarRow"
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region lib/types/client/AppFrame.js
|
|
85
|
+
/**
|
|
86
|
+
* Three-column shell frame, registered into the built-in 'root' slot (the web
|
|
87
|
+
* shell renders only 'root'). Owns the grid tracks (sidebar | center |
|
|
88
|
+
* rightbar), the drag handles (pointer capture + rAF throttle), the column
|
|
89
|
+
* solve (columns.ts), and the child-slot render decisions: the sidebar slot
|
|
90
|
+
* renders HERE with live parameters from that solve, and the session-aware
|
|
91
|
+
* occupants render in fixed column positions; the strict right-column entry
|
|
92
|
+
* gates itself on current-session availability while the session-maybe
|
|
93
|
+
* conversation retains identity.
|
|
94
|
+
*
|
|
95
|
+
* The right column is a track, not a box: its occupant draws its panel anchored
|
|
96
|
+
* to the frame's right edge at the resolved normal width, and the
|
|
97
|
+
* track only decides whether the centre makes room for it. The occupant reports
|
|
98
|
+
* shown/track/fullscreen through `ctx.layout`; fullscreen keeps the reported
|
|
99
|
+
* track but hides the outer resize handle. Everything arrives through the framework
|
|
100
|
+
* shares — zero cordis or framework imports, zero self-made hooks.
|
|
101
|
+
*/
|
|
102
|
+
/** Center column grid item (session-body building block). */
|
|
103
|
+
function CenterColumn(props) {
|
|
104
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
105
|
+
className: AppFrame_module_css_default.centerCol,
|
|
106
|
+
children: props.children
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Right column grid item. Zero-width unless the occupant asked for a track; the
|
|
111
|
+
* occupant's panel is positioned against the column's right edge, which never
|
|
112
|
+
* moves, so it can hang over the centre when there is no track.
|
|
113
|
+
*/
|
|
114
|
+
function RightbarColumn(props) {
|
|
115
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
116
|
+
className: AppFrame_module_css_default.rightbarCol,
|
|
117
|
+
"data-rightbar-col": true,
|
|
118
|
+
children: props.children
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One drag handle: pointer capture, rAF-throttled dx reports against the drag-start origin.
|
|
123
|
+
* `side` keys the hover-reveal CSS to the owning column.
|
|
124
|
+
*/
|
|
125
|
+
function DragHandle(props) {
|
|
126
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
127
|
+
const origin = (0, react.useRef)(0);
|
|
128
|
+
const latest = (0, react.useRef)(0);
|
|
129
|
+
const frame = (0, react.useRef)(null);
|
|
130
|
+
const capture = (0, react.useRef)(null);
|
|
131
|
+
const callbacks = (0, react.useRef)({
|
|
132
|
+
onStart: props.onStart,
|
|
133
|
+
onDrag: props.onDrag,
|
|
134
|
+
onEnd: props.onEnd
|
|
135
|
+
});
|
|
136
|
+
callbacks.current = {
|
|
137
|
+
onStart: props.onStart,
|
|
138
|
+
onDrag: props.onDrag,
|
|
139
|
+
onEnd: props.onEnd
|
|
140
|
+
};
|
|
141
|
+
const endDrag = (0, react.useCallback)(() => {
|
|
142
|
+
const active = capture.current;
|
|
143
|
+
if (active === null) return;
|
|
144
|
+
capture.current = null;
|
|
145
|
+
if (frame.current !== null) {
|
|
146
|
+
cancelAnimationFrame(frame.current);
|
|
147
|
+
frame.current = null;
|
|
148
|
+
}
|
|
149
|
+
if (active.element.hasPointerCapture(active.id)) active.element.releasePointerCapture(active.id);
|
|
150
|
+
setDragging(false);
|
|
151
|
+
callbacks.current.onEnd();
|
|
152
|
+
}, []);
|
|
153
|
+
(0, react.useEffect)(() => endDrag, [endDrag]);
|
|
154
|
+
const onPointerDown = (0, react.useCallback)((e) => {
|
|
155
|
+
if (e.button !== 0 || capture.current !== null) return;
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
158
|
+
capture.current = {
|
|
159
|
+
element: e.currentTarget,
|
|
160
|
+
id: e.pointerId
|
|
161
|
+
};
|
|
162
|
+
origin.current = e.clientX;
|
|
163
|
+
latest.current = e.clientX;
|
|
164
|
+
callbacks.current.onStart();
|
|
165
|
+
setDragging(true);
|
|
166
|
+
}, []);
|
|
167
|
+
const onPointerMove = (0, react.useCallback)((e) => {
|
|
168
|
+
if (capture.current?.id !== e.pointerId) return;
|
|
169
|
+
latest.current = e.clientX;
|
|
170
|
+
frame.current ??= requestAnimationFrame(() => {
|
|
171
|
+
frame.current = null;
|
|
172
|
+
callbacks.current.onDrag(latest.current - origin.current);
|
|
173
|
+
});
|
|
174
|
+
}, []);
|
|
175
|
+
const onPointerUp = (0, react.useCallback)((e) => {
|
|
176
|
+
if (capture.current?.id !== e.pointerId) return;
|
|
177
|
+
callbacks.current.onDrag(e.clientX - origin.current);
|
|
178
|
+
endDrag();
|
|
179
|
+
}, [endDrag]);
|
|
180
|
+
const onPointerCancel = (0, react.useCallback)((e) => {
|
|
181
|
+
if (capture.current?.id === e.pointerId) endDrag();
|
|
182
|
+
}, [endDrag]);
|
|
183
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
184
|
+
className: AppFrame_module_css_default.handle,
|
|
185
|
+
style: { left: props.left },
|
|
186
|
+
"data-side": props.side,
|
|
187
|
+
"data-dragging": dragging || void 0,
|
|
188
|
+
onPointerDown,
|
|
189
|
+
onPointerMove,
|
|
190
|
+
onPointerUp,
|
|
191
|
+
onPointerCancel,
|
|
192
|
+
onLostPointerCapture: onPointerCancel
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/** The three-column frame (see module doc). */
|
|
196
|
+
function AppFrame({ useStore, useSessions, actions, renderSlot, SessionProvider, t }) {
|
|
197
|
+
const panels = useStore((s) => s);
|
|
198
|
+
const documentTitle = useSessions((s) => {
|
|
199
|
+
const current = s.current;
|
|
200
|
+
return current === void 0 ? void 0 : s.byId[current]?.title;
|
|
201
|
+
});
|
|
202
|
+
const frameRef = (0, react.useRef)(null);
|
|
203
|
+
const viewport = panels.viewportWidth;
|
|
204
|
+
(0, react.useLayoutEffect)(() => {
|
|
205
|
+
const el = frameRef.current;
|
|
206
|
+
/* v8 ignore next -- the ref is always attached by effect time: the frame div renders unconditionally. */
|
|
207
|
+
if (el === null) return;
|
|
208
|
+
let raf = null;
|
|
209
|
+
let disposed = false;
|
|
210
|
+
const measure = () => {
|
|
211
|
+
const width = el.getBoundingClientRect().width;
|
|
212
|
+
if (width > 0) actions.setViewportWidth(width);
|
|
213
|
+
};
|
|
214
|
+
measure();
|
|
215
|
+
const observer = new ResizeObserver(() => {
|
|
216
|
+
if (disposed) return;
|
|
217
|
+
raf ??= requestAnimationFrame(() => {
|
|
218
|
+
raf = null;
|
|
219
|
+
measure();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
observer.observe(el);
|
|
223
|
+
return () => {
|
|
224
|
+
disposed = true;
|
|
225
|
+
observer.disconnect();
|
|
226
|
+
if (raf !== null) cancelAnimationFrame(raf);
|
|
227
|
+
};
|
|
228
|
+
}, [actions]);
|
|
229
|
+
const narrow = viewport < SIDEBAR_AUTO_COLLAPSE;
|
|
230
|
+
const sidebarCollapsed = narrow ? !panels.narrowExpanded : panels.sidebar === 0;
|
|
231
|
+
const sidebarPreference = sidebarCollapsed ? 0 : panels.sidebar === 0 ? 280 : panels.sidebar;
|
|
232
|
+
const rightbarPreference = panels.rightbar ?? viewport * .45;
|
|
233
|
+
const normal = computeColumns(viewport, !panels.rightbarShown && narrow ? 0 : sidebarPreference, rightbarPreference);
|
|
234
|
+
const cols = computeColumns(viewport, sidebarPreference, panels.rightbarTrack ? rightbarPreference : 0);
|
|
235
|
+
const colsRef = (0, react.useRef)(cols);
|
|
236
|
+
colsRef.current = cols;
|
|
237
|
+
const rightbarWidth = (0, react.useRef)(normal.rightbar);
|
|
238
|
+
rightbarWidth.current = normal.rightbar;
|
|
239
|
+
const sidebarBase = (0, react.useRef)(0);
|
|
240
|
+
const rightbarBase = (0, react.useRef)(0);
|
|
241
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
242
|
+
const onDragEnd = (0, react.useCallback)(() => {
|
|
243
|
+
setDragging(false);
|
|
244
|
+
}, []);
|
|
245
|
+
const onSidebarStart = (0, react.useCallback)(() => {
|
|
246
|
+
sidebarBase.current = colsRef.current.sidebar;
|
|
247
|
+
setDragging(true);
|
|
248
|
+
}, []);
|
|
249
|
+
const onSidebarDrag = (0, react.useCallback)((dx) => {
|
|
250
|
+
actions.setSidebar(sidebarBase.current + dx);
|
|
251
|
+
}, [actions]);
|
|
252
|
+
const onRightbarStart = (0, react.useCallback)(() => {
|
|
253
|
+
rightbarBase.current = rightbarWidth.current;
|
|
254
|
+
setDragging(true);
|
|
255
|
+
}, []);
|
|
256
|
+
const onRightbarDrag = (0, react.useCallback)((dx) => {
|
|
257
|
+
actions.setRightbar(rightbarBase.current - dx);
|
|
258
|
+
}, [actions]);
|
|
259
|
+
const productTitle = {}.DSH_CLIENT_TITLE ?? t("brand.localBuild");
|
|
260
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
261
|
+
ref: frameRef,
|
|
262
|
+
className: AppFrame_module_css_default.frame,
|
|
263
|
+
style: { gridTemplateColumns: `${cols.sidebar}px minmax(0, 1fr) ${cols.rightbar}px` },
|
|
264
|
+
"data-sidebar-collapsed": sidebarCollapsed || void 0,
|
|
265
|
+
"data-rightbar-collapsed": cols.rightbar === 0 || void 0,
|
|
266
|
+
"data-rightbar-fullscreen": panels.rightbarFullscreen || void 0,
|
|
267
|
+
"data-rightbar-instant": panels.rightbarInstant || void 0,
|
|
268
|
+
"data-dragging": dragging || void 0,
|
|
269
|
+
children: [
|
|
270
|
+
(0, react_jsx_runtime.jsx)(DocumentTitle, {
|
|
271
|
+
productTitle,
|
|
272
|
+
...documentTitle === void 0 ? {} : { title: documentTitle }
|
|
273
|
+
}),
|
|
274
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
275
|
+
className: AppFrame_module_css_default.toolbarRow,
|
|
276
|
+
"data-shell-toolbar-row": true,
|
|
277
|
+
children: renderSlot("shell.toolbar", {})
|
|
278
|
+
}),
|
|
279
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
280
|
+
className: AppFrame_module_css_default.sidebarCol,
|
|
281
|
+
children: renderSlot("sidebar", {
|
|
282
|
+
collapsed: sidebarCollapsed,
|
|
283
|
+
width: cols.sidebar
|
|
284
|
+
})
|
|
285
|
+
}),
|
|
286
|
+
(0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(CenterColumn, { children: renderSlot("conversation", {}) }), (0, react_jsx_runtime.jsx)(RightbarColumn, { children: (0, react_jsx_runtime.jsx)(SessionProvider, { children: renderSlot("rightbar", {
|
|
287
|
+
width: normal.rightbar,
|
|
288
|
+
viewportWidth: viewport,
|
|
289
|
+
canShow: normal.rightbar > 0
|
|
290
|
+
}) }) })] }),
|
|
291
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
292
|
+
className: AppFrame_module_css_default.overlayLayer,
|
|
293
|
+
"data-shell-overlay": true,
|
|
294
|
+
children: renderSlot("shell.overlay", {})
|
|
295
|
+
}),
|
|
296
|
+
!sidebarCollapsed && (0, react_jsx_runtime.jsx)(DragHandle, {
|
|
297
|
+
side: "sidebar",
|
|
298
|
+
left: cols.sidebar,
|
|
299
|
+
onStart: onSidebarStart,
|
|
300
|
+
onDrag: onSidebarDrag,
|
|
301
|
+
onEnd: onDragEnd
|
|
302
|
+
}),
|
|
303
|
+
panels.rightbarShown && !panels.rightbarFullscreen && normal.rightbar > 0 && (0, react_jsx_runtime.jsx)(DragHandle, {
|
|
304
|
+
side: "rightbar",
|
|
305
|
+
left: viewport - normal.rightbar,
|
|
306
|
+
onStart: onRightbarStart,
|
|
307
|
+
onDrag: onRightbarDrag,
|
|
308
|
+
onEnd: onDragEnd
|
|
309
|
+
})
|
|
310
|
+
]
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region lib/types/client/stores.js
|
|
315
|
+
/**
|
|
316
|
+
* Root-owned frame measurement, panel preferences, and presentation reports.
|
|
317
|
+
* The registration supplies a fresh store and binds its actions to ctx.layout.
|
|
318
|
+
*/
|
|
319
|
+
/**
|
|
320
|
+
* Create the layout panel store handle. For the sidebar the preference IS the
|
|
321
|
+
* width, so closing it forgets its drag width — reopening restores the contract
|
|
322
|
+
* default. The right panel initializes at 45% of the frame on first opening
|
|
323
|
+
* and keeps that px preference across resizes and close. Drag writes clamp to
|
|
324
|
+
* the current frame's range. Narrow sidebar toggles change only the expansion
|
|
325
|
+
* override; opening the right panel clears that override.
|
|
326
|
+
* @returns the store handle (spec + type + identity + factory in one).
|
|
327
|
+
*/
|
|
328
|
+
function createLayoutStore() {
|
|
329
|
+
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
330
|
+
init: () => ({
|
|
331
|
+
sidebar: 280,
|
|
332
|
+
viewportWidth: window.innerWidth,
|
|
333
|
+
narrowExpanded: false,
|
|
334
|
+
rightbar: null,
|
|
335
|
+
rightbarShown: false,
|
|
336
|
+
rightbarTrack: false,
|
|
337
|
+
rightbarFullscreen: false,
|
|
338
|
+
rightbarInstant: false
|
|
339
|
+
}),
|
|
340
|
+
actions: {
|
|
341
|
+
setSidebar: (d, px) => {
|
|
342
|
+
d.rightbarInstant = false;
|
|
343
|
+
d.sidebar = clampWidth(px, 264, 420);
|
|
344
|
+
},
|
|
345
|
+
toggleSidebar: (d) => {
|
|
346
|
+
d.rightbarInstant = false;
|
|
347
|
+
if (d.viewportWidth < 1024) d.narrowExpanded = !d.narrowExpanded;
|
|
348
|
+
else d.sidebar = d.sidebar === 0 ? 280 : 0;
|
|
349
|
+
},
|
|
350
|
+
setViewportWidth: (d, width) => {
|
|
351
|
+
if (d.viewportWidth === width) return;
|
|
352
|
+
d.rightbarInstant = false;
|
|
353
|
+
if (d.viewportWidth < 1024 !== width < 1024) d.narrowExpanded = false;
|
|
354
|
+
d.viewportWidth = width;
|
|
355
|
+
},
|
|
356
|
+
setRightbar: (d, px) => {
|
|
357
|
+
d.rightbarInstant = false;
|
|
358
|
+
d.rightbar = clampWidth(px, 300, Math.max(300, d.viewportWidth * RIGHTBAR_MAX_RATIO));
|
|
359
|
+
},
|
|
360
|
+
openRightbar: (d, track, fullscreen) => {
|
|
361
|
+
if (!d.rightbarShown || d.rightbarTrack !== track || d.rightbarFullscreen !== fullscreen) d.rightbarInstant = d.rightbarFullscreen && !fullscreen;
|
|
362
|
+
if (!d.rightbarShown && d.viewportWidth < 1024) d.narrowExpanded = false;
|
|
363
|
+
d.rightbar ??= Math.max(300, Math.round(d.viewportWidth * RIGHTBAR_DEFAULT_RATIO));
|
|
364
|
+
d.rightbarShown = true;
|
|
365
|
+
d.rightbarTrack = track;
|
|
366
|
+
d.rightbarFullscreen = fullscreen;
|
|
367
|
+
},
|
|
368
|
+
closeRightbar: (d) => {
|
|
369
|
+
if (d.rightbarShown) d.rightbarInstant = d.rightbarFullscreen;
|
|
370
|
+
d.rightbarShown = false;
|
|
371
|
+
d.rightbarTrack = false;
|
|
372
|
+
d.rightbarFullscreen = false;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region lib/types/client/service.js
|
|
379
|
+
/** Cross-plugin panel-action face (ctx.layout). */
|
|
380
|
+
var LayoutController = class {
|
|
381
|
+
#panels;
|
|
382
|
+
#toolbarHosts = null;
|
|
383
|
+
#toolbarListeners = /* @__PURE__ */ new Set();
|
|
384
|
+
/**
|
|
385
|
+
* Adopt the root entry's bound store actions. Called from the root
|
|
386
|
+
* registration's inject hook (a sanctioned assembly side effect), so the
|
|
387
|
+
* face is live from the entry's first render; on entry re-register the
|
|
388
|
+
* fresh actions overwrite the stale set.
|
|
389
|
+
* @param actions - bound actions of the entry's layout store instance.
|
|
390
|
+
*/
|
|
391
|
+
attachPanels(actions) {
|
|
392
|
+
this.#panels = actions;
|
|
393
|
+
}
|
|
394
|
+
/** Toggle the sidebar panel (closed ⟷ contract default width). */
|
|
395
|
+
toggleSidebar() {
|
|
396
|
+
this.#require().toggleSidebar();
|
|
397
|
+
}
|
|
398
|
+
/** Report the right panel's track and fullscreen presentation. */
|
|
399
|
+
openRightbar(track, fullscreen) {
|
|
400
|
+
this.#require().openRightbar(track, fullscreen);
|
|
401
|
+
}
|
|
402
|
+
/** Report the right panel as hidden: no track, no handle. */
|
|
403
|
+
closeRightbar() {
|
|
404
|
+
this.#require().closeRightbar();
|
|
405
|
+
}
|
|
406
|
+
/** Publish the desktop toolbar's host pair (identity replaces wholesale). */
|
|
407
|
+
setToolbarHosts(hosts) {
|
|
408
|
+
this.#toolbarHosts = hosts;
|
|
409
|
+
for (const listener of this.#toolbarListeners) listener(hosts);
|
|
410
|
+
}
|
|
411
|
+
/** Release the pair only when the registry still holds THIS caller's pair. */
|
|
412
|
+
releaseToolbarHosts(hosts) {
|
|
413
|
+
if (this.#toolbarHosts !== hosts) return;
|
|
414
|
+
this.#toolbarHosts = null;
|
|
415
|
+
for (const listener of this.#toolbarListeners) listener(null);
|
|
416
|
+
}
|
|
417
|
+
/** The current host pair, or null while no desktop toolbar is mounted. */
|
|
418
|
+
getToolbarHosts() {
|
|
419
|
+
return this.#toolbarHosts;
|
|
420
|
+
}
|
|
421
|
+
/** Subscribe to toolbar-host changes; the header portals while non-null. */
|
|
422
|
+
onToolbarHosts(listener) {
|
|
423
|
+
this.#toolbarListeners.add(listener);
|
|
424
|
+
return () => {
|
|
425
|
+
this.#toolbarListeners.delete(listener);
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
#require() {
|
|
429
|
+
if (this.#panels === void 0) throw new Error("layout: panel actions not wired (root entry not mounted)");
|
|
430
|
+
return this.#panels;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region lib/types/client/theme-presenter.js
|
|
435
|
+
/** Body attribute selecting the dark base palette in the token stylesheets. */
|
|
436
|
+
const DARK_ATTRIBUTE = "data-ds-dark-theme";
|
|
437
|
+
/** Body variable carrying the user's content font size in px. */
|
|
438
|
+
const CONTENT_FONT_SIZE_VARIABLE = "--dsh-content-font-size";
|
|
439
|
+
/** Applies theme snapshots to the document; one instance per plugin fiber. */
|
|
440
|
+
var ThemePresenter = class {
|
|
441
|
+
/** Token names this presenter wrote in the last apply (its retraction set). */
|
|
442
|
+
appliedTokens = [];
|
|
443
|
+
/** The single metadata node this presenter inserts and removes. */
|
|
444
|
+
themeColorMeta;
|
|
445
|
+
/** Create the presenter-owned metadata node before the first snapshot arrives. */
|
|
446
|
+
constructor() {
|
|
447
|
+
this.themeColorMeta = document.createElement("meta");
|
|
448
|
+
this.themeColorMeta.name = "theme-color";
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Project a snapshot onto the document: set root `color-scheme` and the body
|
|
452
|
+
* palette attribute from `active.colorScheme` (never the id — `system` is
|
|
453
|
+
* resolved upstream), publish the content font-size axis, then replace the
|
|
454
|
+
* previously applied token variables with `active.tokens`. Browser
|
|
455
|
+
* theme-color metadata follows the computed body background after those
|
|
456
|
+
* writes, so the rendered palette remains the color authority.
|
|
457
|
+
* @param snapshot - resolved theme snapshot from ctx.theme.
|
|
458
|
+
*/
|
|
459
|
+
apply(snapshot) {
|
|
460
|
+
const scheme = snapshot.active.colorScheme;
|
|
461
|
+
document.documentElement.style.colorScheme = scheme;
|
|
462
|
+
const body = document.body;
|
|
463
|
+
if (scheme === "dark") body.setAttribute(DARK_ATTRIBUTE, "");
|
|
464
|
+
else body.removeAttribute(DARK_ATTRIBUTE);
|
|
465
|
+
body.style.setProperty(CONTENT_FONT_SIZE_VARIABLE, `${snapshot.fontSize}px`);
|
|
466
|
+
for (const name of this.appliedTokens) body.style.removeProperty(name);
|
|
467
|
+
this.appliedTokens = [];
|
|
468
|
+
for (const [name, value] of Object.entries(snapshot.active.tokens)) {
|
|
469
|
+
body.style.setProperty(name, value);
|
|
470
|
+
this.appliedTokens.push(name);
|
|
471
|
+
}
|
|
472
|
+
this.themeColorMeta.content = getComputedStyle(body).backgroundColor;
|
|
473
|
+
if (!this.themeColorMeta.isConnected) document.head.append(this.themeColorMeta);
|
|
474
|
+
}
|
|
475
|
+
/** Retract root color-scheme, the palette attribute, token variables, the font-size axis, and the owned metadata node. */
|
|
476
|
+
dispose() {
|
|
477
|
+
document.documentElement.style.removeProperty("color-scheme");
|
|
478
|
+
const body = document.body;
|
|
479
|
+
body.removeAttribute(DARK_ATTRIBUTE);
|
|
480
|
+
body.style.removeProperty(CONTENT_FONT_SIZE_VARIABLE);
|
|
481
|
+
for (const name of this.appliedTokens) body.style.removeProperty(name);
|
|
482
|
+
this.appliedTokens = [];
|
|
483
|
+
this.themeColorMeta.remove();
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
//#endregion
|
|
487
|
+
//#region lib/types/client/index.js
|
|
488
|
+
/** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
|
|
489
|
+
const inject = [
|
|
490
|
+
"slots",
|
|
491
|
+
"theme",
|
|
492
|
+
"locale"
|
|
493
|
+
];
|
|
494
|
+
/**
|
|
495
|
+
* Client plugin body: provide ctx.layout, then one register() call — AppFrame
|
|
496
|
+
* into 'root' with the four child-slot declarations, the layout store seat,
|
|
497
|
+
* and the inject hook that hands the store's bound actions to the service.
|
|
498
|
+
* @param ctx - client root context.
|
|
499
|
+
*/
|
|
500
|
+
function apply(ctx) {
|
|
501
|
+
const layout = new LayoutController();
|
|
502
|
+
ctx.effect(() => {
|
|
503
|
+
const disposeService = ctx.reflect.provide("layout", layout);
|
|
504
|
+
const disposeRegistration = ctx.slots.register({
|
|
505
|
+
name: "root",
|
|
506
|
+
locale: "common",
|
|
507
|
+
children: {
|
|
508
|
+
"shell.toolbar": {
|
|
509
|
+
kind: "single",
|
|
510
|
+
scope: "session-maybe"
|
|
511
|
+
},
|
|
512
|
+
"sidebar": {
|
|
513
|
+
kind: "single",
|
|
514
|
+
scope: "root"
|
|
515
|
+
},
|
|
516
|
+
"conversation": {
|
|
517
|
+
kind: "single",
|
|
518
|
+
scope: "session-maybe"
|
|
519
|
+
},
|
|
520
|
+
"rightbar": {
|
|
521
|
+
kind: "single",
|
|
522
|
+
scope: "session"
|
|
523
|
+
},
|
|
524
|
+
"shell.overlay": {
|
|
525
|
+
kind: "list",
|
|
526
|
+
scope: "root"
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
store: createLayoutStore,
|
|
530
|
+
inject: (actions) => {
|
|
531
|
+
layout.attachPanels(actions);
|
|
532
|
+
return {};
|
|
533
|
+
}
|
|
534
|
+
}, AppFrame);
|
|
535
|
+
return () => {
|
|
536
|
+
disposeRegistration();
|
|
537
|
+
disposeService();
|
|
538
|
+
};
|
|
539
|
+
}, "ui-layout: service + root registration");
|
|
540
|
+
ctx.effect(() => {
|
|
541
|
+
const presenter = new ThemePresenter();
|
|
542
|
+
presenter.apply(ctx.theme.getTheme());
|
|
543
|
+
const off = ctx.on("theme/change", (snapshot) => {
|
|
544
|
+
presenter.apply(snapshot);
|
|
545
|
+
});
|
|
546
|
+
return () => {
|
|
547
|
+
off();
|
|
548
|
+
presenter.dispose();
|
|
549
|
+
};
|
|
550
|
+
}, "ui-layout: theme presenter");
|
|
551
|
+
}
|
|
552
|
+
//#endregion
|
|
553
|
+
exports.LayoutController = LayoutController;
|
|
554
|
+
exports.apply = apply;
|
|
555
|
+
exports.inject = inject;
|
|
556
|
+
return module.exports;
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { createLayoutStore } from './stores.ts';
|
|
3
|
+
/** Full composed props: runtime share + child-slot render share + store share. */
|
|
4
|
+
export type AppFrameProps = PropsRuntime<'root'> & PropsRenderSlots<'shell.toolbar' | 'sidebar' | 'conversation' | 'rightbar' | 'shell.overlay'> & PropsStore<ReturnType<typeof createLayoutStore>> & PropsLocale<'common'>;
|
|
5
|
+
/** The three-column frame (see module doc). */
|
|
6
|
+
export declare function AppFrame({ useStore, useSessions, actions, renderSlot, SessionProvider, t, }: AppFrameProps): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=AppFrame.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Props for the browser title projection. */
|
|
2
|
+
export interface DocumentTitleProps {
|
|
3
|
+
/** Durable title of the selected session, or undefined for the product title. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Build-configured or localized product title. */
|
|
6
|
+
productTitle: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Project the selected durable session title into the browser title and
|
|
10
|
+
* restore the build-selected product title when unmounted.
|
|
11
|
+
* @param props - Selected session title projection.
|
|
12
|
+
* @returns No rendered content.
|
|
13
|
+
*/
|
|
14
|
+
export declare function DocumentTitle({ title, productTitle }: DocumentTitleProps): null;
|
|
15
|
+
//# sourceMappingURL=DocumentTitle.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normal column geometry: the right column shrinks, then loses its track,
|
|
3
|
+
* before the center drops below its minimum. The sidebar never concedes here;
|
|
4
|
+
* AppFrame supplies its effective preference after responsive collapse.
|
|
5
|
+
*/
|
|
6
|
+
/** Resolved widths for one frame. */
|
|
7
|
+
export interface Columns {
|
|
8
|
+
sidebar: number;
|
|
9
|
+
center: number;
|
|
10
|
+
rightbar: number;
|
|
11
|
+
}
|
|
12
|
+
/** Center width protected while the normal right column is open. */
|
|
13
|
+
export declare const CENTER_MIN = 400;
|
|
14
|
+
/** Sidebar drag clamp floor. */
|
|
15
|
+
export declare const SIDEBAR_MIN = 264;
|
|
16
|
+
/** Sidebar drag clamp ceiling. */
|
|
17
|
+
export declare const SIDEBAR_MAX = 420;
|
|
18
|
+
/** Sidebar width before any user drag. */
|
|
19
|
+
export declare const SIDEBAR_DEFAULT = 280;
|
|
20
|
+
/** Closed-sidebar rail: a 24px icon column between 16px horizontal paddings. */
|
|
21
|
+
export declare const SIDEBAR_COLLAPSED = 56;
|
|
22
|
+
/** Viewport width below which the sidebar auto-collapses to the rail (deepsuite
|
|
23
|
+
* LG breakpoint); a manual toggle below it re-expands over the squeezed center
|
|
24
|
+
* (stores.ts narrowExpanded). */
|
|
25
|
+
export declare const SIDEBAR_AUTO_COLLAPSE = 1024;
|
|
26
|
+
/** Right column drag clamp floor. */
|
|
27
|
+
export declare const RIGHTBAR_MIN = 300;
|
|
28
|
+
/** Maximum normal right panel width as a fraction of the frame. */
|
|
29
|
+
export declare const RIGHTBAR_MAX_RATIO = 0.7;
|
|
30
|
+
/** First-open right panel preference as a fraction of the frame. */
|
|
31
|
+
export declare const RIGHTBAR_DEFAULT_RATIO = 0.45;
|
|
32
|
+
/**
|
|
33
|
+
* Clamp a panel width into its contract range.
|
|
34
|
+
* @param px - requested width.
|
|
35
|
+
* @param min - range lower bound.
|
|
36
|
+
* @param max - range upper bound.
|
|
37
|
+
* @returns the clamped width.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clampWidth(px: number, min: number, max: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Solve the three column widths for one viewport frame.
|
|
42
|
+
* @param viewport - available frame width in px.
|
|
43
|
+
* @param sidebar - sidebar width preference in px (0 = closed).
|
|
44
|
+
* @param rightbar - requested right panel width in px (0 = no track).
|
|
45
|
+
* @returns actual widths after shrinking or removing the right track; only
|
|
46
|
+
* without that track may the center fall below its minimum, down to zero.
|
|
47
|
+
*/
|
|
48
|
+
export declare function computeColumns(viewport: number, sidebar: number, rightbar: number): Columns;
|
|
49
|
+
//# sourceMappingURL=columns.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout plugin, browser half: one register() call contributes AppFrame into
|
|
3
|
+
* the runtime's built-in 'root' slot and, in the same breath, declares the
|
|
4
|
+
* four child slots (declaration = exclusive render authority), seats the
|
|
5
|
+
* layout store (panel geometry), and wires the panel-action service face.
|
|
6
|
+
* ctx.layout is the cross-plugin panel-action contract; navigation state lives
|
|
7
|
+
* with the runtime sessions service. A second effect seats the theme
|
|
8
|
+
* presenter, which projects ctx.theme snapshots onto document.body.
|
|
9
|
+
*/
|
|
10
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
11
|
+
export { LayoutController } from './service.ts';
|
|
12
|
+
export type { ILayout, ToolbarHosts } from './service.ts';
|
|
13
|
+
declare module '@deepseek-ai/cordis' {
|
|
14
|
+
interface Context {
|
|
15
|
+
/** The outward face only; the concrete service stays inside this plugin. */
|
|
16
|
+
layout: import('./service.ts').ILayout;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
20
|
+
interface SlotMap {
|
|
21
|
+
/**
|
|
22
|
+
* The whole left column. OCCUPIED by ui-sidebar's SidebarRoot, which
|
|
23
|
+
* declares the workspace and settings seats inside it — registering here
|
|
24
|
+
* replaces the navigation column outright rather than adding to it, and
|
|
25
|
+
* the seats it declares disappear with it. To add something to the
|
|
26
|
+
* sidebar, register into one of those inner seats instead.
|
|
27
|
+
*
|
|
28
|
+
* The occupant receives the frame's live column state (collapsed, width)
|
|
29
|
+
* and is expected to render the compact control rail while collapsed.
|
|
30
|
+
*/
|
|
31
|
+
'sidebar': {
|
|
32
|
+
kind: 'single';
|
|
33
|
+
scope: 'root';
|
|
34
|
+
owner: SidebarOwnerProps;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The whole center column, across both the no-session hero and a live
|
|
38
|
+
* conversation. OCCUPIED by ui-conversation's ConversationRoot, which
|
|
39
|
+
* declares the session body, composer, and input seats inside it —
|
|
40
|
+
* registering here replaces the entire conversation surface (and removes
|
|
41
|
+
* every seat it declares) rather than adding to it.
|
|
42
|
+
*
|
|
43
|
+
* Current-session-optional: the occupant owns both states without
|
|
44
|
+
* changing its React identity, so it keeps its own state across a session
|
|
45
|
+
* switch. It receives no owner props; session facts arrive through the
|
|
46
|
+
* framework hooks of the `session-maybe` scope.
|
|
47
|
+
*/
|
|
48
|
+
'conversation': {
|
|
49
|
+
kind: 'single';
|
|
50
|
+
scope: 'session-maybe';
|
|
51
|
+
owner: ConvOwnerProps;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The right column: a track the centre makes room for, or nothing. OCCUPIED
|
|
55
|
+
* by the right Sidebar, which uses the resolved column width in normal
|
|
56
|
+
* mode and covers the viewport in fullscreen, retaining the wide-screen
|
|
57
|
+
* column reservation underneath.
|
|
58
|
+
*
|
|
59
|
+
* Whether the panel is shown, and whether it takes a track, is the
|
|
60
|
+
* occupant's own recorded business — it reports the composition of its
|
|
61
|
+
* expanded and presentation state through `ctx.layout`, and the frame sizes
|
|
62
|
+
* the track and places the resize handle from that. The expand control is
|
|
63
|
+
* not this column's: it is a button in the conversation header. With no
|
|
64
|
+
* current session nothing is mounted here.
|
|
65
|
+
*/
|
|
66
|
+
'rightbar': {
|
|
67
|
+
kind: 'single';
|
|
68
|
+
scope: 'session';
|
|
69
|
+
owner: RightbarOwnerProps;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The unified desktop toolbar row: the frame's first grid row spanning
|
|
73
|
+
* every column (macOS toolbar look with the native traffic lights inside
|
|
74
|
+
* its leading inset). OCCUPIED by the desktop bridge's DesktopToolbar,
|
|
75
|
+
* which renders the portal hosts the session header projects into.
|
|
76
|
+
*
|
|
77
|
+
* Session-maybe scope so the occupant receives the standard session
|
|
78
|
+
* shares (it derives the title/workspace face) while staying mounted
|
|
79
|
+
* across no-session/session transitions. With no occupant the row is an
|
|
80
|
+
* empty auto track — zero height — so plain web, Windows, and Linux
|
|
81
|
+
* layouts are pixel-identical to the single-row frame.
|
|
82
|
+
*/
|
|
83
|
+
'shell.toolbar': {
|
|
84
|
+
kind: 'single';
|
|
85
|
+
scope: 'session-maybe';
|
|
86
|
+
owner: ConvOwnerProps;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Frame-wide floating layer, above every column and outside their scroll
|
|
90
|
+
* containers. Deliberately generic and unowned by any feature: a badge, a
|
|
91
|
+
* toast stack or a status pill all belong here, and entries order among
|
|
92
|
+
* themselves. The layer itself is click-through — entries opt back into
|
|
93
|
+
* pointer events — so an occupant never blocks the app underneath.
|
|
94
|
+
*
|
|
95
|
+
* This is the additive seat for a frame-wide surface of your own: a fresh
|
|
96
|
+
* `id` is added beside the shipped entries instead of replacing them.
|
|
97
|
+
*/
|
|
98
|
+
'shell.overlay': {
|
|
99
|
+
kind: 'list';
|
|
100
|
+
scope: 'root';
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Sidebar owner share: live column state from the frame's concession solve. */
|
|
105
|
+
export interface SidebarOwnerProps {
|
|
106
|
+
/** True when the sidebar is closed (the column renders the compact control rail). */
|
|
107
|
+
collapsed: boolean;
|
|
108
|
+
/** Rendered column width in px (SIDEBAR_COLLAPSED when collapsed). */
|
|
109
|
+
width: number;
|
|
110
|
+
}
|
|
111
|
+
/** Conversation owner share: business state and actions belong to the registrant. */
|
|
112
|
+
export interface ConvOwnerProps {
|
|
113
|
+
}
|
|
114
|
+
/** Right column owner share: resolved normal geometry and opening eligibility. */
|
|
115
|
+
export interface RightbarOwnerProps {
|
|
116
|
+
/** Resolved normal panel width in px, not the saved preference; zero if it cannot fit. */
|
|
117
|
+
width: number;
|
|
118
|
+
/** Current frame width in px. */
|
|
119
|
+
viewportWidth: number;
|
|
120
|
+
/**
|
|
121
|
+
* Whether a normal right panel can retain 300px beside a 400px center.
|
|
122
|
+
* Before a narrow opening, includes the space from collapsing the left sidebar.
|
|
123
|
+
*/
|
|
124
|
+
canShow: boolean;
|
|
125
|
+
}
|
|
126
|
+
/** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
|
|
127
|
+
export declare const inject: string[];
|
|
128
|
+
/**
|
|
129
|
+
* Client plugin body: provide ctx.layout, then one register() call — AppFrame
|
|
130
|
+
* into 'root' with the four child-slot declarations, the layout store seat,
|
|
131
|
+
* and the inject hook that hands the store's bound actions to the service.
|
|
132
|
+
* @param ctx - client root context.
|
|
133
|
+
*/
|
|
134
|
+
export declare function apply(ctx: ClientContext): void;
|
|
135
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LayoutController: the cross-plugin panel-action face behind ctx.layout.
|
|
3
|
+
* Panel geometry itself lives in the root entry's layout store (stores.ts);
|
|
4
|
+
* the current-session selection lives with the runtime sessions service, and
|
|
5
|
+
* the per-session active view dissolved into ui-conversation's session store
|
|
6
|
+
* (its only consumer). What remains here is the contract other plugins'
|
|
7
|
+
* apply worlds reach for panel transitions (sidebar toggle from ui-sidebar,
|
|
8
|
+
* right-panel show/hide from ui-sidebar-right) — writes stay inside the
|
|
9
|
+
* store's declared action set, delivered as the registration's bound actions.
|
|
10
|
+
*/
|
|
11
|
+
import type { BoundActions } from '@deepseek-ai/dsh-client-ui-slots';
|
|
12
|
+
import type { createLayoutStore } from './stores.ts';
|
|
13
|
+
/** The layout store's bound action set (framework-baked, draft params peeled). */
|
|
14
|
+
export type PanelActions = BoundActions<ReturnType<typeof createLayoutStore>>;
|
|
15
|
+
/**
|
|
16
|
+
* The two portal hosts a mounted desktop toolbar offers the session header:
|
|
17
|
+
* `centerHost` receives the title/lineage/actions/utilities cluster,
|
|
18
|
+
* `sessionEndHost` receives the rightbar corner so it stays the trailing
|
|
19
|
+
* item. Browser-local DOM elements only — never serialized, never sent
|
|
20
|
+
* through any Host RPC.
|
|
21
|
+
*/
|
|
22
|
+
export interface ToolbarHosts {
|
|
23
|
+
readonly centerHost: HTMLElement;
|
|
24
|
+
readonly sessionEndHost: HTMLElement;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The outward layout face (`ctx.layout`): the panel transitions other
|
|
28
|
+
* plugins may trigger, plus the toolbar host registry the session header
|
|
29
|
+
* portals into — and exactly what a test fake must supply. The
|
|
30
|
+
* attachPanels wiring hook stays on the concrete class (root-entry assembly
|
|
31
|
+
* only).
|
|
32
|
+
*/
|
|
33
|
+
export interface ILayout {
|
|
34
|
+
/** Toggle the sidebar panel (closed ⟷ contract default width). */
|
|
35
|
+
toggleSidebar(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Report the right panel's presentation without changing its expanded state.
|
|
38
|
+
* @param track - whether the normal panel width reserves a grid track,
|
|
39
|
+
* including beneath a fullscreen overlay.
|
|
40
|
+
* @param fullscreen - whether the panel covers the frame and hides its outer
|
|
41
|
+
* resize handle; independent of the underlying grid track.
|
|
42
|
+
*/
|
|
43
|
+
openRightbar(track: boolean, fullscreen: boolean): void;
|
|
44
|
+
/** Report the right panel as hidden: no track, no handle. */
|
|
45
|
+
closeRightbar(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Publish the desktop toolbar's freshly mounted host pair, replacing any
|
|
48
|
+
* previous registration and notifying subscribers.
|
|
49
|
+
* @param hosts - the mounted host pair (both nodes attached).
|
|
50
|
+
*/
|
|
51
|
+
setToolbarHosts(hosts: ToolbarHosts): void;
|
|
52
|
+
/**
|
|
53
|
+
* Release a previously published pair. Identity-guarded by reference
|
|
54
|
+
* equality: a stale HMR disposer releasing its OWN pair never clears a
|
|
55
|
+
* newer toolbar's registration.
|
|
56
|
+
* @param hosts - the exact pair this caller published.
|
|
57
|
+
*/
|
|
58
|
+
releaseToolbarHosts(hosts: ToolbarHosts): void;
|
|
59
|
+
/** The current host pair, or null while no desktop toolbar is mounted. */
|
|
60
|
+
getToolbarHosts(): ToolbarHosts | null;
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to toolbar-host changes (mount, remount, unmount).
|
|
63
|
+
* @param listener - called with the new pair, or null when the header must
|
|
64
|
+
* return to its in-place fallback.
|
|
65
|
+
* @returns the unsubscriber.
|
|
66
|
+
*/
|
|
67
|
+
onToolbarHosts(listener: (hosts: ToolbarHosts | null) => void): () => void;
|
|
68
|
+
}
|
|
69
|
+
/** Cross-plugin panel-action face (ctx.layout). */
|
|
70
|
+
export declare class LayoutController implements ILayout {
|
|
71
|
+
#private;
|
|
72
|
+
/**
|
|
73
|
+
* Adopt the root entry's bound store actions. Called from the root
|
|
74
|
+
* registration's inject hook (a sanctioned assembly side effect), so the
|
|
75
|
+
* face is live from the entry's first render; on entry re-register the
|
|
76
|
+
* fresh actions overwrite the stale set.
|
|
77
|
+
* @param actions - bound actions of the entry's layout store instance.
|
|
78
|
+
*/
|
|
79
|
+
attachPanels(actions: PanelActions): void;
|
|
80
|
+
/** Toggle the sidebar panel (closed ⟷ contract default width). */
|
|
81
|
+
toggleSidebar(): void;
|
|
82
|
+
/** Report the right panel's track and fullscreen presentation. */
|
|
83
|
+
openRightbar(track: boolean, fullscreen: boolean): void;
|
|
84
|
+
/** Report the right panel as hidden: no track, no handle. */
|
|
85
|
+
closeRightbar(): void;
|
|
86
|
+
/** Publish the desktop toolbar's host pair (identity replaces wholesale). */
|
|
87
|
+
setToolbarHosts(hosts: ToolbarHosts): void;
|
|
88
|
+
/** Release the pair only when the registry still holds THIS caller's pair. */
|
|
89
|
+
releaseToolbarHosts(hosts: ToolbarHosts): void;
|
|
90
|
+
/** The current host pair, or null while no desktop toolbar is mounted. */
|
|
91
|
+
getToolbarHosts(): ToolbarHosts | null;
|
|
92
|
+
/** Subscribe to toolbar-host changes; the header portals while non-null. */
|
|
93
|
+
onToolbarHosts(listener: (hosts: ToolbarHosts | null) => void): () => void;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root-owned frame measurement, panel preferences, and presentation reports.
|
|
3
|
+
* The registration supplies a fresh store and binds its actions to ctx.layout.
|
|
4
|
+
*/
|
|
5
|
+
import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
|
|
6
|
+
/**
|
|
7
|
+
* Transient layout preferences. Responsive concessions never rewrite widths;
|
|
8
|
+
* the right panel's expanded state belongs to its occupant.
|
|
9
|
+
*/
|
|
10
|
+
type LayoutState = {
|
|
11
|
+
sidebar: number;
|
|
12
|
+
/** Last positive frame measurement; window width bootstraps the first render. */
|
|
13
|
+
viewportWidth: number;
|
|
14
|
+
narrowExpanded: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Saved right panel width in px, or null before its first opening. Resizing
|
|
17
|
+
* the frame and closing the panel preserve this preference.
|
|
18
|
+
*/
|
|
19
|
+
rightbar: number | null;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the right panel is drawn at all, in either presentation.
|
|
22
|
+
*
|
|
23
|
+
* Derived chrome, not a source of truth: whether the right surface is
|
|
24
|
+
* expanded is a recorded fact owned by that surface, reported here so the
|
|
25
|
+
* frame can place the panel's resize handle. The occupant reports it; nothing
|
|
26
|
+
* else writes it.
|
|
27
|
+
*/
|
|
28
|
+
rightbarShown: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the normal panel width reserves a grid track, including beneath
|
|
31
|
+
* fullscreen. Reported by the occupant; always false while hidden.
|
|
32
|
+
*/
|
|
33
|
+
rightbarTrack: boolean;
|
|
34
|
+
/** Reported fullscreen presentation; hides the outer resize handle. */
|
|
35
|
+
rightbarFullscreen: boolean;
|
|
36
|
+
/** Suppress transitions for a fullscreen exit until another geometry action. */
|
|
37
|
+
rightbarInstant: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Annotation twin of the actions literal below (the export needs a declared
|
|
41
|
+
* return type); drift fails assignability at the defineStore call.
|
|
42
|
+
*/
|
|
43
|
+
type LayoutActions = {
|
|
44
|
+
setSidebar: (draft: LayoutState, px: number) => void;
|
|
45
|
+
toggleSidebar: (draft: LayoutState) => void;
|
|
46
|
+
setViewportWidth: (draft: LayoutState, width: number) => void;
|
|
47
|
+
setRightbar: (draft: LayoutState, px: number) => void;
|
|
48
|
+
openRightbar: (draft: LayoutState, track: boolean, fullscreen: boolean) => void;
|
|
49
|
+
closeRightbar: (draft: LayoutState) => void;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Create the layout panel store handle. For the sidebar the preference IS the
|
|
53
|
+
* width, so closing it forgets its drag width — reopening restores the contract
|
|
54
|
+
* default. The right panel initializes at 45% of the frame on first opening
|
|
55
|
+
* and keeps that px preference across resizes and close. Drag writes clamp to
|
|
56
|
+
* the current frame's range. Narrow sidebar toggles change only the expansion
|
|
57
|
+
* override; opening the right panel clears that override.
|
|
58
|
+
* @returns the store handle (spec + type + identity + factory in one).
|
|
59
|
+
*/
|
|
60
|
+
export declare function createLayoutStore(): EngineStoreHandle<LayoutState, LayoutActions>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=stores.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global theme DOM applier: projects the resolved ThemeSnapshot onto the
|
|
3
|
+
* document — `html { color-scheme }` for native UA chrome (scrollbars, form
|
|
4
|
+
* controls), `body[data-ds-dark-theme]` for the token palette, the active
|
|
5
|
+
* theme's alias-token overrides as inline CSS variables on body, the content
|
|
6
|
+
* font-size axis (`--dsh-content-font-size`), and one presenter-owned
|
|
7
|
+
* `meta[name="theme-color"]` for surrounding browser UI. Pure DOM writes, no
|
|
8
|
+
* React involvement; the presenter only ever retracts what it wrote itself,
|
|
9
|
+
* so foreign attributes, metadata, and inline styles survive.
|
|
10
|
+
*/
|
|
11
|
+
import type { ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client';
|
|
12
|
+
/** Body attribute selecting the dark base palette in the token stylesheets. */
|
|
13
|
+
export declare const DARK_ATTRIBUTE = "data-ds-dark-theme";
|
|
14
|
+
/** Body variable carrying the user's content font size in px. */
|
|
15
|
+
export declare const CONTENT_FONT_SIZE_VARIABLE = "--dsh-content-font-size";
|
|
16
|
+
/** Applies theme snapshots to the document; one instance per plugin fiber. */
|
|
17
|
+
export declare class ThemePresenter {
|
|
18
|
+
/** Token names this presenter wrote in the last apply (its retraction set). */
|
|
19
|
+
private appliedTokens;
|
|
20
|
+
/** The single metadata node this presenter inserts and removes. */
|
|
21
|
+
private readonly themeColorMeta;
|
|
22
|
+
/** Create the presenter-owned metadata node before the first snapshot arrives. */
|
|
23
|
+
constructor();
|
|
24
|
+
/**
|
|
25
|
+
* Project a snapshot onto the document: set root `color-scheme` and the body
|
|
26
|
+
* palette attribute from `active.colorScheme` (never the id — `system` is
|
|
27
|
+
* resolved upstream), publish the content font-size axis, then replace the
|
|
28
|
+
* previously applied token variables with `active.tokens`. Browser
|
|
29
|
+
* theme-color metadata follows the computed body background after those
|
|
30
|
+
* writes, so the rendered palette remains the color authority.
|
|
31
|
+
* @param snapshot - resolved theme snapshot from ctx.theme.
|
|
32
|
+
*/
|
|
33
|
+
apply(snapshot: ThemeSnapshot): void;
|
|
34
|
+
/** Retract root color-scheme, the palette attribute, token variables, the font-size axis, and the owned metadata node. */
|
|
35
|
+
dispose(): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=theme-presenter.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crazx/dsh-client-ui-layout",
|
|
3
|
+
"description": "Shell plugin: three-column AppFrame with drag handles, ctx.layout viewing-state service (navigation + panels)",
|
|
4
|
+
"version": "0.1.5-alpha.1.zw.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/aka-danielZhang/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/client/ui-layout"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./lib/types/client/index.d.ts",
|
|
23
|
+
"default": "./lib/client.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"dsh": {
|
|
29
|
+
"client": {
|
|
30
|
+
"inject": [
|
|
31
|
+
"@deepseek-ai/dsh-client-locale",
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-session",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-theme"
|
|
35
|
+
],
|
|
36
|
+
"platform": "web"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-client-store": "^0.1.5-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.1",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-theme": "^0.1.5-alpha.1",
|
|
50
|
+
"@types/react": "~18.3.1",
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
52
|
+
"react": "^18.2.0"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"lib/index.js",
|
|
56
|
+
"lib/client.js",
|
|
57
|
+
"lib/types/**/*.d.ts"
|
|
58
|
+
],
|
|
59
|
+
"scripts": {
|
|
60
|
+
"bundle": "tsdown",
|
|
61
|
+
"watch": "tsdown --watch"
|
|
62
|
+
}
|
|
63
|
+
}
|