@celestia-island/hikari 0.43.10 → 0.43.13

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 (121) hide show
  1. package/package.json +1 -1
  2. package/src/components/HkAboutModal.scss +12 -1
  3. package/src/components/HkAdaptiveDialog.scss +2 -2
  4. package/src/components/HkAffixPicker.scss +55 -27
  5. package/src/components/HkAlert.scss +35 -31
  6. package/src/components/HkAltSignIn.scss +10 -4
  7. package/src/components/HkAuthMethodList.scss +5 -5
  8. package/src/components/HkAuthSubmitButton.scss +3 -3
  9. package/src/components/HkAvatar.scss +10 -49
  10. package/src/components/HkBadge.scss +6 -9
  11. package/src/components/HkBlockingToast.scss +27 -10
  12. package/src/components/HkBoard.scss +26 -6
  13. package/src/components/HkBreadcrumb.scss +82 -268
  14. package/src/components/HkButton.scss +26 -3
  15. package/src/components/HkCaptchaWidget.scss +2 -3
  16. package/src/components/HkCard.scss +14 -8
  17. package/src/components/HkCheckbox.scss +11 -9
  18. package/src/components/HkColorPicker.scss +22 -8
  19. package/src/components/HkContextRing.scss +8 -4
  20. package/src/components/HkCookieConsent.scss +12 -3
  21. package/src/components/HkDatePicker.scss +49 -65
  22. package/src/components/HkDateTimePicker.scss +48 -31
  23. package/src/components/HkDivider.scss +14 -15
  24. package/src/components/HkDraggableGrid.scss +41 -11
  25. package/src/components/HkDraggableList.scss +22 -11
  26. package/src/components/HkDrawer.scss +24 -4
  27. package/src/components/HkEmptyState.scss +10 -11
  28. package/src/components/HkErrorLanding.scss +5 -5
  29. package/src/components/HkExpansionPanel.scss +6 -2
  30. package/src/components/HkFab.scss +29 -12
  31. package/src/components/HkFileBrowserDialog.scss +21 -10
  32. package/src/components/HkFileField.scss +20 -9
  33. package/src/components/HkFilePickerField.scss +12 -6
  34. package/src/components/HkGaugeRing.scss +8 -0
  35. package/src/components/HkHoverRevealAction.scss +30 -12
  36. package/src/components/HkIcon.scss +0 -18
  37. package/src/components/HkIconButton.scss +29 -62
  38. package/src/components/HkIconButtonVars.scss +8 -28
  39. package/src/components/HkImageLightbox.scss +4 -3
  40. package/src/components/HkImagePreview.scss +17 -9
  41. package/src/components/HkImageViewer.scss +8 -0
  42. package/src/components/HkInput.scss +28 -28
  43. package/src/components/HkJsonTree.scss +13 -9
  44. package/src/components/HkKeywordSearchModal.scss +40 -29
  45. package/src/components/HkLabel.scss +8 -3
  46. package/src/components/HkListTransition.scss +16 -1
  47. package/src/components/HkLoadMore.scss +11 -2
  48. package/src/components/HkLocalizedInput.scss +11 -11
  49. package/src/components/HkLogWindow.scss +22 -4
  50. package/src/components/HkLogo.scss +12 -3
  51. package/src/components/HkMarkdownRenderer.scss +19 -13
  52. package/src/components/HkMediaControlBar.scss +2 -4
  53. package/src/components/HkMediaSlider.scss +0 -2
  54. package/src/components/HkMenu.scss +32 -20
  55. package/src/components/HkMenuIdentityItem.scss +3 -3
  56. package/src/components/HkMinimap.scss +23 -8
  57. package/src/components/HkModal.scss +17 -20
  58. package/src/components/HkModalBreadcrumb.scss +25 -6
  59. package/src/components/HkModelTag.scss +23 -13
  60. package/src/components/HkNavItem.scss +27 -14
  61. package/src/components/HkNumberInput.scss +25 -21
  62. package/src/components/HkPageHeader.scss +7 -6
  63. package/src/components/HkPasswordInput.scss +36 -36
  64. package/src/components/HkPhaseTransition.scss +16 -0
  65. package/src/components/HkPhoneInput.scss +10 -5
  66. package/src/components/HkPickerPane.scss +32 -5
  67. package/src/components/HkPlaceholderMarquee.scss +7 -2
  68. package/src/components/HkPlaceholderMarquee.test.ts +27 -0
  69. package/src/components/HkPlaceholderMarquee.tsx +14 -6
  70. package/src/components/HkPopover.scss +18 -10
  71. package/src/components/HkPopupSelect.scss +28 -7
  72. package/src/components/HkProgressBar.scss +29 -7
  73. package/src/components/HkProgressDialog.scss +4 -2
  74. package/src/components/HkProgressRing.scss +7 -14
  75. package/src/components/HkProtocolModal.scss +0 -3
  76. package/src/components/HkQrCode.scss +2 -2
  77. package/src/components/HkRadio.scss +15 -6
  78. package/src/components/HkRichInput.scss +29 -13
  79. package/src/components/HkRollingNumber.scss +14 -6
  80. package/src/components/HkScrollContainer.scss +20 -8
  81. package/src/components/HkSearchInput.scss +12 -10
  82. package/src/components/HkSecretRevealModal.scss +11 -1
  83. package/src/components/HkSelect.scss +35 -17
  84. package/src/components/HkSelectionGrid.scss +41 -20
  85. package/src/components/HkSelectionWaterfall.scss +21 -9
  86. package/src/components/HkShareBar.scss +14 -5
  87. package/src/components/HkSidebar.scss +23 -9
  88. package/src/components/HkSkeleton.scss +16 -8
  89. package/src/components/HkSlider.scss +12 -2
  90. package/src/components/HkSpinner.scss +15 -9
  91. package/src/components/HkSplash.scss +10 -10
  92. package/src/components/HkStatCard.scss +7 -3
  93. package/src/components/HkStatusPill.scss +4 -7
  94. package/src/components/HkStepFlow.scss +24 -4
  95. package/src/components/HkSwitch.scss +41 -9
  96. package/src/components/HkTable.scss +25 -20
  97. package/src/components/HkTabs.scss +40 -26
  98. package/src/components/HkTag.scss +35 -29
  99. package/src/components/HkTagInput.scss +22 -16
  100. package/src/components/HkTextarea.scss +8 -6
  101. package/src/components/HkThemeToggle.scss +28 -18
  102. package/src/components/HkTimeline.scss +46 -29
  103. package/src/components/HkTimeline.test.tsx +39 -0
  104. package/src/components/HkTimeline.tsx +16 -2
  105. package/src/components/HkTitleBar.scss +16 -7
  106. package/src/components/HkToast.scss +40 -10
  107. package/src/components/HkTokenUsagePanel.scss +11 -6
  108. package/src/components/HkToolBlock.scss +23 -11
  109. package/src/components/HkTooltip.scss +5 -9
  110. package/src/components/HkTree.scss +26 -35
  111. package/src/components/HkVoiceInputPopup.scss +11 -6
  112. package/src/components/HkWindowedItem.scss +3 -9
  113. package/src/components/HkZoomToolbar.scss +12 -10
  114. package/src/components/_menu-item.scss +4 -3
  115. package/src/components/window-close.scss +8 -13
  116. package/src/composables/layoutGeometry.test.ts +177 -0
  117. package/src/composables/layoutGeometry.ts +231 -0
  118. package/src/composables/useOverlayScrollbar.test.ts +32 -0
  119. package/src/composables/useOverlayScrollbar.ts +9 -1
  120. package/src/composables/usePointerReorder.test.ts +5 -2
  121. package/src/composables/usePointerReorder.ts +83 -147
