@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.
Files changed (92) hide show
  1. package/package.json +1 -1
  2. package/src/components/HkAboutModal.scss +2 -1
  3. package/src/components/HkAlert.scss +3 -2
  4. package/src/components/HkAvatar.scss +3 -1
  5. package/src/components/HkBadge.scss +2 -2
  6. package/src/components/HkBlockingToast.scss +6 -6
  7. package/src/components/HkBoard.scss +3 -1
  8. package/src/components/HkBoard3D.scss +3 -1
  9. package/src/components/HkBreadcrumb.scss +2 -1
  10. package/src/components/HkButton.scss +3 -2
  11. package/src/components/HkCard.scss +2 -1
  12. package/src/components/HkCheckbox.scss +3 -1
  13. package/src/components/HkColorPicker.scss +5 -3
  14. package/src/components/HkConfirmDialog.scss +3 -2
  15. package/src/components/HkCopyBadge.test.tsx +1 -1
  16. package/src/components/HkCrossfade.test.tsx +48 -0
  17. package/src/components/HkCrossfade.tsx +45 -3
  18. package/src/components/HkDatePicker.scss +3 -2
  19. package/src/components/HkDateTimePicker.scss +2 -1
  20. package/src/components/HkDiffViewer.scss +6 -6
  21. package/src/components/HkDockBar.scss +4 -2
  22. package/src/components/HkDockBar.test.tsx +1 -1
  23. package/src/components/HkDraggableGrid.scss +4 -3
  24. package/src/components/HkDraggableList.scss +4 -3
  25. package/src/components/HkDrawer.scss +5 -4
  26. package/src/components/HkEmptyState.scss +3 -1
  27. package/src/components/HkEmptyState.surface.contract.test.ts +5 -2
  28. package/src/components/HkErrorLanding.scss +3 -1
  29. package/src/components/HkFab.scss +5 -2
  30. package/src/components/HkFaceShell.rail.test.tsx +322 -4
  31. package/src/components/HkFaceShell.tsx +14 -0
  32. package/src/components/HkImageLightbox.scss +3 -1
  33. package/src/components/HkImageViewer.scss +3 -1
  34. package/src/components/HkJsonTree.scss +3 -3
  35. package/src/components/HkLoadingVeil.scss +4 -2
  36. package/src/components/HkLogWindow.scss +2 -2
  37. package/src/components/HkLogo.scss +3 -1
  38. package/src/components/HkMarkdownRenderer.scss +3 -3
  39. package/src/components/HkMediaControlBar.scss +4 -2
  40. package/src/components/HkMediaSlider.scss +3 -1
  41. package/src/components/HkMenu.rail.test.tsx +29 -0
  42. package/src/components/HkMenu.tsx +62 -17
  43. package/src/components/HkMessageBox.scss +3 -2
  44. package/src/components/HkMinimap.scss +5 -3
  45. package/src/components/HkModal.scss +10 -11
  46. package/src/components/HkModal.sheetmorph.test.ts +8 -6
  47. package/src/components/HkModalBreadcrumb.blur.test.ts +3 -3
  48. package/src/components/HkModalBreadcrumb.scss +6 -4
  49. package/src/components/HkNavItem.scss +2 -1
  50. package/src/components/HkNodeCanvas.scss +3 -1
  51. package/src/components/HkOtpInput.scss +2 -1
  52. package/src/components/HkPasswordSurface.scss +4 -3
  53. package/src/components/HkPillToggleGroup.scss +13 -18
  54. package/src/components/HkPopover.scss +5 -6
  55. package/src/components/HkProgressRing.scss +3 -1
  56. package/src/components/HkRadio.scss +3 -1
  57. package/src/components/HkScrollContainer.fade.contract.test.ts +7 -2
  58. package/src/components/HkScrollContainer.scss +23 -46
  59. package/src/components/HkScrollPin.scss +3 -1
  60. package/src/components/HkSelect.scss +4 -5
  61. package/src/components/HkSelectionGrid.scss +3 -1
  62. package/src/components/HkSelectionWaterfall.scss +3 -1
  63. package/src/components/HkSidebar.rail.test.tsx +141 -3
  64. package/src/components/HkSidebar.scss +4 -1
  65. package/src/components/HkSidebar.tsx +15 -0
  66. package/src/components/HkSidebarRailSwap.tsx +20 -8
  67. package/src/components/HkSlider.scss +3 -1
  68. package/src/components/HkSpinner.scss +5 -2
  69. package/src/components/HkSwitch.scss +3 -1
  70. package/src/components/HkTable.interactions.test.tsx +4 -2
  71. package/src/components/HkTable.scss +8 -8
  72. package/src/components/HkTag.scss +3 -1
  73. package/src/components/HkTagInput.scss +5 -6
  74. package/src/components/HkTagInput.test.tsx +5 -4
  75. package/src/components/HkTemplateField.scss +2 -1
  76. package/src/components/HkTextarea.scss +3 -1
  77. package/src/components/HkThemeToggle.scss +2 -2
  78. package/src/components/HkTitleBar.scss +4 -3
  79. package/src/components/HkToast.scss +5 -4
  80. package/src/components/HkTreemapStage.scss +3 -1
  81. package/src/components/HkZoomToolbar.scss +3 -2
  82. package/src/components/_menu-item.contract.test.ts +3 -1
  83. package/src/components/_menu-item.scss +4 -3
  84. package/src/components/window-close.scss +4 -2
  85. package/src/composables/useMenuGroupState.ts +90 -0
  86. package/src/composables/useSidebarRail.test.ts +158 -1
  87. package/src/composables/useSidebarRail.ts +72 -4
  88. package/src/styles/admin-tokens.scss +4 -3
  89. package/src/styles/polyfill.contract.test.ts +294 -0
  90. package/src/styles/polyfill.scss +110 -0
  91. package/src/styles/selection.scss +4 -4
  92. package/src/theme/_field.scss +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.240",
3
+ "version": "0.55.242",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari UI library, Vue 3 runtime — one design system for Vue 3 and Rust/WASM applications",
@@ -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: none;
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
- -webkit-user-select: text;
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: none;
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: none;
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: blur(12px);
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
- -webkit-user-select: text;
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
- -webkit-user-select: text;
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: none;
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: none;
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: none;
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: blur(4px);
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: none;
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: blur(var(--blur-sm));
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: blur(var(--blur-md));
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: none;
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: blur(var(--blur-lg));
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: none;
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: none;
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
- -webkit-user-select: text;
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(/user-select:\s*none/);
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={() => { armReport(); armWatchdog(); }}
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={settleReport}
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: none;
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: none;
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: none;
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: none;
61
+ @include user-select(none);
61
62
  }
62
63
 
63
64
  .hk-diff-viewer__sign {
64
- user-select: none;
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
- -webkit-user-select: text;
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: none;
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: none;
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: blur(var(--blur-lg));
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: none;
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: none");
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: none;
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: blur(2px);
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: none;
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: blur(2px);
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: var(--hk-drawer-overlay-blur, blur(6px));
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: var(--hk-drawer-blur, blur(12px));
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: var(--hk-drawer-overlay-blur-mobile, none);
230
+ @include backdrop-filter(var(--hk-drawer-overlay-blur-mobile, none));
230
231
  }
231
232
 
232
233
  .hk-drawer-panel {
233
- backdrop-filter: var(--hk-drawer-blur-mobile, none);
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: blur(var(--blur-sm, 8px));
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
  }