@celestia-island/hikari 0.55.186 → 0.55.188
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
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
min-width: 0;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/* Double-class on purpose: the ghost chrome must beat the base
|
|
13
|
+
* `.hk-select-trigger` field chrome regardless of stylesheet import
|
|
14
|
+
* order (both would otherwise sit at one class of specificity). */
|
|
15
|
+
.hk-select-trigger.hk-header-picker-trigger {
|
|
13
16
|
width: auto;
|
|
14
17
|
/* Header-band compact: the button group beside it runs ~28px; the
|
|
15
18
|
* trigger stays a touch taller for its label but must not drive the
|
|
@@ -20,6 +23,28 @@
|
|
|
20
23
|
font-size: var(--text-sm);
|
|
21
24
|
font-weight: 600;
|
|
22
25
|
max-width: 16rem;
|
|
26
|
+
/* Ghost chrome (2026-10-18 user direction: the top-centre dropdown
|
|
27
|
+
* must read like the bottom-centre platform pill — no field border,
|
|
28
|
+
* hover tint only). Mirrors HkButton's ghost variant. */
|
|
29
|
+
background: transparent;
|
|
30
|
+
border-color: transparent;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.hk-select-trigger.hk-header-picker-trigger:hover:not([data-disabled]) {
|
|
34
|
+
background: var(--c-primary-light);
|
|
35
|
+
border-color: transparent;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.hk-select-trigger.hk-header-picker-trigger:active:not([data-disabled]) {
|
|
39
|
+
filter: brightness(0.95);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Keyboard / open ring survives — the ring rides the existing
|
|
43
|
+
* --shadow-focus; only the field border stays gone. */
|
|
44
|
+
.hk-select-trigger.hk-header-picker-trigger:focus-visible,
|
|
45
|
+
.hk-select-trigger.hk-header-picker-trigger[data-state="open"] {
|
|
46
|
+
background: var(--c-primary-light);
|
|
47
|
+
border-color: rgb(var(--color-focused-border));
|
|
23
48
|
}
|
|
24
49
|
|
|
25
50
|
.hk-header-picker-trigger .hk-select-value {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { createApp, defineComponent, h, ref } from "vue";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
|
|
4
7
|
import HkHeaderPicker, { type HkPickerOption } from "./HkHeaderPicker";
|
|
5
8
|
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* HkHeaderPicker — the header-centre selector (the button group's
|
|
8
13
|
* sibling). These pins hold the standardized contract: trigger chrome,
|
|
@@ -122,13 +127,30 @@ describe("HkHeaderPicker", () => {
|
|
|
122
127
|
expect(document.querySelectorAll('.hk-draggable-list-handle[data-hidden]').length).toBe(1);
|
|
123
128
|
});
|
|
124
129
|
|
|
125
|
-
it("declares the placement prop with the
|
|
130
|
+
it("declares the placement prop with the centered-down default (mirror-of-footer contract)", () => {
|
|
126
131
|
// The panel is teleported to body, so the through-wiring is pinned at
|
|
127
132
|
// the contract level: the prop exists, defaults to the header's
|
|
128
|
-
// downward growth
|
|
133
|
+
// downward growth CENTERED on the trigger (2026-10-18 user
|
|
134
|
+
// direction — the footer platform pill's symmetric pop, mirrored
|
|
135
|
+
// up top), and the render passes it straight through
|
|
129
136
|
// (`placement={props.placement}` in HkHeaderPicker.tsx).
|
|
130
137
|
expect(HkHeaderPicker.props!.placement).toBeDefined();
|
|
131
|
-
expect((HkHeaderPicker.props!.placement as { default: string }).default).toBe("bottom-
|
|
138
|
+
expect((HkHeaderPicker.props!.placement as { default: string }).default).toBe("bottom-center");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("keeps the popout content-sized and the trigger chrome ghost", () => {
|
|
142
|
+
// 2026-10-18 user direction: the header-centre dropdown must read
|
|
143
|
+
// like the bottom-centre platform pill — no field border, hover
|
|
144
|
+
// tint only, and a compact centered menu (not a stretched field
|
|
145
|
+
// panel). Pinned at source level like the popout-width contract.
|
|
146
|
+
const tsx = readFileSync(join(here, "HkHeaderPicker.tsx"), "utf-8");
|
|
147
|
+
expect(tsx).toContain("matchAnchorWidth={false}");
|
|
148
|
+
const scss = readFileSync(join(here, "HkHeaderPicker.scss"), "utf-8");
|
|
149
|
+
// The ghost override must be double-classed to beat the base
|
|
150
|
+
// `.hk-select-trigger` chrome regardless of import order.
|
|
151
|
+
expect(scss).toContain(".hk-select-trigger.hk-header-picker-trigger");
|
|
152
|
+
expect(scss).toContain("border-color: transparent;");
|
|
153
|
+
expect(scss).toContain("background: var(--c-primary-light);");
|
|
132
154
|
});
|
|
133
155
|
|
|
134
156
|
it("disables the trigger when disabled", () => {
|
|
@@ -75,10 +75,15 @@ export default defineComponent({
|
|
|
75
75
|
/** Desktop-popout side relative to the trigger. The header sits at
|
|
76
76
|
* the viewport's top, so the popout grows DOWNWARD (bottom-*) —
|
|
77
77
|
* the mirror of bottom-pinned pickers; auto-flip still rescues a
|
|
78
|
-
* viewport that cannot fit it.
|
|
78
|
+
* viewport that cannot fit it. CENTERED on the trigger by default
|
|
79
|
+
* (2026-10-18 user direction): the header pill mirrors the
|
|
80
|
+
* footer's platform pill, whose menu pops symmetrically on the
|
|
81
|
+
* trigger's midpoint — a start-anchored menu reads lopsided off a
|
|
82
|
+
* narrow pill. Callers with edge-pinned triggers may still pin a
|
|
83
|
+
* side explicitly. */
|
|
79
84
|
placement: {
|
|
80
85
|
type: String as PropType<SelectPanelPlacement>,
|
|
81
|
-
default: "bottom-
|
|
86
|
+
default: "bottom-center",
|
|
82
87
|
},
|
|
83
88
|
/** Accessible name of the trigger itself. */
|
|
84
89
|
ariaLabel: { type: String, default: undefined },
|
|
@@ -302,6 +307,10 @@ export default defineComponent({
|
|
|
302
307
|
title={props.panelTitle ?? ""}
|
|
303
308
|
placement={props.placement}
|
|
304
309
|
offset={4}
|
|
310
|
+
// The popout hugs its rows (not the trigger's width): the
|
|
311
|
+
// header pill is narrow, and the mirror-of-the-footer look
|
|
312
|
+
// means a compact centered menu, not a stretched field panel.
|
|
313
|
+
matchAnchorWidth={false}
|
|
305
314
|
sheetOnMobile
|
|
306
315
|
onKeydown={onPopoutKeydown}
|
|
307
316
|
>
|
|
@@ -69,6 +69,32 @@ describe("HkRowActions", () => {
|
|
|
69
69
|
expect(btn.getAttribute("aria-expanded")).toBe("false");
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
it("menu mode: a leaf row click emits select with the item and closes", async () => {
|
|
73
|
+
const items: HkMenuItem[] = [
|
|
74
|
+
{ key: "edit", label: "Edit" },
|
|
75
|
+
{ key: "delete", label: "Delete", danger: true },
|
|
76
|
+
];
|
|
77
|
+
const selected: HkMenuItem[] = [];
|
|
78
|
+
const c = mount(h(HkRowActions, { items, onSelect: (item: HkMenuItem) => selected.push(item) }));
|
|
79
|
+
const btn = c.querySelector("button.hk-icon-button") as HTMLButtonElement;
|
|
80
|
+
btn.click();
|
|
81
|
+
await Promise.resolve();
|
|
82
|
+
await Promise.resolve();
|
|
83
|
+
// The menu rows render through HkSelectPanel's teleported popout.
|
|
84
|
+
const row = Array.from(
|
|
85
|
+
document.body.querySelectorAll<HTMLButtonElement>("button.hk-menu-row"),
|
|
86
|
+
).find((b) => b.textContent?.trim() === "Edit");
|
|
87
|
+
expect(row).toBeTruthy();
|
|
88
|
+
row!.click();
|
|
89
|
+
await Promise.resolve();
|
|
90
|
+
await Promise.resolve();
|
|
91
|
+
expect(selected).toHaveLength(1);
|
|
92
|
+
expect(selected[0]!.key).toBe("edit");
|
|
93
|
+
expect(selected[0]!.label).toBe("Edit");
|
|
94
|
+
// The leaf path closed the popup inside HkMenu; aria follows.
|
|
95
|
+
expect(btn.getAttribute("aria-expanded")).toBe("false");
|
|
96
|
+
});
|
|
97
|
+
|
|
72
98
|
it("stops the click from reaching clickable ancestors", () => {
|
|
73
99
|
const outer = vi.fn();
|
|
74
100
|
const c = mount(
|
|
@@ -89,7 +89,11 @@ export default defineComponent({
|
|
|
89
89
|
emit("open", openerEl() ?? (e.currentTarget as HTMLElement));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function onSelect(item: HkMenuItem) {
|
|
92
|
+
function onSelect(key: string, item: HkMenuItem) {
|
|
93
|
+
// HkMenu's select emit is (key, item) — the key FIRST, matching the
|
|
94
|
+
// HkMenu contract; the consumer-facing select carries the item.
|
|
95
|
+
// The popup is already closed by HkMenu's leaf path (closeAll runs
|
|
96
|
+
// before the emit), so `open` only needs to follow on paper.
|
|
93
97
|
open.value = false;
|
|
94
98
|
emit("select", item);
|
|
95
99
|
}
|