@celestia-island/hikari 0.40.33 → 0.40.34
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/package.json
CHANGED
|
@@ -29,6 +29,21 @@
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/* ONE SCROLLBAR PER WINDOW (2026-09-08 audit): inside a window surface's
|
|
33
|
+
* own scroller the bounded tree cap lit a second scrollbar under the
|
|
34
|
+
* window's — the window's scrollbar is the only one there and the tree
|
|
35
|
+
* grows with its content. Pages and standalone cards (the error landing,
|
|
36
|
+
* chat tool blocks outside windows) keep the bounded viewport above.
|
|
37
|
+
* The host's deferred overlay pass self-hides its rails once the
|
|
38
|
+
* content fits, so no attach-side change is needed. */
|
|
39
|
+
.hk-modal-body-scroll .s-tool-json-tree,
|
|
40
|
+
.hk-select-sheet-list .s-tool-json-tree,
|
|
41
|
+
.hk-select-popout .s-tool-json-tree,
|
|
42
|
+
.hk-popover-panel .s-tool-json-tree,
|
|
43
|
+
.hk-drawer-body .s-tool-json-tree {
|
|
44
|
+
max-height: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
.s-jt-row {
|
|
33
48
|
/* Baseline alignment keeps the small type hints (boolean/number/string)
|
|
34
49
|
on the value's text baseline instead of floating above it. */
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
+
/* ONE SCROLLBAR PER WINDOW (2026-09-08 audit): the body grows with its
|
|
2
|
+
* content and the HkModal body scroller is THE scrollbar. The old
|
|
3
|
+
* `bodyHeight`-driven nested scroll region (with its own overlay chrome)
|
|
4
|
+
* was removed as dead API — no consumer in the family ever passed it. */
|
|
1
5
|
.s-protocol-modal {
|
|
2
6
|
position: relative;
|
|
3
7
|
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/* With `bodyHeight` set the body becomes its own scroll region (nested
|
|
7
|
-
* inside HkModal's body scroller) — overlay chrome only. */
|
|
8
|
-
.s-protocol-modal {
|
|
9
|
-
overflow-y: auto;
|
|
10
|
-
scrollbar-width: none;
|
|
11
|
-
|
|
12
|
-
&::-webkit-scrollbar {
|
|
13
|
-
display: none;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, defineComponent,
|
|
1
|
+
import { computed, defineComponent, type PropType } from "vue";
|
|
2
2
|
import {
|
|
3
3
|
HMarkdownRenderer,
|
|
4
4
|
HModal,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from "@celestia-island/hikari";
|
|
10
10
|
|
|
11
11
|
import { useI18n } from "../i18n/context";
|
|
12
|
-
import { attachOverlayScrollbars, type OverlayScrollbarHandle } from "../composables/useOverlayScrollbar";
|
|
13
12
|
|
|
14
13
|
import "./HkProtocolModal.scss";
|
|
15
14
|
|
|
@@ -21,6 +20,12 @@ import "./HkProtocolModal.scss";
|
|
|
21
20
|
* a Decline/Accept footer. `accept`/`decline` are the caller's commit
|
|
22
21
|
* actions; closing (overlay click / ESC / X) only emits `update:modelValue`
|
|
23
22
|
* so the caller can decide whether close === decline.
|
|
23
|
+
*
|
|
24
|
+
* ONE SCROLLBAR PER WINDOW: the body grows with its content and the
|
|
25
|
+
* HkModal body scroller is THE scrollbar. The old `bodyHeight` prop
|
|
26
|
+
* (a height-capped scroll region nested inside the modal's own scroller —
|
|
27
|
+
* the exact double-scroll this library eliminates) was removed as dead
|
|
28
|
+
* API: no consumer in the family ever passed it.
|
|
24
29
|
*/
|
|
25
30
|
export const HkProtocolModal = defineComponent({
|
|
26
31
|
name: "HkProtocolModal",
|
|
@@ -37,8 +42,6 @@ export const HkProtocolModal = defineComponent({
|
|
|
37
42
|
/** Allow dismissing without a decision (overlay/ESC/X). Default true. */
|
|
38
43
|
closable: { type: Boolean, default: true },
|
|
39
44
|
width: { type: String as PropType<ModalWidth>, default: "48rem" },
|
|
40
|
-
/** Cap the scroll body height (e.g. "60vh"). */
|
|
41
|
-
bodyHeight: { type: String, default: undefined },
|
|
42
45
|
},
|
|
43
46
|
emits: {
|
|
44
47
|
"update:modelValue": (_v: boolean) => true,
|
|
@@ -69,32 +72,6 @@ export const HkProtocolModal = defineComponent({
|
|
|
69
72
|
},
|
|
70
73
|
]);
|
|
71
74
|
|
|
72
|
-
// ── overlay scrollbar (shared chrome) ─────────────────────────
|
|
73
|
-
// With `bodyHeight` set the body becomes a height-capped scroll
|
|
74
|
-
// region NESTED inside HkModal's overlay-equipped body scroller —
|
|
75
|
-
// without the overlay it would be a surviving native bar (the exact
|
|
76
|
-
// double-scroll this library eliminates). Attach on mount, re-sync
|
|
77
|
-
// when the cap flips, detach on unmount.
|
|
78
|
-
const bodyRef = ref<HTMLElement | null>(null);
|
|
79
|
-
let bodyScrollbar: OverlayScrollbarHandle | null = null;
|
|
80
|
-
function syncBodyScrollbar(): void {
|
|
81
|
-
if (props.bodyHeight && bodyRef.value) {
|
|
82
|
-
if (!bodyScrollbar) bodyScrollbar = attachOverlayScrollbars(bodyRef.value);
|
|
83
|
-
} else {
|
|
84
|
-
bodyScrollbar?.detach();
|
|
85
|
-
bodyScrollbar = null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function bindBodyRef(el: unknown): void {
|
|
89
|
-
bodyRef.value = (el as HTMLElement) ?? null;
|
|
90
|
-
syncBodyScrollbar();
|
|
91
|
-
}
|
|
92
|
-
watch(() => props.bodyHeight, () => syncBodyScrollbar());
|
|
93
|
-
onBeforeUnmount(() => {
|
|
94
|
-
bodyScrollbar?.detach();
|
|
95
|
-
bodyScrollbar = null;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
75
|
return () => (
|
|
99
76
|
<HModal
|
|
100
77
|
modelValue={props.modelValue}
|
|
@@ -104,11 +81,7 @@ export const HkProtocolModal = defineComponent({
|
|
|
104
81
|
closable={props.closable}
|
|
105
82
|
footerActions={footerActions.value}
|
|
106
83
|
>
|
|
107
|
-
<div
|
|
108
|
-
ref={bindBodyRef}
|
|
109
|
-
class="s-protocol-modal"
|
|
110
|
-
style={props.bodyHeight ? { maxHeight: props.bodyHeight } : undefined}
|
|
111
|
-
>
|
|
84
|
+
<div class="s-protocol-modal">
|
|
112
85
|
<HMarkdownRenderer content={props.content} />
|
|
113
86
|
</div>
|
|
114
87
|
</HModal>
|
|
@@ -114,6 +114,22 @@
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/* ONE SCROLLBAR PER WINDOW (2026-09-08 audit, HkJsonTree tool blocks and
|
|
118
|
+
code panes): inside a window surface's own scroller the bounded pane
|
|
119
|
+
cap lit a SECOND scrollbar under the window's — one per pane in pane
|
|
120
|
+
lists like the chest todo-log modal. There the window's scrollbar is
|
|
121
|
+
the only one and the pane grows with its content. Chat message cards
|
|
122
|
+
and pages are NOT window surfaces: the bounded viewport above stays.
|
|
123
|
+
(The panes' overlay rails self-hide once the content fits, so the
|
|
124
|
+
deferred attach pass needs no changes.) */
|
|
125
|
+
.hk-modal-body-scroll .s-tool-code,
|
|
126
|
+
.hk-select-sheet-list .s-tool-code,
|
|
127
|
+
.hk-select-popout .s-tool-code,
|
|
128
|
+
.hk-popover-panel .s-tool-code,
|
|
129
|
+
.hk-drawer-body .s-tool-code {
|
|
130
|
+
max-height: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
117
133
|
.s-tool-result {
|
|
118
134
|
border-left: 2px solid rgb(var(--color-success) / 40%);
|
|
119
135
|
padding-left: var(--space-8, 0.5rem);
|
|
@@ -167,6 +183,15 @@
|
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
|
|
186
|
+
/* Window-surface twin of the .s-tool-code rule above. */
|
|
187
|
+
.hk-modal-body-scroll .s-tool-code-block,
|
|
188
|
+
.hk-select-sheet-list .s-tool-code-block,
|
|
189
|
+
.hk-select-popout .s-tool-code-block,
|
|
190
|
+
.hk-popover-panel .s-tool-code-block,
|
|
191
|
+
.hk-drawer-body .s-tool-code-block {
|
|
192
|
+
max-height: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
.s-tool-code-table {
|
|
171
196
|
border-collapse: collapse;
|
|
172
197
|
width: 100%;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source contract: ONE SCROLLBAR PER WINDOW — tool panes (2026-09-08
|
|
3
|
+
* audit follow-up). The tool block's code panes and the shared JSON tree
|
|
4
|
+
* keep a bounded widget viewport in chat cards and on pages (the exempt
|
|
5
|
+
* content-widget class, like markdown code blocks), but INSIDE a window
|
|
6
|
+
* surface's own scroller (modal body, select sheet, popout, popover
|
|
7
|
+
* sheet/panel, drawer body) that cap lit a second scrollbar under the
|
|
8
|
+
* window's — one per pane in pane lists like the chest todo-log modal.
|
|
9
|
+
* There the window's scrollbar is the only one and the pane grows with
|
|
10
|
+
* its content.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const toolScss = readFileSync(join(here, "HkToolBlock.scss"), "utf-8");
|
|
19
|
+
const treeScss = readFileSync(join(here, "HkJsonTree.scss"), "utf-8");
|
|
20
|
+
const toolTsx = readFileSync(join(here, "HkToolBlock.tsx"), "utf-8");
|
|
21
|
+
|
|
22
|
+
/** Window-surface scrollers — the ONE-scrollbar owners. */
|
|
23
|
+
const SCROLLERS = [
|
|
24
|
+
".hk-modal-body-scroll",
|
|
25
|
+
".hk-select-sheet-list",
|
|
26
|
+
".hk-select-popout",
|
|
27
|
+
".hk-popover-panel",
|
|
28
|
+
".hk-drawer-body",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
const PANES = [".s-tool-code", ".s-tool-code-block", ".s-tool-json-tree"] as const;
|
|
32
|
+
|
|
33
|
+
/** Extract ONE balanced `{...}` declaration block following `selector`
|
|
34
|
+
* (a naive `[^}]*` would stop at the first nested `}` — see the
|
|
35
|
+
* HkAffixPicker single-scroll contract for the established pattern). */
|
|
36
|
+
function cssBlock(src: string, selector: string): string {
|
|
37
|
+
const at = src.indexOf(selector);
|
|
38
|
+
if (at < 0) return "";
|
|
39
|
+
const open = src.indexOf("{", at);
|
|
40
|
+
if (open < 0) return "";
|
|
41
|
+
let depth = 0;
|
|
42
|
+
for (let i = open; i < src.length; i++) {
|
|
43
|
+
if (src[i] === "{") depth++;
|
|
44
|
+
else if (src[i] === "}") {
|
|
45
|
+
depth--;
|
|
46
|
+
if (depth === 0) return src.slice(open, i + 1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("tool pane single-scrollbar-per-window contract", () => {
|
|
53
|
+
it("keeps the bounded widget viewport outside windows", () => {
|
|
54
|
+
expect(cssBlock(toolScss, ".s-tool-code ")).toContain("max-height: 240px");
|
|
55
|
+
expect(cssBlock(toolScss, ".s-tool-code-block ")).toContain("max-height: 240px");
|
|
56
|
+
expect(cssBlock(treeScss, ".s-tool-json-tree ")).toContain("max-height: 320px");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("drops the cap inside every window surface scroller", () => {
|
|
60
|
+
for (const pane of PANES) {
|
|
61
|
+
for (const scroller of SCROLLERS) {
|
|
62
|
+
// The uncap rule lives next to the pane's own file: tool panes in
|
|
63
|
+
// HkToolBlock.scss, the tree in HkJsonTree.scss. Search BOTH — a
|
|
64
|
+
// rule moved between the files must not fail the pin, a rule
|
|
65
|
+
// deleted anywhere must fail it.
|
|
66
|
+
const body =
|
|
67
|
+
cssBlock(toolScss, `${scroller} ${pane}`) ||
|
|
68
|
+
cssBlock(treeScss, `${scroller} ${pane}`);
|
|
69
|
+
expect(body, `${scroller} ${pane} uncap rule exists`).toBeTruthy();
|
|
70
|
+
expect(body, `${scroller} ${pane} uncaps via max-height: none`).toContain(
|
|
71
|
+
"max-height: none",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("no window-scroller context ever re-caps a pane with a length", () => {
|
|
78
|
+
for (const scss of [toolScss, treeScss]) {
|
|
79
|
+
for (const scroller of SCROLLERS) {
|
|
80
|
+
// Any scroller-descendant rule for a pane carrying a px/vh/rem
|
|
81
|
+
// max-height would reintroduce the nested scrollbar.
|
|
82
|
+
const re = new RegExp(
|
|
83
|
+
`${scroller.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} [^{]*\\{[^}]*max-height:\\s*(?!none)[\\d.]`,
|
|
84
|
+
);
|
|
85
|
+
expect(scss.match(re), `${scroller} descendant px cap`).toBeNull();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("the deferred overlay pass still targets the three panes (rails self-hide when uncapped)", () => {
|
|
91
|
+
expect(toolTsx).toContain('".s-tool-code, .s-tool-code-block, .s-tool-json-tree"');
|
|
92
|
+
});
|
|
93
|
+
});
|