@@ -1,5 +1,5 @@
1
1
  /* Field caption, mirroring .hk-input-label in HkInput.scss (the number
2
- input carries its own copy so it does not depend on HInput's stylesheet
2
+ input carries its own copy so it does not depend on HkInput's stylesheet
3
3
  being loaded too). */
4
4
  .hk-input-label {
5
5
  display: block;
@@ -25,7 +25,7 @@
25
25
  align-items: center;
26
26
  background: var(--hi-color-surface);
27
27
  border: 1px solid var(--hi-color-border);
28
- border-radius: var(--hi-radius-md, 6px);
28
+ border-radius: var(--hi-radius-md, 8px);
29
29
  overflow: hidden;
30
30
  transition:
31
31
  background-color var(--hi-transition-base, 200ms cubic-bezier(0.4, 0, 0.2, 1)),
@@ -53,9 +53,10 @@
53
53
  outline: none;
54
54
  color: var(--hi-color-text-primary);
55
55
  font-family: inherit;
56
- font-size: 0.875rem;
56
+ /* Font size / horizontal padding come from the per-size blocks below —
57
+ the size class is always on the wrapper (HkNumberInput.tsx), so the
58
+ md block is the single source for those metrics. */
57
59
  line-height: 1.5;
58
- padding: 0 0.75rem;
59
60
 
60
61
  &::placeholder {
61
62
  color: var(--hi-color-muted);
@@ -83,11 +84,11 @@
83
84
  }
84
85
 
85
86
  .hk-number-input-prefix {
86
- padding-left: 0.75rem;
87
+ padding-inline-start: var(--space-12, 0.75rem);
87
88
  }
88
89
 
89
90
  .hk-number-input-suffix {
90
- padding-right: 0.5rem;
91
+ padding-inline-end: var(--space-8, 0.5rem);
91
92
  }
92
93
 
93
94
  .hk-number-input-steppers {
@@ -98,14 +99,17 @@
98
99
  against the real box height and the divider spans it fully —
99
100
  content-sized centering left a floating ~28px stack in a 36px box. */
100
101
  align-self: stretch;
101
- border-left: 1px solid var(--hi-color-border);
102
+ /* Inline-start so the divider faces the field/suffix side in both
103
+ LTR and RTL (the flex row flips under direction: rtl). */
104
+ border-inline-start: 1px solid var(--hi-color-border);
102
105
  }
103
106
 
104
107
  .hk-number-input-step {
105
108
  display: flex;
106
109
  align-items: center;
107
110
  justify-content: center;
108
- width: 1.5rem;
111
+ /* Width comes from the per-size blocks below (the size class is always
112
+ on the wrapper); height stays here as the size-independent half. */
109
113
  height: 50%;
110
114
  padding: 0;
111
115
  background: transparent;
@@ -135,18 +139,18 @@
135
139
  /* --- Size variants --- */
136
140
 
137
141
  .hk-number-input-sm .hk-number-input-field {
138
- font-size: 0.75rem;
139
- padding: 0 0.5rem;
140
- height: 1.75rem;
142
+ font-size: var(--text-xs, 0.75rem);
143
+ padding: 0 var(--space-8, 0.5rem);
144
+ height: var(--space-28, 1.75rem);
141
145
  }
142
146
 
143
147
  .hk-number-input-sm .hk-number-input-prefix,
144
148
  .hk-number-input-sm .hk-number-input-suffix {
145
- font-size: 0.75rem;
149
+ font-size: var(--text-xs, 0.75rem);
146
150
  }
147
151
 
148
152
  .hk-number-input-sm .hk-number-input-step {
149
- width: 1.25rem;
153
+ width: var(--space-20, 1.25rem);
150
154
  }
151
155
 
152
156
  .hk-number-input-sm .hk-number-input-step svg {
@@ -155,33 +159,33 @@
155
159
  }
156
160
 
157
161
  .hk-number-input-md .hk-number-input-field {
158
- font-size: 0.875rem;
159
- padding: 0 0.75rem;
162
+ font-size: var(--text-base, 0.875rem);
163
+ padding: 0 var(--space-12, 0.75rem);
160
164
  height: 2.25rem;
161
165
  }
162
166
 
163
167
  .hk-number-input-md .hk-number-input-prefix,
164
168
  .hk-number-input-md .hk-number-input-suffix {
165
- font-size: 0.875rem;
169
+ font-size: var(--text-base, 0.875rem);
166
170
  }
167
171
 
168
172
  .hk-number-input-md .hk-number-input-step {
169
- width: 1.5rem;
173
+ width: var(--space-24, 1.5rem);
170
174
  }
171
175
 
172
176
  .hk-number-input-lg .hk-number-input-field {
173
- font-size: 1rem;
174
- padding: 0 1rem;
177
+ font-size: var(--text-md, 1rem);
178
+ padding: 0 var(--space-16, 1rem);
175
179
  height: 2.75rem;
176
180
  }
177
181
 
178
182
  .hk-number-input-lg .hk-number-input-prefix,
179
183
  .hk-number-input-lg .hk-number-input-suffix {
180
- font-size: 1rem;
184
+ font-size: var(--text-md, 1rem);
181
185
  }
182
186
 
183
187
  .hk-number-input-lg .hk-number-input-step {
184
- width: 1.75rem;
188
+ width: var(--space-28, 1.75rem);
185
189
  }
186
190
 
187
191
  .hk-number-input-lg .hk-number-input-step svg {
@@ -33,8 +33,8 @@
33
33
  display: inline-flex;
34
34
  align-items: center;
35
35
  justify-content: center;
36
- width: 1.75rem;
37
- height: 1.75rem;
36
+ width: var(--space-28, 1.75rem);
37
+ height: var(--space-28, 1.75rem);
38
38
  flex-shrink: 0;
39
39
  border-radius: var(--radius-sm, 6px);
40
40
  background: rgb(var(--color-primary) / 10%);
@@ -59,8 +59,9 @@
59
59
  align-items: center;
60
60
  gap: var(--space-8, 0.5rem);
61
61
  flex-shrink: 0;
62
- /* Align the tool row with the title baseline; wrapped rows (narrow
63
- screens) fall back to stretch alignment via flex-wrap. */
62
+ /* Align the tool row with the title baseline; align-items: flex-end
63
+ applies per wrapped line too, and this 2px nudge optically matches
64
+ the title's descender space. */
64
65
  padding-block-end: var(--space-2, 0.125rem);
65
66
  }
66
67
 
@@ -73,7 +74,7 @@
73
74
  }
74
75
 
75
76
  .hk-page-header-icon {
76
- width: 1.5rem;
77
- height: 1.5rem;
77
+ width: var(--space-24, 1.5rem);
78
+ height: var(--space-24, 1.5rem);
78
79
  }
79
80
  }
