@celestia-island/hikari 0.55.240 → 0.55.242
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 +1 -1
- package/src/components/HkAboutModal.scss +2 -1
- package/src/components/HkAlert.scss +3 -2
- package/src/components/HkAvatar.scss +3 -1
- package/src/components/HkBadge.scss +2 -2
- package/src/components/HkBlockingToast.scss +6 -6
- package/src/components/HkBoard.scss +3 -1
- package/src/components/HkBoard3D.scss +3 -1
- package/src/components/HkBreadcrumb.scss +2 -1
- package/src/components/HkButton.scss +3 -2
- package/src/components/HkCard.scss +2 -1
- package/src/components/HkCheckbox.scss +3 -1
- package/src/components/HkColorPicker.scss +5 -3
- package/src/components/HkConfirmDialog.scss +3 -2
- package/src/components/HkCopyBadge.test.tsx +1 -1
- package/src/components/HkCrossfade.test.tsx +48 -0
- package/src/components/HkCrossfade.tsx +45 -3
- package/src/components/HkDatePicker.scss +3 -2
- package/src/components/HkDateTimePicker.scss +2 -1
- package/src/components/HkDiffViewer.scss +6 -6
- package/src/components/HkDockBar.scss +4 -2
- package/src/components/HkDockBar.test.tsx +1 -1
- package/src/components/HkDraggableGrid.scss +4 -3
- package/src/components/HkDraggableList.scss +4 -3
- package/src/components/HkDrawer.scss +5 -4
- package/src/components/HkEmptyState.scss +3 -1
- package/src/components/HkEmptyState.surface.contract.test.ts +5 -2
- package/src/components/HkErrorLanding.scss +3 -1
- package/src/components/HkFab.scss +5 -2
- package/src/components/HkFaceShell.rail.test.tsx +322 -4
- package/src/components/HkFaceShell.tsx +14 -0
- package/src/components/HkImageLightbox.scss +3 -1
- package/src/components/HkImageViewer.scss +3 -1
- package/src/components/HkJsonTree.scss +3 -3
- package/src/components/HkLoadingVeil.scss +4 -2
- package/src/components/HkLogWindow.scss +2 -2
- package/src/components/HkLogo.scss +3 -1
- package/src/components/HkMarkdownRenderer.scss +3 -3
- package/src/components/HkMediaControlBar.scss +4 -2
- package/src/components/HkMediaSlider.scss +3 -1
- package/src/components/HkMenu.rail.test.tsx +29 -0
- package/src/components/HkMenu.tsx +62 -17
- package/src/components/HkMessageBox.scss +3 -2
- package/src/components/HkMinimap.scss +5 -3
- package/src/components/HkModal.scss +10 -11
- package/src/components/HkModal.sheetmorph.test.ts +8 -6
- package/src/components/HkModalBreadcrumb.blur.test.ts +3 -3
- package/src/components/HkModalBreadcrumb.scss +6 -4
- package/src/components/HkNavItem.scss +2 -1
- package/src/components/HkNodeCanvas.scss +3 -1
- package/src/components/HkOtpInput.scss +2 -1
- package/src/components/HkPasswordSurface.scss +4 -3
- package/src/components/HkPillToggleGroup.scss +13 -18
- package/src/components/HkPopover.scss +5 -6
- package/src/components/HkProgressRing.scss +3 -1
- package/src/components/HkRadio.scss +3 -1
- package/src/components/HkScrollContainer.fade.contract.test.ts +7 -2
- package/src/components/HkScrollContainer.scss +23 -46
- package/src/components/HkScrollPin.scss +3 -1
- package/src/components/HkSelect.scss +4 -5
- package/src/components/HkSelectionGrid.scss +3 -1
- package/src/components/HkSelectionWaterfall.scss +3 -1
- package/src/components/HkSidebar.rail.test.tsx +141 -3
- package/src/components/HkSidebar.scss +4 -1
- package/src/components/HkSidebar.tsx +15 -0
- package/src/components/HkSidebarRailSwap.tsx +20 -8
- package/src/components/HkSlider.scss +3 -1
- package/src/components/HkSpinner.scss +5 -2
- package/src/components/HkSwitch.scss +3 -1
- package/src/components/HkTable.interactions.test.tsx +4 -2
- package/src/components/HkTable.scss +8 -8
- package/src/components/HkTag.scss +3 -1
- package/src/components/HkTagInput.scss +5 -6
- package/src/components/HkTagInput.test.tsx +5 -4
- package/src/components/HkTemplateField.scss +2 -1
- package/src/components/HkTextarea.scss +3 -1
- package/src/components/HkThemeToggle.scss +2 -2
- package/src/components/HkTitleBar.scss +4 -3
- package/src/components/HkToast.scss +5 -4
- package/src/components/HkTreemapStage.scss +3 -1
- package/src/components/HkZoomToolbar.scss +3 -2
- package/src/components/_menu-item.contract.test.ts +3 -1
- package/src/components/_menu-item.scss +4 -3
- package/src/components/window-close.scss +4 -2
- package/src/composables/useMenuGroupState.ts +90 -0
- package/src/composables/useSidebarRail.test.ts +158 -1
- package/src/composables/useSidebarRail.ts +72 -4
- package/src/styles/admin-tokens.scss +4 -3
- package/src/styles/polyfill.contract.test.ts +294 -0
- package/src/styles/polyfill.scss +110 -0
- package/src/styles/selection.scss +4 -4
- package/src/theme/_field.scss +3 -1
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Use theme variables with namespace to avoid conflicts
|
|
2
2
|
@use "./hikari-vars" as vars;
|
|
3
|
+
@use "../styles/polyfill" as *;
|
|
3
4
|
|
|
4
5
|
.s-about-modal {
|
|
5
6
|
position: relative;
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
width: 3.75rem;
|
|
65
66
|
height: 3.75rem;
|
|
66
67
|
object-fit: contain;
|
|
67
|
-
user-select
|
|
68
|
+
@include user-select(none);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
.s-about-modal-logo {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "../styles/polyfill" as *;
|
|
2
|
+
|
|
1
3
|
.hk-alert {
|
|
2
4
|
display: flex;
|
|
3
5
|
align-items: flex-start;
|
|
@@ -96,8 +98,7 @@
|
|
|
96
98
|
/* Alert body = the same copy-this-error-detail content class as the
|
|
97
99
|
* toast message and message-box text (L2 selection policy re-enables,
|
|
98
100
|
* 2026-10-29); the title above stays chrome. */
|
|
99
|
-
|
|
100
|
-
user-select: text;
|
|
101
|
+
@include user-select(text);
|
|
101
102
|
margin: 0;
|
|
102
103
|
line-height: 1.5;
|
|
103
104
|
color: color-mix(in srgb, var(--hi-color-text-secondary, #666666) 85%, transparent);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Hikari Avatar Component
|
|
3
3
|
// ------
|
|
4
4
|
|
|
5
|
+
@use "../styles/polyfill" as *;
|
|
6
|
+
|
|
5
7
|
.hk-avatar {
|
|
6
8
|
display: inline-flex;
|
|
7
9
|
align-items: center;
|
|
@@ -44,7 +46,7 @@
|
|
|
44
46
|
display: flex;
|
|
45
47
|
align-items: center;
|
|
46
48
|
justify-content: center;
|
|
47
|
-
user-select
|
|
49
|
+
@include user-select(none);
|
|
48
50
|
font-weight: 600;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
// Use theme variables with namespace to avoid conflicts
|
|
11
11
|
@use "./hikari-vars" as vars;
|
|
12
|
+
@use "../styles/polyfill" as *;
|
|
12
13
|
|
|
13
14
|
.hk-badge {
|
|
14
15
|
--hk-badge-text: inherit;
|
|
@@ -30,8 +31,7 @@
|
|
|
30
31
|
/* Tags are labels, not prose: dragging across a row of pills must never
|
|
31
32
|
start a text selection (the workspace tag contract makes HkCopyBadge
|
|
32
33
|
the copy path instead). */
|
|
33
|
-
user-select
|
|
34
|
-
-webkit-user-select: none;
|
|
34
|
+
@include user-select(none);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/* ---------- size ---------- */
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// with a button row appended. Pointer events pass through the column
|
|
7
7
|
// and re-enable on the per-card slot, exactly like toasts.
|
|
8
8
|
|
|
9
|
+
@use "../styles/polyfill" as *;
|
|
10
|
+
|
|
9
11
|
.hk-blocking-toast-container {
|
|
10
12
|
position: fixed;
|
|
11
13
|
top: 4rem;
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
font-size: var(--text-base, 0.875rem);
|
|
39
41
|
line-height: 1.4;
|
|
40
42
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
41
|
-
backdrop-filter
|
|
43
|
+
@include backdrop-filter(blur(12px));
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
.hk-blocking-toast-icon {
|
|
@@ -61,14 +63,12 @@
|
|
|
61
63
|
/* Content, not chrome — copy-worthy detail under the L2 selection
|
|
62
64
|
* policy (2026-10-29): the card shell denies, these two text blocks
|
|
63
65
|
* select again. */
|
|
64
|
-
|
|
65
|
-
user-select: text;
|
|
66
|
+
@include user-select(text);
|
|
66
67
|
font-weight: 700;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
.hk-blocking-toast-message {
|
|
70
|
-
|
|
71
|
-
user-select: text;
|
|
71
|
+
@include user-select(text);
|
|
72
72
|
margin: 0;
|
|
73
73
|
display: -webkit-box;
|
|
74
74
|
-webkit-line-clamp: 5;
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
.hk-blocking-toast-actions .hk-btn-secondary {
|
|
164
164
|
background: transparent;
|
|
165
165
|
border-color: color-mix(in srgb, currentColor 45%, transparent);
|
|
166
|
-
backdrop-filter
|
|
166
|
+
@include backdrop-filter(none);
|
|
167
167
|
color: inherit;
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// this file only styles the layers (grid, world, edges, node shells,
|
|
4
4
|
// minimap dock).
|
|
5
5
|
|
|
6
|
+
@use "../styles/polyfill" as *;
|
|
7
|
+
|
|
6
8
|
.hk-board {
|
|
7
9
|
position: relative;
|
|
8
10
|
width: 100%;
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
background: rgb(var(--color-surface));
|
|
13
15
|
// gestures own the surface: no native scroll/selection interference
|
|
14
16
|
touch-action: none;
|
|
15
|
-
user-select
|
|
17
|
+
@include user-select(none);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
.hk-board-grid {
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// The renderer owns the pixels; this file only styles the layers (canvas,
|
|
3
3
|
// CSS2D labels, overlay slot, fallback note, minimap dock).
|
|
4
4
|
|
|
5
|
+
@use "../styles/polyfill" as *;
|
|
6
|
+
|
|
5
7
|
.hk-board3d {
|
|
6
8
|
position: relative;
|
|
7
9
|
width: 100%;
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
// Gestures own the surface: no native scroll/selection interference
|
|
13
15
|
// (HkBoard precedent).
|
|
14
16
|
touch-action: none;
|
|
15
|
-
user-select
|
|
17
|
+
@include user-select(none);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
.hk-board3d-canvas {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@use "./menu-item" as mi;
|
|
2
2
|
// Use theme variables with namespace to avoid conflicts
|
|
3
3
|
@use "./hikari-vars" as vars;
|
|
4
|
+
@use "../styles/polyfill" as *;
|
|
4
5
|
|
|
5
6
|
// ------
|
|
6
7
|
// Hikari Breadcrumb Component
|
|
@@ -218,7 +219,7 @@
|
|
|
218
219
|
border-radius: vars.$hikari-radius-fine-md;
|
|
219
220
|
border: 1px solid var(--hi-color-border);
|
|
220
221
|
background: var(--hi-surface);
|
|
221
|
-
backdrop-filter
|
|
222
|
+
@include backdrop-filter(blur(4px));
|
|
222
223
|
box-shadow:
|
|
223
224
|
0 8px 16px var(--hi-black-30, rgba(0, 0, 0, 0.3)),
|
|
224
225
|
0 0 0 1px var(--hi-white-5, rgba(255, 255, 255, 0.05)) inset;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@use "../tokens";
|
|
2
|
+
@use "../styles/polyfill" as *;
|
|
2
3
|
|
|
3
4
|
/* HkButton */
|
|
4
5
|
.hk-btn {
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
transition-duration: var(--duration-normal);
|
|
23
24
|
transition-timing-function: var(--ease-standard);
|
|
24
25
|
white-space: nowrap;
|
|
25
|
-
user-select
|
|
26
|
+
@include user-select(none);
|
|
26
27
|
outline: none;
|
|
27
28
|
position: relative;
|
|
28
29
|
}
|
|
@@ -328,7 +329,7 @@
|
|
|
328
329
|
background: rgb(var(--color-surface));
|
|
329
330
|
color: rgb(var(--color-text));
|
|
330
331
|
border-color: rgb(var(--color-border) / 20%);
|
|
331
|
-
backdrop-filter
|
|
332
|
+
@include backdrop-filter(blur(var(--blur-sm)));
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
/* background stays the base surface — the hover declaration only restated
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@use "../tokens";
|
|
2
|
+
@use "../styles/polyfill" as *;
|
|
2
3
|
|
|
3
4
|
/* HkCard — canonical surface card. Hover emphasis is border-color only
|
|
4
5
|
(the base elevation shadow is already at its resting value). No
|
|
@@ -7,7 +8,7 @@
|
|
|
7
8
|
display: block;
|
|
8
9
|
overflow: hidden;
|
|
9
10
|
background: color-mix(in srgb, rgb(var(--color-surface)) 70%, transparent);
|
|
10
|
-
backdrop-filter
|
|
11
|
+
@include backdrop-filter(blur(var(--blur-md)));
|
|
11
12
|
border: 1px solid var(--border-subtle);
|
|
12
13
|
border-radius: var(--radius-md);
|
|
13
14
|
box-shadow: var(--shadow-elevated);
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* HkCheckbox / HkRadio — unified selection control.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
@use "../styles/polyfill" as *;
|
|
6
|
+
|
|
5
7
|
.hk-checkbox {
|
|
6
8
|
display: inline-flex;
|
|
7
9
|
align-items: center;
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
vertical-align: middle;
|
|
13
15
|
gap: var(--space-8, 0.5rem);
|
|
14
16
|
cursor: pointer;
|
|
15
|
-
user-select
|
|
17
|
+
@include user-select(none);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
.hk-checkbox[data-disabled] {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "../styles/polyfill" as *;
|
|
2
|
+
|
|
1
3
|
.hk-color-picker {
|
|
2
4
|
display: inline-flex;
|
|
3
5
|
min-width: 0;
|
|
@@ -88,7 +90,7 @@
|
|
|
88
90
|
.hk-color-picker-panel {
|
|
89
91
|
padding: var(--space-12);
|
|
90
92
|
background: rgb(var(--color-surface));
|
|
91
|
-
backdrop-filter
|
|
93
|
+
@include backdrop-filter(blur(var(--blur-lg)));
|
|
92
94
|
border: 1px solid var(--border-subtle);
|
|
93
95
|
border-radius: var(--radius-md);
|
|
94
96
|
box-shadow: var(--shadow-modal);
|
|
@@ -134,7 +136,7 @@
|
|
|
134
136
|
flex: 1;
|
|
135
137
|
height: 14px;
|
|
136
138
|
touch-action: none;
|
|
137
|
-
user-select
|
|
139
|
+
@include user-select(none);
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
.hk-color-picker-slider-track {
|
|
@@ -245,7 +247,7 @@
|
|
|
245
247
|
font-size: var(--text-xs);
|
|
246
248
|
font-weight: 600;
|
|
247
249
|
color: rgb(var(--color-muted));
|
|
248
|
-
user-select
|
|
250
|
+
@include user-select(none);
|
|
249
251
|
pointer-events: none;
|
|
250
252
|
line-height: 1;
|
|
251
253
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// buttons share the row equally (no right-clustering of tiny sm buttons),
|
|
7
7
|
// and on touch-width viewports they grow to the 44px thumb standard.
|
|
8
8
|
|
|
9
|
+
@use "../styles/polyfill" as *;
|
|
10
|
+
|
|
9
11
|
.hk-confirm-dialog {
|
|
10
12
|
display: flex;
|
|
11
13
|
flex-direction: column;
|
|
@@ -17,8 +19,7 @@
|
|
|
17
19
|
.hk-confirm-dialog-message {
|
|
18
20
|
/* Dialog message = copy-worthy content, same class as the message-box
|
|
19
21
|
* text (L2 selection policy re-enables, 2026-10-29). */
|
|
20
|
-
|
|
21
|
-
user-select: text;
|
|
22
|
+
@include user-select(text);
|
|
22
23
|
margin: 0;
|
|
23
24
|
color: color-mix(in srgb, var(--hi-color-text-secondary) 85%, transparent);
|
|
24
25
|
font-size: var(--text-sm, 0.8125rem);
|
|
@@ -201,7 +201,7 @@ describe("HkCopyBadge", () => {
|
|
|
201
201
|
// inherits the no-selection rule, copyable or not.
|
|
202
202
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
203
203
|
const scss = readFileSync(join(here, "HkBadge.scss"), "utf-8");
|
|
204
|
-
expect(scss).toMatch(
|
|
204
|
+
expect(scss).toMatch(/@include user-select\(\s*none\s*\)/);
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
it("lands consumer class on the badge element, not the tooltip wrapper", () => {
|
|
@@ -139,6 +139,54 @@ describe("HkCrossfade — DOM contract", () => {
|
|
|
139
139
|
expect(list[1]!.classList.contains("hk-crossfade-enter-from")).toBe(true);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
it("marks the leaving copy INERT for the dissolve (a11y: dead content)", async () => {
|
|
143
|
+
// pointer-events: none only stops the mouse — the frozen copy must
|
|
144
|
+
// also leave the tab order and the accessibility tree, or a screen
|
|
145
|
+
// reader walks a second, stale copy (duplicate landmarks/roles; in
|
|
146
|
+
// the rail swap it is a whole second sidebar). A cancelled leave has
|
|
147
|
+
// to restore the item (see the next case).
|
|
148
|
+
vi.useFakeTimers();
|
|
149
|
+
freezeRaf();
|
|
150
|
+
const { container, set } = mountCrossfade();
|
|
151
|
+
await set("b");
|
|
152
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
153
|
+
|
|
154
|
+
const list = items(container);
|
|
155
|
+
expect(list[0]!.hasAttribute("inert"), "the leaving copy is inert").toBe(true);
|
|
156
|
+
expect(list[0]!.getAttribute("aria-hidden")).toBe("true");
|
|
157
|
+
expect(list[1]!.hasAttribute("inert"), "the live copy stays interactive").toBe(false);
|
|
158
|
+
expect(list[1]!.hasAttribute("aria-hidden")).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("a flip-back never leaves a LIVE copy inert (Vue removes the leaving node)", async () => {
|
|
162
|
+
// Pins the behavior the inert contract relies on: on a keyed
|
|
163
|
+
// flip-back Vue cancels the in-flight leave of the old element
|
|
164
|
+
// through its internal callback (no element argument -> routed to
|
|
165
|
+
// afterLeave, which REMOVES it) and re-enters the key as a FRESH
|
|
166
|
+
// element. So no live copy can carry inert; if Vue ever changed that
|
|
167
|
+
// shape (a reused, re-entered element), this test is the tripwire.
|
|
168
|
+
vi.useFakeTimers();
|
|
169
|
+
freezeRaf();
|
|
170
|
+
const { container, set } = mountCrossfade();
|
|
171
|
+
await set("b");
|
|
172
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
173
|
+
const oldA = items(container).find((el) => el.dataset.crossfadeKey === "a")!;
|
|
174
|
+
expect(oldA.hasAttribute("inert")).toBe(true);
|
|
175
|
+
|
|
176
|
+
await set("a");
|
|
177
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
178
|
+
|
|
179
|
+
// The inert old node is gone (not left mounted), and every live node
|
|
180
|
+
// for the key renders un-inert.
|
|
181
|
+
expect(oldA.isConnected, "the inert leaving node is removed").toBe(false);
|
|
182
|
+
const live = items(container).filter((el) => el.dataset.crossfadeKey === "a");
|
|
183
|
+
expect(live.length).toBeGreaterThan(0);
|
|
184
|
+
for (const el of live) {
|
|
185
|
+
expect(el.hasAttribute("inert"), "a live copy must never be inert").toBe(false);
|
|
186
|
+
expect(el.getAttribute("aria-hidden")).toBeNull();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
142
190
|
it("watchdog completes a stalled swap: sweeps frozen classes, drops the stale item", async () => {
|
|
143
191
|
vi.useFakeTimers();
|
|
144
192
|
freezeRaf();
|
|
@@ -40,7 +40,16 @@ const REPORT_MS = CROSSFADE_WATCHDOG_MS;
|
|
|
40
40
|
* opacity ahead of the outgoing one's decay and stops the composite from
|
|
41
41
|
* dipping toward the background mid-swap.
|
|
42
42
|
* - The leaving item drops pointer events: it is dead content and must not
|
|
43
|
-
* intercept clicks while it dissolves under the incoming one.
|
|
43
|
+
* intercept clicks while it dissolves under the incoming one. It is also
|
|
44
|
+
* marked `inert` + `aria-hidden` for the dissolve (2026-10-31): a frozen
|
|
45
|
+
* copy must leave the TAB ORDER and the accessibility tree too, or a
|
|
46
|
+
* screen reader walks a stale duplicate (in the rail swap it is a whole
|
|
47
|
+
* second sidebar). TRADE-OFF: a control focused in the leaving item is
|
|
48
|
+
* blurred by the browser the moment the attribute lands, `--duration`
|
|
49
|
+
* earlier than the unmount would have; hosts that care move focus FIRST
|
|
50
|
+
* (the rail kit's closePeek does exactly that). happy-dom applies no
|
|
51
|
+
* inert focus fixup, so that browser behavior is intentionally not
|
|
52
|
+
* covered by this suite.
|
|
44
53
|
* - `appear` dissolves the initially rendered item in; off by default —
|
|
45
54
|
* first paint should never animate (view shells, restored state).
|
|
46
55
|
* - `disabled` renders the active item bare (synchronous swap, no watchdog):
|
|
@@ -115,6 +124,25 @@ export default defineComponent({
|
|
|
115
124
|
if (pending === 0) report.cancel();
|
|
116
125
|
};
|
|
117
126
|
|
|
127
|
+
/** Mark a leaving item inert (out of the tab order and the a11y tree)
|
|
128
|
+
* for the dissolve. Attribute-based (not the `inert` IDL property) so
|
|
129
|
+
* the DOM contract is observable in tests and SSR-less hosts alike.
|
|
130
|
+
* The restore is defence: on a keyed flip-back Vue removes the
|
|
131
|
+
* leaving node and re-enters a FRESH element (its internal
|
|
132
|
+
* cancel-callback runs with no argument and routes to afterLeave), so
|
|
133
|
+
* nothing live is ever left inert by that route — but any shape that
|
|
134
|
+
* DOES cancel a leave must not keep a live copy inert. */
|
|
135
|
+
function setLeavingInert(el: Element | null | undefined, on: boolean): void {
|
|
136
|
+
if (!el) return;
|
|
137
|
+
if (on) {
|
|
138
|
+
el.setAttribute("inert", "");
|
|
139
|
+
el.setAttribute("aria-hidden", "true");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
el.removeAttribute("inert");
|
|
143
|
+
el.removeAttribute("aria-hidden");
|
|
144
|
+
}
|
|
145
|
+
|
|
118
146
|
// ── Swap-stall watchdog ──────────────────────────────────────────
|
|
119
147
|
/** Budget for one arm: the base sweep window plus the choreographed
|
|
120
148
|
* enter hold — a delayed enter is mid-flight, not stalled, when the
|
|
@@ -211,9 +239,23 @@ export default defineComponent({
|
|
|
211
239
|
onBeforeEnter={() => { armReport(); armWatchdog(); }}
|
|
212
240
|
onAfterEnter={settleReport}
|
|
213
241
|
onEnterCancelled={settleReport}
|
|
214
|
-
onBeforeLeave={() => {
|
|
242
|
+
onBeforeLeave={(el: Element) => {
|
|
243
|
+
// DEAD CONTENT for the dissolve: drop it from the tab order
|
|
244
|
+
// AND the accessibility tree. pointer-events: none (the
|
|
245
|
+
// leave rule) only stops the mouse — a frozen copy otherwise
|
|
246
|
+
// stays reachable by Tab and by a screen reader's virtual
|
|
247
|
+
// cursor for the whole dissolve, and in the rail swap it is
|
|
248
|
+
// a second full sidebar (duplicate landmark/roles, stale
|
|
249
|
+
// controls) while the live one dissolves in.
|
|
250
|
+
setLeavingInert(el, true);
|
|
251
|
+
armReport();
|
|
252
|
+
armWatchdog();
|
|
253
|
+
}}
|
|
215
254
|
onAfterLeave={settleReport}
|
|
216
|
-
onLeaveCancelled={
|
|
255
|
+
onLeaveCancelled={(el: Element) => {
|
|
256
|
+
setLeavingInert(el, false);
|
|
257
|
+
settleReport();
|
|
258
|
+
}}
|
|
217
259
|
>
|
|
218
260
|
{item()}
|
|
219
261
|
</Transition>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Uses the shared --hi-* design tokens. */
|
|
4
4
|
|
|
5
5
|
@use "./HkPickerPane" as picker-pane;
|
|
6
|
+
@use "../styles/polyfill" as *;
|
|
6
7
|
|
|
7
8
|
.hk-dp {
|
|
8
9
|
display: block;
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
align-items: center;
|
|
41
42
|
gap: var(--space-6, 6px);
|
|
42
43
|
cursor: pointer;
|
|
43
|
-
user-select
|
|
44
|
+
@include user-select(none);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
.hk-dp:not(.is-disabled) .hk-dp-trigger:hover,
|
|
@@ -139,7 +140,7 @@
|
|
|
139
140
|
flex-direction: column;
|
|
140
141
|
gap: var(--space-8, 8px);
|
|
141
142
|
min-width: 17.5rem;
|
|
142
|
-
user-select
|
|
143
|
+
@include user-select(none);
|
|
143
144
|
color: var(--hi-color-text-primary);
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* stage and the direction-aware view transition live in HkPickerPane.scss. */
|
|
4
4
|
|
|
5
5
|
@use "./HkPickerPane" as picker-pane;
|
|
6
|
+
@use "../styles/polyfill" as *;
|
|
6
7
|
|
|
7
8
|
.hk-dtp {
|
|
8
9
|
display: flex;
|
|
9
10
|
flex-direction: column;
|
|
10
11
|
gap: var(--space-8, 8px);
|
|
11
|
-
user-select
|
|
12
|
+
@include user-select(none);
|
|
12
13
|
/* Fill the host (inline sheets/dialogs) instead of collapsing to the
|
|
13
14
|
* grid's min-content width — a bottom-sheet calendar should use the
|
|
14
15
|
* space it is given, not shrink to a cramped block. */
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// by the consumer's scroll container).
|
|
5
5
|
|
|
6
6
|
@use "./hikari-vars" as vars;
|
|
7
|
+
@use "../styles/polyfill" as *;
|
|
7
8
|
|
|
8
9
|
.hk-diff-viewer {
|
|
9
10
|
border: 1px solid var(--hi-color-border, #646464);
|
|
@@ -57,11 +58,11 @@
|
|
|
57
58
|
text-align: end;
|
|
58
59
|
color: var(--hi-color-text-tertiary, #8b949e);
|
|
59
60
|
opacity: 0.75;
|
|
60
|
-
user-select
|
|
61
|
+
@include user-select(none);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
.hk-diff-viewer__sign {
|
|
64
|
-
user-select
|
|
65
|
+
@include user-select(none);
|
|
65
66
|
text-align: center;
|
|
66
67
|
font-weight: 600;
|
|
67
68
|
}
|
|
@@ -69,8 +70,7 @@
|
|
|
69
70
|
.hk-diff-viewer__code {
|
|
70
71
|
/* The diff body is content (review text users copy into replies);
|
|
71
72
|
* the line-number gutter above and the sign column stay denied. */
|
|
72
|
-
|
|
73
|
-
user-select: text;
|
|
73
|
+
@include user-select(text);
|
|
74
74
|
padding-inline-end: 1ch;
|
|
75
75
|
min-width: 0;
|
|
76
76
|
}
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
font: inherit;
|
|
94
94
|
text-align: start;
|
|
95
95
|
cursor: pointer;
|
|
96
|
-
user-select
|
|
96
|
+
@include user-select(none);
|
|
97
97
|
|
|
98
98
|
&:hover {
|
|
99
99
|
color: var(--hi-color-text-primary, #1e1e1e);
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
color: var(--hi-color-text-tertiary, #8b949e);
|
|
154
154
|
font: inherit;
|
|
155
155
|
cursor: pointer;
|
|
156
|
-
user-select
|
|
156
|
+
@include user-select(none);
|
|
157
157
|
|
|
158
158
|
&:hover:not(:disabled) {
|
|
159
159
|
background: rgb(var(--color-primary) / 10%);
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* together.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
@use "../styles/polyfill" as *;
|
|
9
|
+
|
|
8
10
|
.hk-dock-bar {
|
|
9
11
|
/* Transparent anchor box — never intercepts canvas gestures around
|
|
10
12
|
* the docked surface. */
|
|
@@ -137,12 +139,12 @@
|
|
|
137
139
|
|
|
138
140
|
/* Glass surface (default) — blur the page canvas behind the dock. */
|
|
139
141
|
.hk-dock-bar[data-surface="glass"] .hk-dock-bar-surface {
|
|
140
|
-
backdrop-filter
|
|
142
|
+
@include backdrop-filter(blur(var(--blur-lg)));
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
/* Solid surface — no blur, for moving 3D canvases (per-frame backdrop
|
|
144
146
|
* re-sampling over a live WebGL scene is expensive; the surface must
|
|
145
147
|
* stay opaque anyway for contrast over the scene). */
|
|
146
148
|
.hk-dock-bar[data-surface="solid"] .hk-dock-bar-surface {
|
|
147
|
-
backdrop-filter
|
|
149
|
+
@include backdrop-filter(none);
|
|
148
150
|
}
|
|
@@ -198,6 +198,6 @@ describe("HkDockBar anchor geometry contract", () => {
|
|
|
198
198
|
/\.hk-dock-bar\[data-surface="solid"\] \.hk-dock-bar-surface\s*\{[^}]*\}/,
|
|
199
199
|
)?.[0];
|
|
200
200
|
expect(solid, "solid surface rule must exist").toBeTruthy();
|
|
201
|
-
expect(solid).toContain("backdrop-filter
|
|
201
|
+
expect(solid).toContain("@include backdrop-filter(none)");
|
|
202
202
|
});
|
|
203
203
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "../styles/polyfill" as *;
|
|
2
|
+
|
|
1
3
|
.hk-draggable-grid {
|
|
2
4
|
display: flex;
|
|
3
5
|
flex-direction: column;
|
|
@@ -33,8 +35,7 @@
|
|
|
33
35
|
border-color 150ms ease,
|
|
34
36
|
background 150ms ease,
|
|
35
37
|
box-shadow 150ms ease;
|
|
36
|
-
user-select
|
|
37
|
-
-webkit-user-select: none;
|
|
38
|
+
@include user-select(none);
|
|
38
39
|
transform-origin: center;
|
|
39
40
|
|
|
40
41
|
&:hover {
|
|
@@ -131,7 +132,7 @@
|
|
|
131
132
|
0 2px 6px rgb(0 0 0 / 18%);
|
|
132
133
|
border-radius: var(--radius-sm, 4px);
|
|
133
134
|
background: var(--hi-color-bg-elevated, #161b22);
|
|
134
|
-
backdrop-filter
|
|
135
|
+
@include backdrop-filter(blur(2px));
|
|
135
136
|
pointer-events: none;
|
|
136
137
|
/* Drag ghost: above page chrome (--z-sidebar 900) but below every
|
|
137
138
|
popup band (overlay 1000 < dropdown 2000 < tooltip 3000 < toast 4000)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "../styles/polyfill" as *;
|
|
2
|
+
|
|
1
3
|
.hk-draggable-list {
|
|
2
4
|
display: flex;
|
|
3
5
|
flex-direction: column;
|
|
@@ -18,8 +20,7 @@
|
|
|
18
20
|
opacity 150ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
19
21
|
border-color 150ms ease,
|
|
20
22
|
background 150ms ease;
|
|
21
|
-
user-select
|
|
22
|
-
-webkit-user-select: none;
|
|
23
|
+
@include user-select(none);
|
|
23
24
|
|
|
24
25
|
&:hover {
|
|
25
26
|
border-color: var(--hi-color-text-tertiary, #8b949e);
|
|
@@ -139,7 +140,7 @@
|
|
|
139
140
|
0 2px 6px rgb(0 0 0 / 18%);
|
|
140
141
|
border-radius: var(--radius-sm, 4px);
|
|
141
142
|
background: var(--hi-color-bg-elevated, #161b22);
|
|
142
|
-
backdrop-filter
|
|
143
|
+
@include backdrop-filter(blur(2px));
|
|
143
144
|
pointer-events: none;
|
|
144
145
|
/* Drag ghost: above page chrome (--z-sidebar 900) but below every
|
|
145
146
|
popup band (overlay 1000 < dropdown 2000 < tooltip 3000 < toast 4000)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@use "./scrim-fade" as sf;
|
|
2
|
+
@use "../styles/polyfill" as *;
|
|
2
3
|
|
|
3
4
|
.hk-drawer-overlay {
|
|
4
5
|
/* Overlay-root contract: #hk-overlay-root fences pointer events
|
|
@@ -8,7 +9,7 @@
|
|
|
8
9
|
position: fixed;
|
|
9
10
|
inset: 0;
|
|
10
11
|
background: var(--hk-drawer-overlay-bg, rgba(0, 0, 0, 0.45));
|
|
11
|
-
backdrop-filter
|
|
12
|
+
@include backdrop-filter(var(--hk-drawer-overlay-blur, blur(6px)));
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
// Overlay enter/leave — fade-only classes from the shared window-layer
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
// lag shows as patchy flicker along the moving edge. The mobile
|
|
34
35
|
// guard below turns it off ≤767px; hosts restore a finish per
|
|
35
36
|
// surface with --hk-drawer-blur / --hk-drawer-blur-mobile.
|
|
36
|
-
backdrop-filter
|
|
37
|
+
@include backdrop-filter(var(--hk-drawer-blur, blur(12px)));
|
|
37
38
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16);
|
|
38
39
|
outline: none;
|
|
39
40
|
// Scope layout/style invalidation to the drawer subtree — the page
|
|
@@ -226,11 +227,11 @@
|
|
|
226
227
|
// family guard as the modal/select sheets, 2026-09-15 chest report).
|
|
227
228
|
// The scrim's dim alone reads correctly there.
|
|
228
229
|
.hk-drawer-overlay {
|
|
229
|
-
backdrop-filter
|
|
230
|
+
@include backdrop-filter(var(--hk-drawer-overlay-blur-mobile, none));
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
.hk-drawer-panel {
|
|
233
|
-
backdrop-filter
|
|
234
|
+
@include backdrop-filter(var(--hk-drawer-blur-mobile, none));
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
.hk-drawer-bottom {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use "../styles/polyfill" as *;
|
|
2
|
+
|
|
1
3
|
.hk-empty-state {
|
|
2
4
|
display: flex;
|
|
3
5
|
flex-direction: column;
|
|
@@ -13,7 +15,7 @@
|
|
|
13
15
|
every in-card empty state into a nested placeholder layer. */
|
|
14
16
|
.hk-empty-state--boxed {
|
|
15
17
|
background: var(--hk-empty-bg, var(--hi-color-surface, rgba(255,255,255,0.95)));
|
|
16
|
-
backdrop-filter
|
|
18
|
+
@include backdrop-filter(blur(var(--blur-sm, 8px)));
|
|
17
19
|
border: 1px solid var(--hi-color-border, rgba(100,100,100,0.06));
|
|
18
20
|
border-radius: var(--radius-md, 0.5rem);
|
|
19
21
|
}
|