@celestia-island/hikari 0.55.136 → 0.55.137
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
|
@@ -85,4 +85,96 @@ describe("HSelect mobile sheet", () => {
|
|
|
85
85
|
|
|
86
86
|
app.unmount();
|
|
87
87
|
});
|
|
88
|
+
|
|
89
|
+
it("consumes Escape while the panel is open so hosting dialogs stay put", async () => {
|
|
90
|
+
// The trigger commonly renders inside a dialog that closes on
|
|
91
|
+
// bubbled Escape (HkModal/HkDrawer). One press must close ONLY the
|
|
92
|
+
// dropdown; with the dropdown already closed the key bubbles on so
|
|
93
|
+
// the dialog's own Escape semantics are untouched.
|
|
94
|
+
setViewportWidth(1280);
|
|
95
|
+
const host = document.createElement("div");
|
|
96
|
+
document.body.appendChild(host);
|
|
97
|
+
const dialogKeydowns: KeyboardEvent[] = [];
|
|
98
|
+
host.addEventListener("keydown", (ev) => dialogKeydowns.push(ev as KeyboardEvent));
|
|
99
|
+
|
|
100
|
+
const app = createApp({
|
|
101
|
+
render: () =>
|
|
102
|
+
h("div", { onKeydown: () => {} }, [
|
|
103
|
+
h(HSelect, {
|
|
104
|
+
modelValue: "",
|
|
105
|
+
"onUpdate:modelValue": () => {},
|
|
106
|
+
options: [
|
|
107
|
+
{ value: "a", label: "Option A" },
|
|
108
|
+
{ value: "b", label: "Option B" },
|
|
109
|
+
],
|
|
110
|
+
}),
|
|
111
|
+
]),
|
|
112
|
+
});
|
|
113
|
+
app.mount(host);
|
|
114
|
+
await nextTick();
|
|
115
|
+
|
|
116
|
+
const trigger = host.querySelector<HTMLButtonElement>(".hk-select-trigger")!;
|
|
117
|
+
|
|
118
|
+
// Panel open: Escape closes it AND is consumed.
|
|
119
|
+
trigger.click();
|
|
120
|
+
await nextTick();
|
|
121
|
+
const escapeOpen = new KeyboardEvent("keydown", {
|
|
122
|
+
key: "Escape", bubbles: true, cancelable: true,
|
|
123
|
+
});
|
|
124
|
+
trigger.dispatchEvent(escapeOpen);
|
|
125
|
+
await nextTick();
|
|
126
|
+
expect(escapeOpen.defaultPrevented).toBe(true);
|
|
127
|
+
expect(dialogKeydowns).toHaveLength(0);
|
|
128
|
+
|
|
129
|
+
// Panel closed: the same key bubbles to the hosting dialog.
|
|
130
|
+
const escapeClosed = new KeyboardEvent("keydown", {
|
|
131
|
+
key: "Escape", bubbles: true, cancelable: true,
|
|
132
|
+
});
|
|
133
|
+
trigger.dispatchEvent(escapeClosed);
|
|
134
|
+
await nextTick();
|
|
135
|
+
expect(escapeClosed.defaultPrevented).toBe(false);
|
|
136
|
+
expect(dialogKeydowns).toHaveLength(1);
|
|
137
|
+
|
|
138
|
+
app.unmount();
|
|
139
|
+
host.remove();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("titles the opened surface from panelTitle without a visible label", async () => {
|
|
143
|
+
// Consumers whose visible label lives outside the field (a titled
|
|
144
|
+
// row) still need the mobile sheet header / popout a11y name; an
|
|
145
|
+
// untitled surface registers an unnamed blocking layer on phones.
|
|
146
|
+
setViewportWidth(375);
|
|
147
|
+
const el = document.createElement("div");
|
|
148
|
+
document.body.appendChild(el);
|
|
149
|
+
const app = createApp({
|
|
150
|
+
render: () =>
|
|
151
|
+
h(HSelect, {
|
|
152
|
+
modelValue: "",
|
|
153
|
+
"onUpdate:modelValue": () => {},
|
|
154
|
+
panelTitle: "What this screen shows",
|
|
155
|
+
options: [
|
|
156
|
+
{ value: "a", label: "Option A" },
|
|
157
|
+
{ value: "b", label: "Option B" },
|
|
158
|
+
],
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
app.mount(el);
|
|
162
|
+
await nextTick();
|
|
163
|
+
|
|
164
|
+
// No visible field label rendered above the trigger.
|
|
165
|
+
expect(document.querySelector(".hk-select-label")).toBeNull();
|
|
166
|
+
|
|
167
|
+
const trigger = document.querySelector<HTMLButtonElement>(".hk-select-trigger")!;
|
|
168
|
+
trigger.click();
|
|
169
|
+
await nextTick();
|
|
170
|
+
|
|
171
|
+
const title = document.querySelector<HTMLElement>(".hk-select-sheet-title");
|
|
172
|
+
expect(title?.textContent?.trim()).toBe("What this screen shows");
|
|
173
|
+
// The sheet panel is the a11y-named dialog for the surface.
|
|
174
|
+
const panel = document.querySelector<HTMLElement>(".hk-select-sheet-panel");
|
|
175
|
+
expect(panel?.getAttribute("aria-label")).toBe("What this screen shows");
|
|
176
|
+
|
|
177
|
+
app.unmount();
|
|
178
|
+
el.remove();
|
|
179
|
+
});
|
|
88
180
|
});
|
|
@@ -30,6 +30,15 @@ export default defineComponent({
|
|
|
30
30
|
props: {
|
|
31
31
|
modelValue: { type: String, default: "" },
|
|
32
32
|
label: { type: String, default: undefined },
|
|
33
|
+
/**
|
|
34
|
+
* Title for the OPENED surface only (mobile sheet header, desktop
|
|
35
|
+
* popout's a11y name, popup-manager breadcrumb layer) — without
|
|
36
|
+
* rendering the visible field `label`. Consumers whose visible label
|
|
37
|
+
* lives elsewhere (a titled row around the field) pass this so the
|
|
38
|
+
* surface is not registered untitled; resolution order is
|
|
39
|
+
* panelTitle, then `label`, then empty (the historic behaviour).
|
|
40
|
+
*/
|
|
41
|
+
panelTitle: { type: String, default: undefined },
|
|
33
42
|
placeholder: { type: String, default: "" },
|
|
34
43
|
error: { type: String, default: undefined },
|
|
35
44
|
disabled: { type: Boolean, default: false },
|
|
@@ -100,6 +109,18 @@ export default defineComponent({
|
|
|
100
109
|
normalizedOptions.value.length;
|
|
101
110
|
}
|
|
102
111
|
} else if (e.key === "Escape") {
|
|
112
|
+
// Consume the key ONLY when it actually closed something: the
|
|
113
|
+
// trigger commonly renders inside a dialog surface (HkModal and
|
|
114
|
+
// HkDrawer close on bubbled Escape), and one press must mean one
|
|
115
|
+
// thing — close the dropdown, not the dropdown AND the hosting
|
|
116
|
+
// dialog. The panel surface itself stops propagation for the
|
|
117
|
+
// same reason (HkSelectPanel's onSurfaceEscape); with the panel
|
|
118
|
+
// closed the key bubbles on as before, so the hosting dialog's
|
|
119
|
+
// own Escape semantics are untouched.
|
|
120
|
+
if (isOpen.value) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
}
|
|
103
124
|
isOpen.value = false;
|
|
104
125
|
}
|
|
105
126
|
}
|
|
@@ -182,7 +203,7 @@ export default defineComponent({
|
|
|
182
203
|
open={isOpen.value}
|
|
183
204
|
onUpdate:open={(v: boolean) => { isOpen.value = v; }}
|
|
184
205
|
anchorRef={triggerRef.value ?? null}
|
|
185
|
-
title={props.label ?? ""}
|
|
206
|
+
title={props.panelTitle ?? props.label ?? ""}
|
|
186
207
|
placement="bottom-start"
|
|
187
208
|
offset={4}
|
|
188
209
|
onKeydown={onPopoutKeydown}
|