@@ -16,7 +16,7 @@
16
16
  }
17
17
 
18
18
  .hk-pwd-required {
19
- margin-left: 0.125rem;
19
+ margin-inline-start: var(--space-2);
20
20
  color: var(--hi-color-danger, #f85149);
21
21
  }
22
22
 
@@ -25,7 +25,7 @@
25
25
  display: flex;
26
26
  align-items: center;
27
27
  min-height: 2.5rem;
28
- padding: 0.5rem 0.75rem;
28
+ padding: var(--space-8) var(--space-12);
29
29
  background: color-mix(in srgb, rgb(var(--color-surface)) 55%, transparent);
30
30
  border: 1px solid color-mix(in srgb, rgb(var(--color-text)) 14%, transparent);
31
31
  border-radius: 6px;
@@ -51,7 +51,7 @@
51
51
 
52
52
  .hk-pwd-box[data-error][data-focused] {
53
53
  border-color: var(--hi-color-danger, #f85149);
54
- box-shadow: 0 0 0 3px rgba(248, 81, 73, 0.15);
54
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--hi-color-danger, #f85149) 15%, transparent);
55
55
  }
56
56
 
57
57
  .hk-pwd-box[data-disabled] {
@@ -101,12 +101,15 @@
101
101
  cursor: pointer;
102
102
  }
103
103
 
104
- .hk-pwd-box:focus-within .hk-pwd-lock,
105
- .hk-pwd-box[data-focused] .hk-pwd-lock {
106
- color: var(--hi-color-primary, #58a6ff);
104
+ /* Hover feedback for the hold-to-reveal control, mirroring the blur
105
+ * hint's hover; suppressed while revealing (press restores full
106
+ * primary) and while disabled (reveal is unreachable). */
107
+ .hk-pwd-box:not([data-disabled]) .hk-pwd-lock-filled:hover:not([data-revealing]) {
108
+ color: color-mix(in srgb, var(--hi-color-primary, #58a6ff) 75%, transparent);
107
109
  }
108
110
 
109
- .hk-pwd-lock[data-revealing] {
111
+ .hk-pwd-box:focus-within .hk-pwd-lock,
112
+ .hk-pwd-box[data-focused] .hk-pwd-lock {
110
113
  color: var(--hi-color-primary, #58a6ff);
111
114
  }
112
115
 
@@ -128,7 +131,7 @@
128
131
  justify-content: center;
129
132
  z-index: 1;
130
133
  user-select: none;
131
- font-size: 0.875rem;
134
+ font-size: var(--text-base);
132
135
  color: var(--hi-color-primary, rgba(88, 166, 255, 0.7));
133
136
  animation: hk-pwd-breathe 3s ease-in-out infinite;
134
137
  /* Default overflow strategy: hard-cut with an ellipsis (the marquee
@@ -150,6 +153,17 @@
150
153
  50% { opacity: 0.7; }
151
154
  }
152
155
 
156
+ /* Reduced motion: stop the infinite breathe pulse. The global switch
157
+ * (`html[data-css-animations="0"]`) pauses it too when the host wired
158
+ * it; the media block covers hosts that never did. */
159
+ @media (prefers-reduced-motion: reduce) {
160
+ .hk-pwd-placeholder,
161
+ .hk-pwd-blur-hint,
162
+ .hk-pwd-select-hint {
163
+ animation: none;
164
+ }
165
+ }
166
+
153
167
  .hk-pwd-blur-hint {
154
168
  position: absolute;
155
169
  inset: 0;
@@ -158,7 +172,7 @@
158
172
  justify-content: center;
159
173
  z-index: 1;
160
174
  user-select: none;
161
- font-size: 0.875rem;
175
+ font-size: var(--text-base);
162
176
  font-style: italic;
163
177
  color: var(--hi-color-text-secondary, #666);
164
178
  cursor: pointer;
@@ -182,7 +196,7 @@
182
196
  justify-content: center;
183
197
  z-index: 1;
184
198
  user-select: none;
185
- font-size: 0.875rem;
199
+ font-size: var(--text-base);
186
200
  font-style: italic;
187
201
  color: var(--hi-color-text-secondary, #666);
188
202
  pointer-events: none;
@@ -199,7 +213,7 @@
199
213
  pointer-events: none;
200
214
  user-select: none;
201
215
  font-family: var(--font-mono, vars.$hikari-font-family-mono);
202
- font-size: 0.8125rem;
216
+ font-size: var(--text-sm);
203
217
  letter-spacing: 0.04em;
204
218
  color: var(--hi-color-text-primary, #333);
205
219
  }
@@ -220,7 +234,7 @@
220
234
 
221
235
  .hk-pwd-strength {
222
236
  position: absolute;
223
- right: 0.75rem;
237
+ inset-inline-end: var(--space-12);
224
238
  top: 50%;
225
239
  transform: translateY(-50%);
226
240
  width: 0.55rem;
@@ -235,29 +249,25 @@
235
249
 
236
250
  .hk-pwd-strength[data-level="weak"] {
237
251
  background: var(--hi-color-danger, #f85149);
238
- box-shadow: 0 0 6px rgba(248, 81, 73, 0.6);
252
+ box-shadow: 0 0 6px color-mix(in srgb, var(--hi-color-danger, #f85149) 60%, transparent);
239
253
  }
240
254
 
241
255
  .hk-pwd-strength[data-level="fair"] {
242
256
  background: var(--hi-color-warning, #d29922);
243
- box-shadow: 0 0 6px rgba(210, 153, 34, 0.6);
257
+ box-shadow: 0 0 6px color-mix(in srgb, var(--hi-color-warning, #d29922) 60%, transparent);
244
258
  }
245
259
 
246
260
  .hk-pwd-strength[data-level="strong"] {
247
261
  background: var(--hi-color-success, #3fb950);
248
- box-shadow: 0 0 6px rgba(63, 185, 80, 0.6);
249
- }
250
-
251
- .hk-pwd-hints {
252
- min-height: 0;
262
+ box-shadow: 0 0 6px color-mix(in srgb, var(--hi-color-success, #3fb950) 60%, transparent);
253
263
  }
254
264
 
255
265
  .hk-pwd-hint {
256
266
  display: inline-flex;
257
267
  align-items: center;
258
- gap: 0.25rem;
259
- margin-top: 0.25rem;
260
- font-size: 0.75rem;
268
+ gap: var(--space-4);
269
+ margin-top: var(--space-4);
270
+ font-size: var(--text-xs);
261
271
  }
262
272
 
263
273
  .hk-pwd-hint[data-variant="caps"] {
@@ -268,25 +278,15 @@
268
278
  color: var(--hi-color-danger, #f85149);
269
279
  }
270
280
 
271
- .hk-pwd-hint-enter-active,
272
- .hk-pwd-hint-leave-active {
273
- transition: opacity 0.3s ease;
274
- }
275
-
276
- .hk-pwd-hint-enter-from,
277
- .hk-pwd-hint-leave-to {
278
- opacity: 0;
279
- }
280
-
281
281
  .hk-pwd-error {
282
- margin-top: 0.25rem;
283
- font-size: 0.75rem;
282
+ margin-top: var(--space-4);
283
+ font-size: var(--text-xs);
284
284
  color: var(--hi-color-danger, #f85149);
285
285
  }
286
286
 
287
287
  .hk-pwd-hint-text {
288
- margin-top: 0.25rem;
289
- font-size: 0.75rem;
288
+ margin-top: var(--space-4);
289
+ font-size: var(--text-xs);
290
290
  color: var(--hi-color-text-secondary, #666);
291
291
  }
292
292
 
@@ -20,6 +20,22 @@
20
20
  transform: translateY(-8px);
21
21
  }
22
22
 
23
+ /* Reduced motion: keep the opacity cross-fade, drop the translateY
24
+ * slide. The global animation switch (`html[data-css-animations="0"]`)
25
+ * disables these transitions too when the host wired it; the media
26
+ * block covers hosts that never did. */
27
+ @media (prefers-reduced-motion: reduce) {
28
+ .hk-phase-enter-active,
29
+ .hk-phase-leave-active {
30
+ transition: opacity var(--hi-duration-fast, 0.15s) ease;
31
+ }
32
+
33
+ .hk-phase-enter-from,
34
+ .hk-phase-leave-to {
35
+ transform: none;
36
+ }
37
+ }
38
+
23
39
  .hk-phase-content {
24
40
  width: 100%;
25
41
  }
@@ -9,13 +9,11 @@
9
9
  * handling live there). A button so it is focusable and AT-reachable;
10
10
  * mousedown is suppressed in the shared picker so clicking it never
11
11
  * steals focus from the number field. */
12
- .hk-phone-chip {
13
- max-width: 11rem;
14
- }
15
-
12
+ /* Width cap comes from the shared .hk-affix-chip base (max-width: 11rem);
13
+ * this class stays a tsx/test hook for hosts to layer overrides on. */
16
14
  .hk-phone-chip-dial {
17
15
  font-weight: 600;
18
- font-size: var(--text-sm, 14px);
16
+ font-size: var(--text-sm, 0.8125rem);
19
17
  white-space: nowrap;
20
18
  font-variant-numeric: tabular-nums;
21
19
  }
@@ -24,3 +22,10 @@
24
22
  flex: none;
25
23
  color: rgb(var(--color-muted));
26
24
  }
25
+
26
+ /* Open disclosure state (aria-expanded sits on the chip button): flip the
27
+ * caret like the shared default chip does (HkAffixPicker.scss). Instant
28
+ * swap — no transition, so no reduced-motion entry is needed. */
29
+ .hk-phone-chip[aria-expanded="true"] .hk-phone-chip-caret {
30
+ transform: rotate(180deg);
31
+ }
@@ -44,7 +44,8 @@
44
44
  .hk-dp-stage,
45
45
  .hk-dtp-stage {
46
46
  position: relative;
47
- height: calc(var(--hk-picker-header) + var(--hk-picker-wd) + 6 * var(--hk-picker-cell) + 10px);
47
+ height: calc(var(--hk-picker-header) + var(--hk-picker-wd) + 6 * var(--hk-picker-cell) +
48
+ 5 * var(--space-2, 2px));
48
49
  }
49
50
 
50
51
  .hk-dp-pane,
@@ -72,22 +73,48 @@
72
73
 
73
74
  .hk-dp-stage[data-dir="fwd"] > .hk-picker-pane-enter-active,
74
75
  .hk-dtp-stage[data-dir="fwd"] > .hk-picker-pane-enter-active {
75
- animation: hk-picker-pane-fwd-in var(--hi-duration-normal, 0.18s) cubic-bezier(0.16, 1, 0.3, 1);
76
+ animation: hk-picker-pane-fwd-in var(--hi-duration-normal, 0.3s) cubic-bezier(0.16, 1, 0.3, 1);
76
77
  }
77
78
 
78
79
  .hk-dp-stage[data-dir="fwd"] > .hk-picker-pane-leave-active,
79
80
  .hk-dtp-stage[data-dir="fwd"] > .hk-picker-pane-leave-active {
80
- animation: hk-picker-pane-fwd-out var(--hi-duration-normal, 0.18s) cubic-bezier(0.16, 1, 0.3, 1);
81
+ animation: hk-picker-pane-fwd-out var(--hi-duration-normal, 0.3s) cubic-bezier(0.16, 1, 0.3, 1);
81
82
  }
82
83
 
83
84
  .hk-dp-stage[data-dir="back"] > .hk-picker-pane-enter-active,
84
85
  .hk-dtp-stage[data-dir="back"] > .hk-picker-pane-enter-active {
85
- animation: hk-picker-pane-back-in var(--hi-duration-normal, 0.18s) cubic-bezier(0.16, 1, 0.3, 1);
86
+ animation: hk-picker-pane-back-in var(--hi-duration-normal, 0.3s) cubic-bezier(0.16, 1, 0.3, 1);
86
87
  }
87
88
 
88
89
  .hk-dp-stage[data-dir="back"] > .hk-picker-pane-leave-active,
89
90
  .hk-dtp-stage[data-dir="back"] > .hk-picker-pane-leave-active {
90
- animation: hk-picker-pane-back-out var(--hi-duration-normal, 0.18s) cubic-bezier(0.16, 1, 0.3, 1);
91
+ animation: hk-picker-pane-back-out var(--hi-duration-normal, 0.3s) cubic-bezier(0.16, 1, 0.3, 1);
92
+ }
93
+
94
+ /* RTL: reading direction flips, so a forward drill flows left→right —
95
+ * swap the fwd/back keyframes (HkListTransition/HkBlockingToast
96
+ * [dir="rtl"] form; same registered keyframes, mirrored direction).
97
+ * Only the name is overridden: duration and easing carry over from the
98
+ * rules above, and the reduced-motion / data-css-animations guards below
99
+ * still win with !important. */
100
+ [dir="rtl"] .hk-dp-stage[data-dir="fwd"] > .hk-picker-pane-enter-active,
101
+ [dir="rtl"] .hk-dtp-stage[data-dir="fwd"] > .hk-picker-pane-enter-active {
102
+ animation-name: hk-picker-pane-back-in;
103
+ }
104
+
105
+ [dir="rtl"] .hk-dp-stage[data-dir="fwd"] > .hk-picker-pane-leave-active,
106
+ [dir="rtl"] .hk-dtp-stage[data-dir="fwd"] > .hk-picker-pane-leave-active {
107
+ animation-name: hk-picker-pane-back-out;
108
+ }
109
+
110
+ [dir="rtl"] .hk-dp-stage[data-dir="back"] > .hk-picker-pane-enter-active,
111
+ [dir="rtl"] .hk-dtp-stage[data-dir="back"] > .hk-picker-pane-enter-active {
112
+ animation-name: hk-picker-pane-fwd-in;
113
+ }
114
+
115
+ [dir="rtl"] .hk-dp-stage[data-dir="back"] > .hk-picker-pane-leave-active,
116
+ [dir="rtl"] .hk-dtp-stage[data-dir="back"] > .hk-picker-pane-leave-active {
117
+ animation-name: hk-picker-pane-fwd-out;
91
118
  }
92
119
 
93
120
  /* The leaving pane overlays the fixed stage so the entering pane can take
@@ -58,7 +58,11 @@
58
58
  }
59
59
 
60
60
  &__copy {
61
- padding-right: 24px;
61
+ // Trailing cushion between copies. Kept as a px literal (not a token)
62
+ // on purpose: it must equal COPY_SPACING = 24 in the tsx overflow math
63
+ // at every root font size. Logical axis so the cushion trails the
64
+ // reading direction under RTL hosts.
65
+ padding-inline-end: 24px;
62
66
  }
63
67
 
64
68
  // Hidden probe mode (text fits): visibility keeps the element laid out —
@@ -74,8 +78,9 @@
74
78
  width: 100%;
75
79
  overflow: hidden;
76
80
  text-overflow: ellipsis;
81
+ // white-space stays local (ellipsis-trio idiom); text-align: center
82
+ // was dropped — the root already centers and hosts may re-align it.
77
83
  white-space: nowrap;
78
- text-align: center;
79
84
  }
80
85
  }
81
86
 
@@ -84,6 +84,33 @@ afterEach(() => {
84
84
  }
85
85
  });
86
86
 
87
+ describe("HkPlaceholderMarquee scale-aware measurement", () => {
88
+ it("converts the copy's drawn width into the host's own units before looping", async () => {
89
+ // The copy is measured where it is DRAWN while the host it is compared
90
+ // with (`clientWidth`) is laid out. Inside a scaled or zoomed root the two
91
+ // differ by the factor the space is drawn at: the raw rect would make the
92
+ // loop overshoot by (k-1) of a copy per cycle and report overflow for
93
+ // copies that fit k times over.
94
+ const { container } = mountMarquee({ text: "a very long placeholder that overflows".repeat(4) });
95
+ const host = container.querySelector(".hk-placeholder-marquee") as HTMLElement;
96
+ const copy = container.querySelector(".hk-placeholder-marquee__copy") as HTMLElement;
97
+ Object.defineProperty(host, "clientWidth", { configurable: true, get: () => 200 });
98
+ // The host is drawn twice the size it is laid out at: 800 drawn px are
99
+ // 400 of its own, while the copy really advances 500 of them.
100
+ Object.defineProperty(host, "getBoundingClientRect", {
101
+ configurable: true,
102
+ value: () => ({ width: 400, left: 0, top: 0, right: 400, bottom: 40, height: 40, x: 0, y: 0 }) as DOMRect,
103
+ });
104
+ Object.defineProperty(host, "offsetWidth", { configurable: true, get: () => 200 });
105
+ vi.spyOn(copy, "getBoundingClientRect").mockReturnValue({ width: 1000 } as DOMRect);
106
+ marqueeExposed(container)?.measure?.();
107
+ await nextTick();
108
+
109
+ const strip = container.querySelector(".hk-placeholder-marquee__strip") as HTMLElement;
110
+ expect(strip.style.getPropertyValue("--hk-marquee-shift"), "1000 drawn = 500 of the host's own").toBe("-500px");
111
+ });
112
+ });
113
+
87
114
  describe("HkPlaceholderMarquee", () => {
88
115
  it("stays a hidden single-copy probe while the text fits", () => {
89
116
  const { container } = mountMarquee({ text: "用户名" });
@@ -1,4 +1,5 @@
1
1
  import { defineComponent, ref, computed, onMounted, onBeforeUnmount, watch, type PropType } from "vue";
2
+ import { drawnScale } from "../composables/layoutGeometry";
2
3
 
3
4
  /**
4
5
  * Overflow strategy for a placeholder that does not fit its input.
@@ -64,12 +65,19 @@ export const HkPlaceholderMarquee = defineComponent({
64
65
  // copy spacing as trailing padding). Reading the first copy element —
65
66
  // instead of stripWidth / 3 — stays correct in the fitting case,
66
67
  // where the strip holds a single copy, and through overflow flips,
67
- // where the copy count changes under the same strip. The fractional
68
- // bounding-rect width keeps the CSS loop shift bit-exact with the
69
- // laid-out advance — an integer-rounded width would show a ≤0.5px
70
- // seam at every wrap (the copy rides the animated strip, so the
71
- // width itself is never affected by the strip's transform).
72
- const copyWidth = copy.getBoundingClientRect().width;
68
+ // where the copy count changes under the same strip. The width is read
69
+ // where the copy is DRAWN and converted into the host's own units,
70
+ // because that is the space the CSS loop shift and `clientWidth` live
71
+ // in: inside a scaled or zoomed root the raw rect is k times the
72
+ // advance, so the loop would overshoot by (k-1) per cycle and the
73
+ // overflow test would fire on copies that fit k times over. The
74
+ // conversion keeps the fractional width that makes the shift
75
+ // bit-exact — an integer-rounded width would show a ≤0.5px seam at
76
+ // every wrap (the copy rides the animated strip, so the width itself
77
+ // is never affected by the strip's transform).
78
+ const space = drawnScale(host);
79
+ const factor = space.x > 0 ? space.x : 1;
80
+ const copyWidth = copy.getBoundingClientRect().width / factor;
73
81
  loopWidth.value = copyWidth;
74
82
  overflowing.value = copyWidth - COPY_SPACING > host.clientWidth;
75
83
  emit("overflowChange", overflowing.value);
@@ -68,7 +68,7 @@
68
68
  border: 1px solid rgb(var(--color-border) / 20%);
69
69
  border-radius: var(--radius-sm);
70
70
  box-shadow: var(--shadow-dropdown);
71
- padding: var(--space-4, 12px);
71
+ padding: var(--space-4, 0.25rem);
72
72
  }
73
73
 
74
74
  /* Locale picker menu items — unified menu-item spec (./_menu-item.scss). */
@@ -76,10 +76,6 @@
76
76
  @include mi.item;
77
77
  }
78
78
 
79
- .hk-popup-menu-item[data-active]:hover {
80
- background: var(--c-primary-dim, rgb(var(--color-primary) / 10%));
81
- }
82
-
83
79
  .hk-locale-submenu {
84
80
  min-width: 140px;
85
81
  padding: var(--space-6);
@@ -125,6 +121,9 @@
125
121
  border-left: 0;
126
122
  border-right: 0;
127
123
  border-bottom: 0;
124
+ // The panel is a programmatic focus surface (tabindex="-1", focused on
125
+ // sheet open) — suppress the UA focus ring like .hk-modal-content.
126
+ outline: none;
128
127
  max-height: min(
129
128
  72vh,
130
129
  calc(
@@ -203,8 +202,11 @@
203
202
  display: flex;
204
203
  align-items: center;
205
204
  gap: var(--space-8, 0.5rem);
206
- padding: 0 var(--space-8, 0.5rem) var(--space-8, 0.5rem)
207
- var(--hk-sheet-title-inset, var(--space-12, 0.75rem));
205
+ // Logical longhand (was physical `0 x y title-inset`): the title inset is
206
+ // the inline START edge so RTL mirrors it with the mirrored title/close.
207
+ padding-block: 0 var(--space-8, 0.5rem);
208
+ padding-inline: var(--hk-sheet-title-inset, var(--space-12, 0.75rem))
209
+ var(--space-8, 0.5rem);
208
210
  }
209
211
 
210
212
  .hk-popover-sheet-title {
@@ -220,7 +222,7 @@
220
222
  font-size: var(--text-base, 0.875rem);
221
223
  font-weight: 600;
222
224
  letter-spacing: 0.02em;
223
- color: rgb(var(--color-muted, 140 140 140));
225
+ color: rgb(var(--color-muted, 108 108 108));
224
226
  user-select: none;
225
227
  -webkit-user-select: none;
226
228
  }
@@ -228,8 +230,9 @@
228
230
  .hk-popover-sheet-close {
229
231
  // Legacy placement hook — the chrome lives on the shared .hk-icon-button
230
232
  // / .hk-window-close rules (window-close.scss) since the unified-close
231
- // wave. Only the right-edge pinning stays here.
232
- margin-left: auto;
233
+ // wave. Only the trailing-edge pinning stays here (logical, so the close
234
+ // mirrors to the left in RTL like HkInput/HkTagInput's auto inline start).
235
+ margin-inline-start: auto;
233
236
  }
234
237
 
235
238
  .hk-popover-sheet-enter-active {
@@ -257,6 +260,11 @@
257
260
  @include sf.scrim-fade("hk-popover-scrim");
258
261
 
259
262
  @media (prefers-reduced-motion: reduce) {
263
+ // Pop transitions included: the anchored panel's translateY+scale pop is
264
+ // the same non-trivial motion the sheet slide already silences here
265
+ // (house pattern: HkExpansionPanel.scss / HkAffixPicker.scss guards).
266
+ .hk-popover-enter-active,
267
+ .hk-popover-leave-active,
260
268
  .hk-popover-sheet-enter-active,
261
269
  .hk-popover-sheet-leave-active {
262
270
  transition: none;