@luziyang2026/dsh-question-nav 0.7.1 → 0.7.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/README.md +6 -4
- package/README.zh.md +4 -3
- package/lib/client.js +91 -127
- package/lib/client.js.map +1 -1
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/core/focus.d.ts +18 -29
- package/package.json +1 -1
- package/src/client/QuestionNavStrip.tsx +86 -145
- package/src/client/locales.ts +4 -0
- package/src/client/question-nav.module.css +66 -17
- package/src/core/focus.ts +27 -48
package/README.md
CHANGED
|
@@ -56,10 +56,12 @@ Package: **`@luziyang2026/dsh-question-nav`** ([npm][npm] · [GitHub][github]).
|
|
|
56
56
|
- **Click**: jumps to that question. Only then does the jump loop page the
|
|
57
57
|
window (`loadOlder()`) to bring that specific page into view — never the
|
|
58
58
|
whole history up front.
|
|
59
|
-
- **
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
- **Paged overflow**: when many dots don't fit the 60% band, the native
|
|
60
|
+
scrollbar is hidden and two small triangle buttons in the dot style appear —
|
|
61
|
+
**▲ above the dot queue and ▼ below it** — each click revealing five hidden
|
|
62
|
+
dots. The column fades at its top and bottom edges as a pure visual cue
|
|
63
|
+
(wheel and trackpad still scroll too); **hovering never scrolls the band**,
|
|
64
|
+
so the dots stay put while you browse.
|
|
63
65
|
- Empty/left areas of the rail pass pointer events through to the conversation
|
|
64
66
|
(it never blocks the chat).
|
|
65
67
|
|
package/README.zh.md
CHANGED
|
@@ -45,9 +45,10 @@
|
|
|
45
45
|
效果一致。
|
|
46
46
|
- **点击**:跳转到该提问;只有这时跳转循环才 `loadOlder()` 扩窗,把**那一页**
|
|
47
47
|
带进视图——绝不会一次性展开整个历史。
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
- **分页式溢出**:圆点太多超出 60% 限高时,原生滚动条被隐藏,点列上下会出现
|
|
49
|
+
两个与圆点同风格的三角按钮——**队列上方 ▲、队列下方 ▼**——每次点击翻出
|
|
50
|
+
5 个隐藏圆点。点列上下边缘的渐隐只是纯视觉提示(滚轮/触控板同样可滚);
|
|
51
|
+
**悬停绝不滚动点列**,浏览时点列保持不动。
|
|
51
52
|
- 圆点列空白区域**点穿**到对话内容,不会挡住聊天。
|
|
52
53
|
|
|
53
54
|
## 环境要求
|
package/lib/client.js
CHANGED
|
@@ -140,30 +140,13 @@ window.__ModuleLoader__.load({
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* dot — plus `clearance` room for its magnified neighbors — inside the
|
|
147
|
-
* visible band with the smallest possible movement, or null when the dot is
|
|
148
|
-
* already fully visible. Unlike unconditional centering this never shifts the
|
|
149
|
-
* band while the user browses dot-by-dot: only a clipped dot is scrolled.
|
|
150
|
-
*/
|
|
151
|
-
function minimalScrollIntoView(scrollTop, clientHeight, scrollHeight, dotTop, dotHeight, clearance = 50) {
|
|
152
|
-
const margin = Math.min(clearance, clientHeight / 4);
|
|
153
|
-
const viewTop = scrollTop + margin;
|
|
154
|
-
const viewBottom = scrollTop + clientHeight - margin;
|
|
155
|
-
if (dotTop >= viewTop && dotTop + dotHeight <= viewBottom) return null;
|
|
156
|
-
const next = dotTop < viewTop ? dotTop - margin : dotTop + dotHeight + margin - clientHeight;
|
|
157
|
-
return Math.max(0, Math.min(next, Math.max(0, scrollHeight - clientHeight)));
|
|
143
|
+
/** Scroll step (px) for one paging click: `rows` whole dot rows. */
|
|
144
|
+
function pageStep(dotSize = 8, gap = 6, rows = 5) {
|
|
145
|
+
return rows * (dotSize + gap);
|
|
158
146
|
}
|
|
159
|
-
/**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
* linearly from MIN to MAX so a shallow probe scrolls gently and pushing
|
|
163
|
-
* into the edge moves fast. The sign (direction) is applied by the caller.
|
|
164
|
-
*/
|
|
165
|
-
function edgeScrollSpeed(ratio) {
|
|
166
|
-
return 90 + 330 * Math.max(0, Math.min(1, ratio));
|
|
147
|
+
/** Clamp a target scrollTop into the band's valid range (0..maxScroll). */
|
|
148
|
+
function clampScrollTop(target, maxScroll) {
|
|
149
|
+
return Math.max(0, Math.min(target, Math.max(0, maxScroll)));
|
|
167
150
|
}
|
|
168
151
|
//#endregion
|
|
169
152
|
//#region src/core/time.ts
|
|
@@ -203,7 +186,7 @@ window.__ModuleLoader__.load({
|
|
|
203
186
|
}
|
|
204
187
|
//#endregion
|
|
205
188
|
//#region \0dsh-css:dsh-question-nav/src/client/question-nav.module.css.mjs
|
|
206
|
-
const css = ".TWf_pa_rail{z-index:1;box-sizing:border-box;pointer-events:none;background:0 0;flex-direction:column;align-items:center;width:44px;display:flex;position:absolute;top:0;bottom:0;left:0}.TWf_pa_railRight{left:auto}.
|
|
189
|
+
const css = ".TWf_pa_rail{z-index:1;box-sizing:border-box;pointer-events:none;background:0 0;flex-direction:column;align-items:center;width:44px;display:flex;position:absolute;top:0;bottom:0;left:0}.TWf_pa_railRight{left:auto}.TWf_pa_queue{box-sizing:border-box;pointer-events:none;flex-direction:column;flex:0 auto;align-items:center;gap:6px;width:100%;min-height:0;max-height:60%;margin:auto 0;display:flex}.TWf_pa_list{scrollbar-width:none;pointer-events:auto;flex-direction:column;flex:auto;align-items:center;gap:6px;width:100%;min-height:0;padding:8px 0;display:flex;overflow:hidden auto}.TWf_pa_list::-webkit-scrollbar{width:0;height:0;display:none}.TWf_pa_listScrollable{-webkit-mask-image:linear-gradient(#0000,#000 22px calc(100% - 22px),#0000);mask-image:linear-gradient(#0000,#000 22px calc(100% - 22px),#0000)}.TWf_pa_dot{pointer-events:auto;background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:50%;flex:none;width:8px;height:8px;padding:0;transition:transform .12s,background .12s}.TWf_pa_dot:hover,.TWf_pa_dot.TWf_pa_focused,.TWf_pa_dot.TWf_pa_active{background:var(--dsw-alias-brand-primary)}.TWf_pa_count{color:var(--dsw-alias-label-tertiary);user-select:none;flex:none;font-size:10px;font-weight:600;line-height:1}.TWf_pa_dots{flex-direction:column;align-items:center;gap:6px;display:flex}.TWf_pa_navBtn{pointer-events:auto;cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;width:14px;height:12px;padding:0;display:flex}.TWf_pa_navBtn:before{content:\"\";border-left:5px solid #0000;border-right:5px solid #0000;width:0;height:0;transition:border-color .12s;display:block}.TWf_pa_navUp:before{border-bottom:7px solid var(--dsw-alias-border-l3)}.TWf_pa_navDown:before{border-top:7px solid var(--dsw-alias-border-l3)}.TWf_pa_navUp:hover:before{border-bottom-color:var(--dsw-alias-brand-primary)}.TWf_pa_navDown:hover:before{border-top-color:var(--dsw-alias-brand-primary)}.TWf_pa_empty{color:var(--dsw-alias-label-tertiary);text-align:center;word-break:break-word;padding:10px 4px;font-size:11px}.TWf_pa_hint{z-index:30;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:260px;color:var(--dsw-alias-label-secondary);pointer-events:none;border-radius:6px;padding:6px 10px;font-size:12px;line-height:16px;position:fixed;box-shadow:0 2px 10px #0000002e}.TWf_pa_cascade{z-index:20;pointer-events:none;flex-direction:column;align-items:flex-start;gap:6px;display:flex;position:fixed}.TWf_pa_card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:380px;color:var(--dsw-alias-label-secondary);white-space:normal;word-break:break-word;pointer-events:auto;cursor:pointer;border-radius:10px;padding:8px 12px 10px;font-size:13px;line-height:18px;transition:border-color .12s,box-shadow .12s;box-shadow:0 1px 6px #0000001a}.TWf_pa_card:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary);box-shadow:0 3px 12px #0000002e}.TWf_pa_cardSelected{border-color:var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);box-shadow:inset 3px 0 0 0 var(--dsw-alias-brand-primary), 0 6px 20px #00000038}.TWf_pa_cardSelected:hover{border-color:var(--dsw-alias-brand-primary);box-shadow:inset 3px 0 0 0 var(--dsw-alias-brand-primary), 0 6px 22px #00000042}.TWf_pa_cardTitle{color:var(--dsw-alias-label-tertiary);margin-bottom:4px;font-size:11px;font-weight:600}.TWf_pa_cardBody{scrollbar-width:thin;max-height:96px;overflow-y:auto}.TWf_pa_cardClamp{word-break:break-word;-webkit-box-orient:vertical;font-size:13px;line-height:18px;display:-webkit-box;overflow:hidden}.TWf_pa_cardLine+.TWf_pa_cardLine{border-top:1px solid var(--dsw-alias-border-l1);margin-top:6px;padding-top:6px}.TWf_pa_cardTime{color:var(--dsw-alias-label-tertiary);margin-top:6px;font-size:11px}.TWf_pa_settings{flex-direction:column;gap:8px;padding:4px 0;display:flex}.TWf_pa_settingsTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:600}.TWf_pa_settingsDesc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.TWf_pa_segmented{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;align-self:flex-start;display:inline-flex;overflow:hidden}.TWf_pa_segment{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:8px 16px;font-size:13px;line-height:1;transition:background .12s,color .12s}.TWf_pa_segment+.TWf_pa_segment{border-left:1px solid var(--dsw-alias-border-l1)}.TWf_pa_segment:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.TWf_pa_segmentActive,.TWf_pa_segmentActive:hover{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-inverse)}";
|
|
207
190
|
const tagId = "@luziyang2026/dsh-question-nav/question-nav.module.css";
|
|
208
191
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
209
192
|
const tag = document.createElement("style");
|
|
@@ -230,6 +213,10 @@ window.__ModuleLoader__.load({
|
|
|
230
213
|
"hint": "TWf_pa_hint",
|
|
231
214
|
"list": "TWf_pa_list",
|
|
232
215
|
"listScrollable": "TWf_pa_listScrollable",
|
|
216
|
+
"navBtn": "TWf_pa_navBtn",
|
|
217
|
+
"navDown": "TWf_pa_navDown",
|
|
218
|
+
"navUp": "TWf_pa_navUp",
|
|
219
|
+
"queue": "TWf_pa_queue",
|
|
233
220
|
"rail": "TWf_pa_rail",
|
|
234
221
|
"railRight": "TWf_pa_railRight",
|
|
235
222
|
"segment": "TWf_pa_segment",
|
|
@@ -255,8 +242,13 @@ window.__ModuleLoader__.load({
|
|
|
255
242
|
* vertical cascade of question cards opens — the selected (center) card is the
|
|
256
243
|
* focus (brand accent, elevated, full question text), the four neighbors are
|
|
257
244
|
* narrower context cards clamped to fewer lines. Every card is clickable and
|
|
258
|
-
* jumps to its question, exactly like clicking the dot
|
|
259
|
-
*
|
|
245
|
+
* jumps to its question, exactly like clicking the dot. Hovering never scrolls
|
|
246
|
+
* the band — the dot column stays put while you browse.
|
|
247
|
+
*
|
|
248
|
+
* Overflow is paged, not auto-scrolled: two small triangle buttons in the dot
|
|
249
|
+
* style sit above and below the dot queue (▲ / ▼), each click revealing five
|
|
250
|
+
* hidden dots. The native scrollbar stays hidden and the column fades at its
|
|
251
|
+
* edges as a pure visual cue — no hover auto-scroll of any kind.
|
|
260
252
|
*
|
|
261
253
|
* Data source: the host-folded `questionIndex` session projection (whole
|
|
262
254
|
* history, persisted host-side, pushed live through session/projection
|
|
@@ -311,71 +303,29 @@ window.__ModuleLoader__.load({
|
|
|
311
303
|
const hintTimerRef = (0, react.useRef)(null);
|
|
312
304
|
const clearFocusTimerRef = (0, react.useRef)(null);
|
|
313
305
|
const lastDotsRef = (0, react.useRef)([]);
|
|
314
|
-
const lastFocusedKeyRef = (0, react.useRef)(null);
|
|
315
306
|
const [scrollable, setScrollable] = (0, react.useState)(false);
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
last: 0
|
|
307
|
+
const [scrollPos, setScrollPos] = (0, react.useState)({
|
|
308
|
+
top: 0,
|
|
309
|
+
max: 0
|
|
320
310
|
});
|
|
321
|
-
const
|
|
322
|
-
const cancelEdgeDwell = () => {
|
|
323
|
-
if (edgeDwellRef.current !== null) {
|
|
324
|
-
window.clearTimeout(edgeDwellRef.current);
|
|
325
|
-
edgeDwellRef.current = null;
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
const stopEdgeScroll = () => {
|
|
329
|
-
cancelEdgeDwell();
|
|
330
|
-
edgeScrollRef.current.speed = 0;
|
|
331
|
-
if (edgeScrollRef.current.raf !== 0) {
|
|
332
|
-
cancelAnimationFrame(edgeScrollRef.current.raf);
|
|
333
|
-
edgeScrollRef.current.raf = 0;
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
const edgeTick = (now) => {
|
|
311
|
+
const syncScroll = () => {
|
|
337
312
|
const list = listRef.current;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const atTop = list.scrollTop <= 0 && state.speed < 0;
|
|
345
|
-
const atBottom = list.scrollTop >= list.scrollHeight - list.clientHeight - 1 && state.speed > 0;
|
|
346
|
-
if (atTop || atBottom) {
|
|
347
|
-
state.speed = 0;
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
state.raf = requestAnimationFrame(edgeTick);
|
|
313
|
+
if (list === null) return;
|
|
314
|
+
const max = Math.max(0, list.scrollHeight - list.clientHeight);
|
|
315
|
+
setScrollPos({
|
|
316
|
+
top: list.scrollTop,
|
|
317
|
+
max
|
|
318
|
+
});
|
|
351
319
|
};
|
|
352
|
-
const
|
|
320
|
+
const pageBy = (dir) => {
|
|
353
321
|
const list = listRef.current;
|
|
354
322
|
if (list === null) return;
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const depthTop = 26 - y;
|
|
359
|
-
const depthBottom = y - (rect.height - 26);
|
|
360
|
-
let speed = 0;
|
|
361
|
-
if (!overDot) {
|
|
362
|
-
if (depthTop > 0 && list.scrollTop > 0) speed = -edgeScrollSpeed(depthTop / 26);
|
|
363
|
-
else if (depthBottom > 0 && list.scrollTop < list.scrollHeight - list.clientHeight - 1) speed = edgeScrollSpeed(depthBottom / 26);
|
|
364
|
-
}
|
|
365
|
-
const state = edgeScrollRef.current;
|
|
366
|
-
state.speed = speed;
|
|
367
|
-
if (speed === 0) {
|
|
368
|
-
stopEdgeScroll();
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (state.raf === 0 && edgeDwellRef.current === null) edgeDwellRef.current = window.setTimeout(() => {
|
|
372
|
-
edgeDwellRef.current = null;
|
|
373
|
-
if (edgeScrollRef.current.speed !== 0 && edgeScrollRef.current.raf === 0) {
|
|
374
|
-
edgeScrollRef.current.last = performance.now();
|
|
375
|
-
edgeScrollRef.current.raf = requestAnimationFrame(edgeTick);
|
|
376
|
-
}
|
|
377
|
-
}, 200);
|
|
323
|
+
const max = Math.max(0, list.scrollHeight - list.clientHeight);
|
|
324
|
+
list.scrollTop = clampScrollTop(list.scrollTop + dir * pageStep(), max);
|
|
325
|
+
syncScroll();
|
|
378
326
|
};
|
|
327
|
+
const canPageUp = scrollable && scrollPos.top > 0;
|
|
328
|
+
const canPageDown = scrollable && scrollPos.top < scrollPos.max;
|
|
379
329
|
const cancelClearFocus = () => {
|
|
380
330
|
if (clearFocusTimerRef.current !== null) {
|
|
381
331
|
window.clearTimeout(clearFocusTimerRef.current);
|
|
@@ -510,23 +460,13 @@ window.__ModuleLoader__.load({
|
|
|
510
460
|
window.removeEventListener("resize", wake);
|
|
511
461
|
};
|
|
512
462
|
}, [visible, align]);
|
|
513
|
-
(0, react.useLayoutEffect)(() => {
|
|
514
|
-
const key = focus?.key ?? null;
|
|
515
|
-
if (lastFocusedKeyRef.current === key) return;
|
|
516
|
-
lastFocusedKeyRef.current = key;
|
|
517
|
-
if (key === null) return;
|
|
518
|
-
if (edgeScrollRef.current.raf !== 0) return;
|
|
519
|
-
const list = listRef.current;
|
|
520
|
-
if (list === null) return;
|
|
521
|
-
const target = list.querySelector("[data-question-nav-focused=\"true\"]");
|
|
522
|
-
if (target === null) return;
|
|
523
|
-
const next = minimalScrollIntoView(list.scrollTop, list.clientHeight, list.scrollHeight, target.offsetTop - list.offsetTop, target.offsetHeight);
|
|
524
|
-
if (next !== null) list.scrollTop = next;
|
|
525
|
-
}, [focus]);
|
|
526
463
|
(0, react.useLayoutEffect)(() => {
|
|
527
464
|
const list = listRef.current;
|
|
528
465
|
if (list === null) return;
|
|
529
|
-
const check = () =>
|
|
466
|
+
const check = () => {
|
|
467
|
+
setScrollable(list.scrollHeight > list.clientHeight + 1);
|
|
468
|
+
syncScroll();
|
|
469
|
+
};
|
|
530
470
|
check();
|
|
531
471
|
if (typeof ResizeObserver === "undefined") return;
|
|
532
472
|
const observer = new ResizeObserver(check);
|
|
@@ -536,7 +476,6 @@ window.__ModuleLoader__.load({
|
|
|
536
476
|
(0, react.useEffect)(() => () => {
|
|
537
477
|
if (hintTimerRef.current !== null) window.clearTimeout(hintTimerRef.current);
|
|
538
478
|
if (clearFocusTimerRef.current !== null) window.clearTimeout(clearFocusTimerRef.current);
|
|
539
|
-
stopEdgeScroll();
|
|
540
479
|
}, []);
|
|
541
480
|
if (!visible) return null;
|
|
542
481
|
const onJump = (dot) => {
|
|
@@ -573,39 +512,60 @@ window.__ModuleLoader__.load({
|
|
|
573
512
|
"data-question-nav": "rail",
|
|
574
513
|
children: [
|
|
575
514
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
576
|
-
|
|
577
|
-
className: scrollable ? `${question_nav_module_css_default.list} ${question_nav_module_css_default.listScrollable}` : question_nav_module_css_default.list,
|
|
578
|
-
onMouseMove: scrollable ? onListMouseMove : void 0,
|
|
579
|
-
onMouseLeave: () => {
|
|
580
|
-
stopEdgeScroll();
|
|
581
|
-
scheduleClearFocus();
|
|
582
|
-
},
|
|
515
|
+
className: question_nav_module_css_default.queue,
|
|
583
516
|
children: dots.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
584
517
|
className: question_nav_module_css_default.empty,
|
|
585
518
|
children: t("strip.empty")
|
|
586
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
587
|
-
|
|
588
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
519
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
589
521
|
className: question_nav_module_css_default.count,
|
|
590
522
|
children: dots.length
|
|
591
|
-
}),
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
523
|
+
}),
|
|
524
|
+
scrollable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
525
|
+
type: "button",
|
|
526
|
+
className: `${question_nav_module_css_default.navBtn} ${question_nav_module_css_default.navUp}`,
|
|
527
|
+
"aria-label": t("strip.up"),
|
|
528
|
+
style: { visibility: canPageUp ? "visible" : "hidden" },
|
|
529
|
+
onMouseEnter: cancelClearFocus,
|
|
530
|
+
onMouseLeave: scheduleClearFocus,
|
|
531
|
+
onClick: () => pageBy(-1)
|
|
532
|
+
}) : null,
|
|
533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
534
|
+
ref: listRef,
|
|
535
|
+
className: scrollable ? `${question_nav_module_css_default.list} ${question_nav_module_css_default.listScrollable}` : question_nav_module_css_default.list,
|
|
536
|
+
onScroll: syncScroll,
|
|
537
|
+
onMouseLeave: scheduleClearFocus,
|
|
538
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
539
|
+
className: question_nav_module_css_default.dots,
|
|
540
|
+
children: dots.map((dot, index) => {
|
|
541
|
+
const isFocused = focus !== null && focus.key === dot.key;
|
|
542
|
+
const tier = selectedIndex < 0 ? null : focusTier(index - selectedIndex);
|
|
543
|
+
const scale = jumpingKey === dot.key ? 1.6 : tier !== null ? focusScale(tier) : 1;
|
|
544
|
+
const cls = [question_nav_module_css_default.dot];
|
|
545
|
+
if (isFocused) cls.push(question_nav_module_css_default.focused);
|
|
546
|
+
if (jumpingKey === dot.key) cls.push(question_nav_module_css_default.active);
|
|
547
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
548
|
+
className: cls.join(" "),
|
|
549
|
+
style: { transform: `scale(${scale})` },
|
|
550
|
+
"data-question-nav-focused": isFocused ? "true" : void 0,
|
|
551
|
+
"data-question-nav-index": index,
|
|
552
|
+
"aria-label": dot.texts[0] ?? "",
|
|
553
|
+
onMouseEnter: (e) => openFocus(dot, e.currentTarget),
|
|
554
|
+
onClick: () => onJump(dot)
|
|
555
|
+
}, dot.key);
|
|
556
|
+
})
|
|
557
|
+
})
|
|
558
|
+
}),
|
|
559
|
+
scrollable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
560
|
+
type: "button",
|
|
561
|
+
className: `${question_nav_module_css_default.navBtn} ${question_nav_module_css_default.navDown}`,
|
|
562
|
+
"aria-label": t("strip.down"),
|
|
563
|
+
style: { visibility: canPageDown ? "visible" : "hidden" },
|
|
564
|
+
onMouseEnter: cancelClearFocus,
|
|
565
|
+
onMouseLeave: scheduleClearFocus,
|
|
566
|
+
onClick: () => pageBy(1)
|
|
567
|
+
}) : null
|
|
568
|
+
] })
|
|
609
569
|
}),
|
|
610
570
|
hint !== null ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
611
571
|
className: question_nav_module_css_default.hint,
|
|
@@ -803,6 +763,8 @@ window.__ModuleLoader__.load({
|
|
|
803
763
|
*/
|
|
804
764
|
const zh = {
|
|
805
765
|
"strip.empty": "本会话还没有提问",
|
|
766
|
+
"strip.up": "向上翻出 5 个提问圆点",
|
|
767
|
+
"strip.down": "向下翻出 5 个提问圆点",
|
|
806
768
|
"jump.inactive": "聊天视图未激活",
|
|
807
769
|
"jump.hidden": "目标无独立气泡,已定位到邻近内容",
|
|
808
770
|
"jump.notfound": "目标未加载或不存在(可能已压缩)",
|
|
@@ -815,6 +777,8 @@ window.__ModuleLoader__.load({
|
|
|
815
777
|
};
|
|
816
778
|
const en = {
|
|
817
779
|
"strip.empty": "No questions in this session yet",
|
|
780
|
+
"strip.up": "Reveal 5 question dots above",
|
|
781
|
+
"strip.down": "Reveal 5 question dots below",
|
|
818
782
|
"jump.inactive": "Chat view is not active",
|
|
819
783
|
"jump.hidden": "No dedicated bubble; landed on nearby content",
|
|
820
784
|
"jump.notfound": "Target not loaded or missing (maybe compacted)",
|