dsh-conversation-navigator 0.2.2 → 0.2.4
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.en.md +6 -1
- package/README.md +6 -1
- package/lib/client.js +117 -7
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
7
|
+
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
7
8
|
[](https://github.com/gjj-star/dsh-conversation-navigator)
|
|
8
9
|
[](https://github.com/gjj-star/dsh-conversation-navigator)
|
|
9
10
|
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
@@ -36,6 +37,7 @@ Browser-only (no host behavior), plain JavaScript, zero build step, zero npm dep
|
|
|
36
37
|
- **Minimal-left**: the same strip pinned to the left edge of the conversation area (at the right border of the DSH sidebar), leaving the right side for dsh-better-sidebar
|
|
37
38
|
- The minimal strip collapses to the in-row indicator bars (current = solid brand color, others = 40% foreground-color mix); hovering expands a fixed 7-row positioning panel (click to jump, hover bubble for the full question, thumb-only scrollbar beyond 7 rows; right-aligned expands from right to left, left-aligned from left to right); a floating button above the bars keeps cycling forward (minimal-right → minimal-left → Full); the toggle yields while the search input is open
|
|
38
39
|
- **Draggable panel**: in Full / Hidden mode, grab the header to drag the panel anywhere in the viewport (auto-clamped to the edges; the expanded height is capped at half the viewport with the top edge fixed and the panel growing downward — expanding a huge turn scrolls the list internally while the "Back to latest / Collapse all" footer stays reachable); the pin button in the header toggles between the default right-dock and the last dragged position; the dragged position and the minimal left/right alignment survive page reloads via localStorage
|
|
40
|
+
- **Resizable panel**: drag from the bottom-left rounded corner (Full / Hidden modes only) — width 240–560 px, height vertical. Height follows a content-adaptive + cap model: actual height = min(turn content, the cap you drag to), tracking the number of turns with no blank space under the footer; floor ~3 turns, ceiling within the conversation viewport. The drag tracks the pointer 1:1 and is fully reversible; width/height persist in localStorage
|
|
39
41
|
- **Silky motion**: panel fade, step expand/collapse height transitions, staggered fade-in of filter results, rotating collapse arrow — all pure CSS, zero dependencies
|
|
40
42
|
- **Click to locate**: click a turn or a step to smooth-scroll the conversation to that exact position (fold state is preserved)
|
|
41
43
|
- **Load earlier / Load all**: two buttons on top — "Load earlier" pages one batch back, "Load all" loads every historical turn into the navigator for arbitrary jumps (the page itself stays lazily loaded until you click)
|
|
@@ -68,7 +70,9 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
68
70
|
|
|
69
71
|
## Updates
|
|
70
72
|
|
|
71
|
-
Edit `lib/client.js` and restart `dsh web`.
|
|
73
|
+
Edit `lib/client.js` and restart `dsh web`. Docked state, dragged position, minimal alignment and the panel width/height persist (localStorage `dsh-cnvnav:ui:v1`); expand/collapse, mode selection and search keywords still live only within the page session.
|
|
74
|
+
|
|
75
|
+
> Published to npm as `dsh-conversation-navigator` (the version badge above always shows the latest release); to upgrade an installed copy, update from the marketplace or run `dsh plugin --profile web add dsh-conversation-navigator` again, then restart.
|
|
72
76
|
|
|
73
77
|
## How it works
|
|
74
78
|
|
|
@@ -81,6 +85,7 @@ Edit `lib/client.js` and restart `dsh web`. Only the panel position, docked stat
|
|
|
81
85
|
- Hover full text: the turn-row bubble reads `fullDialogueText` (all text blocks of the user node joined), shown via `Tooltip` with a 340px width cap
|
|
82
86
|
- Modes: the header button cycles `viewMode` (full/hidden/minimal/minimal-left); hidden replaces the turn head title with a trajectory badge (`titleNode` strategy); minimal-right is docked by CSS `right`, minimal-left is pinned to `scrollport.left + 12` (the placement key includes `r.left` and a ResizeObserver on the chat area re-anchors it whenever the left sidebar expands or collapses); the toggle yields while the search input is open
|
|
83
87
|
- Drag & persistence: the full-panel header is draggable (pointer events, clamped to the viewport); dragging or the pin toggles `docked`; `place()` only clamps a freely-placed panel into the viewport instead of re-docking it; the position, docked state and alignment choice persist in localStorage (`dsh-cnvnav:ui:v1`)
|
|
88
|
+
- Resize: a transparent bottom-left corner grip captures pointer events; width writes `style.width` directly (240–560), height only adjusts `maxHeight` while `height` stays `auto`, so the actual height is always min(content, cap) with no blank space; dragging follows the start point linearly (`base + delta`) and shares the same container/viewport clamps as `place()` so nothing snaps on release; width/height persist as `panelWidth`/`panelHeight`
|
|
84
89
|
- Styling: `Button`/`Tooltip`/search & close icons reuse `@deepseek-ai/dsh-client-ui-primitives`, the rest are inline Lucide SVGs (ISC License); the panel injects its own `<style>` element, colors use `--dsw-*` theme tokens; everything is cleaned up with the fiber on unload
|
|
85
90
|
|
|
86
91
|
## Compatibility
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
7
|
+
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
7
8
|
[](https://github.com/gjj-star/dsh-conversation-navigator)
|
|
8
9
|
[](https://github.com/gjj-star/dsh-conversation-navigator)
|
|
9
10
|
[](https://www.npmjs.com/package/dsh-conversation-navigator)
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
- **显示 / 隐藏 / 极简四种形态**:头部切换按钮四态循环——「显示轮次」为经典分组视图(压缩等系统事件与轮次同级加粗展示)→「隐藏轮次」每行统一为轨迹徽标 + 文本(用户 = 业务蓝、助手 = 紫罗兰、压缩 = 中性灰)→「极简·右」视口右缘指示条→「极简·左」锚定对话区左缘(为 dsh-better-sidebar 让出右侧),再回到「显示轮次」;
|
|
33
34
|
- 「极简」形态收起时只露行内指示条(当前 = 品牌色实色、非当前 = 主题前景色掺 40%),悬停展开 7 行画幅的定位面板(点击跳转、悬停全文气泡、超 7 条仅滑块滚动;右对齐从右向左展开、左对齐向右展开),外置悬浮按钮继续循环(极简·右→极简·左→显示轮次),搜索框展开时按钮自动让位
|
|
34
35
|
- **面板可拖动**:显示轮次 / 隐藏轮次面板按住头部即可拖到视口任意位置(自动夹边;展开高度至多半屏,上边缘固定、下边缘展开,超长轮次时列表内部滚动,底部「回到最新/全部折叠」始终可达),头部图钉按钮在「默认右侧停靠」与「上次拖动位置」间切换;拖动位置与极简左/右对齐选择持久化于 localStorage,刷新页面原样恢复
|
|
36
|
+
- **面板尺寸可调**:左下角圆角处拖拽缩放(仅显示轮次 / 隐藏轮次)——横向拖宽(240–560px),纵向拖高/拖矮;高度采用「内容自适应 + 上限」模型:实际高度 = min(轮次内容, 你拖到的上限),随轮数变化、不会在底部产生空白,下限约 3 轮,上限不超过会话可视区;拖拽 1:1 线性跟随、可逆,尺寸持久化于 localStorage
|
|
35
37
|
- **丝滑动效**:面板开合淡入淡出、步次展开/折叠高度过渡、过滤结果逐条级联淡入、折叠箭头旋转——全部纯 CSS 实现,零依赖
|
|
36
38
|
- **点击定位**:点击轮次主体或步次条目,平滑滚动跳转到对话中对应位置(不改变折叠状态)
|
|
37
39
|
- **加载更早 / 加载全部**:面板顶部两个按钮——「加载更早」向后翻一页、「加载全部」一键把所有历史轮次载入导航,之后可任意跳转(页面默认仍懒加载,只有点按钮才补载)
|
|
@@ -64,7 +66,9 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
64
66
|
|
|
65
67
|
## 更新
|
|
66
68
|
|
|
67
|
-
修改 `lib/client.js` 后重启 `dsh web`
|
|
69
|
+
修改 `lib/client.js` 后重启 `dsh web` 即可;停靠状态、拖动位置、极简左/右对齐与面板宽高持久化于 localStorage(`dsh-cnvnav:ui:v1`),展开/折叠、模式、搜索关键词等界面状态仍只存于页面会话内。
|
|
70
|
+
|
|
71
|
+
> 已发布到 npm(`dsh-conversation-navigator`,徽章实时显示最新版本);升级已安装副本:市场更新或 `dsh plugin --profile web add dsh-conversation-navigator` 后重启。
|
|
68
72
|
|
|
69
73
|
## 工作原理
|
|
70
74
|
|
|
@@ -77,6 +81,7 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
77
81
|
- 悬停全文:轮次头气泡读取 `fullDialogueText`(用户节点全部文本块拼接),用 `Tooltip` 展示并限宽 340px
|
|
78
82
|
- 显示/隐藏/极简模式:`viewMode` 四态循环(full/hidden/minimal/minimal-left),隐藏时仅把轮次头标题替换为轨迹徽标(`titleNode` 策略);极简右对齐由 CSS `right` 锚定视口右缘、左对齐以 `scrollport.left + 12` 钉在对话区左缘(定位 key 含 `r.left`,并用 ResizeObserver 监听对话区宽度变化,左侧栏展开/折叠时紧贴跟随);搜索框展开时按钮让位
|
|
79
83
|
- 拖动定位与持久化:面板头部拖拽(pointer events,视口夹边),拖动或图钉切换 `docked` 状态;`place()` 对自由放置的面板只做视口夹边、不再自动停靠;位置、停靠状态与对齐选择存于 localStorage(`dsh-cnvnav:ui:v1`)
|
|
84
|
+
- 拖拽缩放:左下角透明热区捕获 pointer 事件;宽度直接写 `style.width`(240–560),高度只调 `maxHeight` 并保持 `height:auto`,使实际高度恒为 min(内容, 上限) 而不产生空白;拖动以起点为基准线性跟随(`base + 位移`),上限与 `place()` 的容器/视口钳制同源,松手零回弹;宽高随 `panelWidth`/`panelHeight` 持久化
|
|
80
85
|
- 样式:`Button`/`Tooltip`/搜索与关闭图标复用 `@deepseek-ai/dsh-client-ui-primitives`,其余图标为 Lucide 内联 SVG(ISC License);面板容器自建 `<style>` 注入,颜色使用 `--dsw-*` 主题 token;插件卸载时随 fiber 清理
|
|
81
86
|
|
|
82
87
|
## 兼容性说明
|
package/lib/client.js
CHANGED
|
@@ -116,6 +116,15 @@ window.__ModuleLoader__.load({
|
|
|
116
116
|
border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35));
|
|
117
117
|
}
|
|
118
118
|
.cnvnav-count { color: var(--dsw-alias-label-secondary, #888); font-size: 11px; }
|
|
119
|
+
/* 左下角拖拽缩放热区(普通面板): 仅圆角区域可拖, 无可见手柄 */
|
|
120
|
+
.cnvnav-resize-grip {
|
|
121
|
+
position: absolute; left: 0; bottom: 0;
|
|
122
|
+
width: 14px; height: 14px;
|
|
123
|
+
cursor: nesw-resize;
|
|
124
|
+
border-bottom-left-radius: 12px;
|
|
125
|
+
touch-action: none;
|
|
126
|
+
background: transparent;
|
|
127
|
+
}
|
|
119
128
|
.cnvnav-search { flex: none; display: flex; }
|
|
120
129
|
.cnvnav-turns-toggle { flex: none; display: flex; }
|
|
121
130
|
.cnvnav-close { margin-left: auto; }
|
|
@@ -361,6 +370,8 @@ window.__ModuleLoader__.load({
|
|
|
361
370
|
const out = {};
|
|
362
371
|
if (VIEW_MODES.indexOf(o.viewMode) !== -1) out.viewMode = o.viewMode;
|
|
363
372
|
if (o.docked === true || o.docked === false) out.docked = o.docked;
|
|
373
|
+
if (typeof o.panelWidth === "number" && o.panelWidth >= 240 && o.panelWidth <= 560) out.panelWidth = o.panelWidth;
|
|
374
|
+
if (typeof o.panelHeight === "number" && o.panelHeight >= 90 && o.panelHeight <= 2000) out.panelHeight = o.panelHeight;
|
|
364
375
|
if (o.userPos && typeof o.userPos.left === "number" && typeof o.userPos.top === "number"
|
|
365
376
|
&& Number.isFinite(o.userPos.left) && Number.isFinite(o.userPos.top)) {
|
|
366
377
|
out.userPos = { left: o.userPos.left, top: o.userPos.top };
|
|
@@ -373,7 +384,7 @@ window.__ModuleLoader__.load({
|
|
|
373
384
|
function persistUi() {
|
|
374
385
|
try {
|
|
375
386
|
window.localStorage.setItem(UI_KEY, JSON.stringify({
|
|
376
|
-
viewMode: state.viewMode, docked: state.docked, userPos: state.userPos,
|
|
387
|
+
viewMode: state.viewMode, docked: state.docked, userPos: state.userPos, panelWidth: state.panelWidth, panelHeight: state.panelHeight,
|
|
377
388
|
}));
|
|
378
389
|
} catch (err) { /* 隐私模式等场景忽略 */ }
|
|
379
390
|
}
|
|
@@ -381,7 +392,7 @@ window.__ModuleLoader__.load({
|
|
|
381
392
|
let state = Object.assign({
|
|
382
393
|
open: true, sessionId: null, outline: null, activeKey: null, expanded: {},
|
|
383
394
|
hasMore: false, loadingOlder: false, loadingAll: false,
|
|
384
|
-
viewMode: "full", docked: true, userPos: null,
|
|
395
|
+
viewMode: "full", docked: true, userPos: null, panelWidth: 288, panelHeight: null,
|
|
385
396
|
}, savedUi);
|
|
386
397
|
const listeners = new Set();
|
|
387
398
|
function setState(patch) {
|
|
@@ -665,6 +676,7 @@ window.__ModuleLoader__.load({
|
|
|
665
676
|
let panelEl = null;
|
|
666
677
|
let listEl = null;
|
|
667
678
|
let dragMove = null;
|
|
679
|
+
let resizeStart = null; /* { x, y, w, h, docked } for the corner grip */
|
|
668
680
|
const itemEls = {};
|
|
669
681
|
const groupEls = {};
|
|
670
682
|
|
|
@@ -759,6 +771,82 @@ window.__ModuleLoader__.load({
|
|
|
759
771
|
setState({ docked: false, userPos: { left: Math.round(r.left), top: Math.round(r.top) } });
|
|
760
772
|
persistUi();
|
|
761
773
|
}
|
|
774
|
+
/* ---------- 左下角拖拽缩放(仅普通面板) ---------- */
|
|
775
|
+
/* 语义化最小高度: 固定区(头部/顶部按钮栏/底部栏) + 至少 3 行轮次 + 内边距余量,
|
|
776
|
+
不用固定 px, 任何主题/字号下都能看到对话内容 */
|
|
777
|
+
function minPanelH() {
|
|
778
|
+
const p = panelEl;
|
|
779
|
+
if (p === null) return 240;
|
|
780
|
+
let fixed = 0;
|
|
781
|
+
const head = p.querySelector(".cnvnav-head");
|
|
782
|
+
const topbar = p.querySelector(".cnvnav-topbar");
|
|
783
|
+
const footer = p.querySelector(".cnvnav-footer");
|
|
784
|
+
if (head !== null) fixed += head.offsetHeight;
|
|
785
|
+
if (topbar !== null) fixed += topbar.offsetHeight;
|
|
786
|
+
if (footer !== null) fixed += footer.offsetHeight;
|
|
787
|
+
const row = p.querySelector(".cnvnav-group-row") || p.querySelector(".cnvnav-item");
|
|
788
|
+
const rowH = row !== null ? row.offsetHeight : 34;
|
|
789
|
+
return Math.ceil(fixed + rowH * 3 + 26);
|
|
790
|
+
}
|
|
791
|
+
function startResize(e) {
|
|
792
|
+
if (e.button !== 0) return;
|
|
793
|
+
if (resizeStart !== null) return;
|
|
794
|
+
const el = panelEl;
|
|
795
|
+
if (el === null) return;
|
|
796
|
+
resizeStart = {
|
|
797
|
+
startX: e.clientX, startY: e.clientY,
|
|
798
|
+
baseW: el.offsetWidth || 288,
|
|
799
|
+
baseH: el.offsetHeight || 200,
|
|
800
|
+
baseMaxH: (function () { const v = parseInt(el.style.maxHeight, 10); return Number.isFinite(v) && v > 0 ? v : (el.offsetHeight || 200); })(),
|
|
801
|
+
el: el,
|
|
802
|
+
};
|
|
803
|
+
try { e.currentTarget.setPointerCapture(e.pointerId); } catch (err) { /* 忽略 */ }
|
|
804
|
+
}
|
|
805
|
+
function moveResize(e) {
|
|
806
|
+
const d = resizeStart;
|
|
807
|
+
if (d === null) return;
|
|
808
|
+
const vh = document.documentElement.clientHeight || 800;
|
|
809
|
+
const dx = e.clientX - d.startX;
|
|
810
|
+
const dy = e.clientY - d.startY;
|
|
811
|
+
/* 高度用 maxHeight + height:auto: 实际高度 = min(内容, 上限)。
|
|
812
|
+
以拖拽起点为基准线性跟随鼠标(无漂移/无状态跳变):
|
|
813
|
+
拖小立即压缩出滚动, 拖大在内容充足时线性恢复更多行;
|
|
814
|
+
内容不足时面板保持内容高(拖动无感, 属预期)。 */
|
|
815
|
+
const r = d.el.getBoundingClientRect();
|
|
816
|
+
const sp = document.querySelector("[data-conversation-scroll]");
|
|
817
|
+
/* 上限必须与 place() 各分支一致, 否则松手后会被 place 压回造成跳变:
|
|
818
|
+
docked/常规 = min(容器高-24, vh-40); 自由位置 = vh-顶-8 */
|
|
819
|
+
const sr = sp !== null ? sp.getBoundingClientRect() : null;
|
|
820
|
+
const cap = Math.max(minPanelH(), Math.round(
|
|
821
|
+
state.docked && sr !== null
|
|
822
|
+
? Math.min(sr.height - 24, vh - 40)
|
|
823
|
+
: vh - r.top - 8
|
|
824
|
+
));
|
|
825
|
+
const nw = Math.min(Math.max(240, d.baseW - dx), 560);
|
|
826
|
+
/* 目标上限 = 起点可见高 + 总位移, 线性且可逆 */
|
|
827
|
+
const nMaxH = Math.min(Math.max(minPanelH(), d.baseH + dy), cap);
|
|
828
|
+
const left = Math.max(8, r.right - nw);
|
|
829
|
+
d.el.style.width = Math.round(nw) + "px";
|
|
830
|
+
d.el.style.left = Math.round(left) + "px";
|
|
831
|
+
d.el.style.height = "";
|
|
832
|
+
d.el.style.maxHeight = Math.round(nMaxH) + "px";
|
|
833
|
+
}
|
|
834
|
+
function endResize() {
|
|
835
|
+
const d = resizeStart;
|
|
836
|
+
resizeStart = null;
|
|
837
|
+
if (d === null) return;
|
|
838
|
+
const el = d.el;
|
|
839
|
+
const rect = el.getBoundingClientRect();
|
|
840
|
+
const nw = el.offsetWidth;
|
|
841
|
+
const parsed = parseInt(el.style.maxHeight, 10);
|
|
842
|
+
const nh = Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
843
|
+
if (state.docked) {
|
|
844
|
+
setState({ panelWidth: Math.round(nw), panelHeight: nh });
|
|
845
|
+
} else {
|
|
846
|
+
setState({ panelWidth: Math.round(nw), panelHeight: nh, userPos: { left: Math.round(rect.left), top: Math.round(rect.top) } });
|
|
847
|
+
}
|
|
848
|
+
persistUi();
|
|
849
|
+
}
|
|
762
850
|
|
|
763
851
|
React.useEffect(() => {
|
|
764
852
|
if (!snap.open) return;
|
|
@@ -773,20 +861,26 @@ window.__ModuleLoader__.load({
|
|
|
773
861
|
const vh = document.documentElement.clientHeight || 800;
|
|
774
862
|
const mini = viewMode === "minimal" || viewMode === "minimal-left";
|
|
775
863
|
const alignLeft = viewMode === "minimal-left";
|
|
864
|
+
const width = (state.panelWidth || 288);
|
|
865
|
+
/* 面板宽度对非极简模式统一生效, 确保拖动(未停靠)状态下也能实时刷新 */
|
|
866
|
+
if (!mini) panel.style.width = width + "px";
|
|
776
867
|
/* 面板被拖走后不再自动停靠: 夹进视口、上边缘固定, 展开高度至多半屏以便下方留白 */
|
|
777
868
|
if (!mini && !state.docked && state.userPos !== null) {
|
|
778
869
|
const w = panel.offsetWidth || 288;
|
|
779
870
|
const h = panel.offsetHeight || 200;
|
|
780
871
|
const left = Math.min(Math.max(8, Math.round(state.userPos.left)), Math.max(8, vw - w - 8));
|
|
781
872
|
const top = Math.min(Math.max(8, Math.round(state.userPos.top)), Math.max(8, vh - h - 8));
|
|
782
|
-
|
|
873
|
+
const fh = state.panelHeight;
|
|
874
|
+
const cap = Math.max(minPanelH(), Math.round(vh - top - 8));
|
|
875
|
+
const def = Math.max(minPanelH(), Math.round(Math.min(vh / 2, vh - top - 8)));
|
|
876
|
+
panel.style.height = "";
|
|
877
|
+
panel.style.maxHeight = Math.max(minPanelH(), Math.min(typeof fh === "number" && fh > 0 ? fh : def, cap)) + "px";
|
|
783
878
|
panel.style.left = left + "px";
|
|
784
879
|
panel.style.top = top + "px";
|
|
785
880
|
panel.classList.remove("cnvnav-panel-unplaced");
|
|
786
881
|
return true;
|
|
787
882
|
}
|
|
788
883
|
const sp = document.querySelector("[data-conversation-scroll]");
|
|
789
|
-
const width = 288;
|
|
790
884
|
const MINI_H = 220;
|
|
791
885
|
if (sp === null) {
|
|
792
886
|
/* 会话视图可能尚未挂载(刷新/重启/切会话时面板先就位);
|
|
@@ -808,7 +902,11 @@ window.__ModuleLoader__.load({
|
|
|
808
902
|
} else {
|
|
809
903
|
panel.style.left = Math.max(8, vw - width - 12) + "px";
|
|
810
904
|
panel.style.top = Math.max(8, 60) + "px";
|
|
811
|
-
|
|
905
|
+
const fh = state.panelHeight;
|
|
906
|
+
const cap = Math.round(vh - 90);
|
|
907
|
+
const def = cap;
|
|
908
|
+
panel.style.height = "";
|
|
909
|
+
panel.style.maxHeight = Math.max(minPanelH(), Math.min(typeof fh === "number" && fh > 0 ? fh : def, cap)) + "px";
|
|
812
910
|
}
|
|
813
911
|
panel.classList.remove("cnvnav-panel-unplaced");
|
|
814
912
|
return true;
|
|
@@ -835,7 +933,11 @@ window.__ModuleLoader__.load({
|
|
|
835
933
|
} else {
|
|
836
934
|
panel.style.left = Math.max(8, vw - width - 12) + "px";
|
|
837
935
|
panel.style.top = Math.max(8, Math.round(r.top + 8)) + "px";
|
|
838
|
-
|
|
936
|
+
const fh = state.panelHeight;
|
|
937
|
+
const cap = Math.max(minPanelH(), Math.round(Math.min(r.height - 24, vh - 40)));
|
|
938
|
+
const def = cap;
|
|
939
|
+
panel.style.height = "";
|
|
940
|
+
panel.style.maxHeight = Math.max(minPanelH(), Math.min(typeof fh === "number" && fh > 0 ? fh : def, cap)) + "px";
|
|
839
941
|
}
|
|
840
942
|
}
|
|
841
943
|
panel.classList.remove("cnvnav-panel-unplaced");
|
|
@@ -866,7 +968,7 @@ window.__ModuleLoader__.load({
|
|
|
866
968
|
window.removeEventListener("resize", place);
|
|
867
969
|
if (ro !== null) ro.disconnect();
|
|
868
970
|
};
|
|
869
|
-
}, [snap.open, snap.sessionId, viewMode, state.docked, state.userPos]);
|
|
971
|
+
}, [snap.open, snap.sessionId, viewMode, state.docked, state.userPos, state.panelWidth, state.panelHeight]);
|
|
870
972
|
|
|
871
973
|
React.useEffect(() => {
|
|
872
974
|
if (!snap.open) return;
|
|
@@ -1343,6 +1445,14 @@ window.__ModuleLoader__.load({
|
|
|
1343
1445
|
),
|
|
1344
1446
|
),
|
|
1345
1447
|
),
|
|
1448
|
+
React.createElement("span", {
|
|
1449
|
+
className: "cnvnav-resize-grip",
|
|
1450
|
+
onPointerDown: startResize,
|
|
1451
|
+
onPointerMove: moveResize,
|
|
1452
|
+
onPointerUp: endResize,
|
|
1453
|
+
onPointerCancel: endResize,
|
|
1454
|
+
"aria-hidden": true,
|
|
1455
|
+
}),
|
|
1346
1456
|
);
|
|
1347
1457
|
}
|
|
1348
1458
|
|