@celestia-island/hikari 0.55.304 → 0.55.306
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/HkBreadcrumb.tsx +3 -3
- package/src/components/HkDraggableGrid.tsx +3 -8
- package/src/components/HkDraggableList.tsx +3 -8
- package/src/components/HkEmptyState.tsx +3 -13
- package/src/components/HkExpansionPanel.tsx +3 -3
- package/src/components/HkFab.test.tsx +4 -3
- package/src/components/HkFab.tsx +5 -13
- package/src/components/HkLocalePicker.tsx +3 -8
- package/src/components/HkModal.open-close-scale.test.ts +75 -72
- package/src/components/HkModal.scss +17 -51
- package/src/components/HkModalBreadcrumb.tsx +3 -12
- package/src/components/HkRangeRibbon.scss +32 -1
- package/src/components/HkRangeRibbon.test.tsx +54 -1
- package/src/components/HkRangeRibbon.tsx +80 -11
- package/src/components/HkSignInCard.tsx +3 -14
- package/src/components/HkStepGuide.tsx +3 -14
- package/src/components/HkTable.tsx +7 -42
- package/src/components/HkTree.tsx +3 -3
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChevronRight } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { computed, defineComponent, type PropType } from "vue";
|
|
2
4
|
import { safeHref } from "../utils/safeHref";
|
|
3
5
|
import { useI18n } from "../i18n/context";
|
|
@@ -26,9 +28,7 @@ export default defineComponent({
|
|
|
26
28
|
)}
|
|
27
29
|
{index < props.items.length - 1 && (
|
|
28
30
|
<span class="hk-breadcrumb-separator">
|
|
29
|
-
<
|
|
30
|
-
<polyline points="9 18 15 12 9 6" />
|
|
31
|
-
</svg>
|
|
31
|
+
<ChevronRight />
|
|
32
32
|
</span>
|
|
33
33
|
)}
|
|
34
34
|
</li>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { GripVertical } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { defineComponent, onBeforeUnmount, ref, shallowRef, watch, type PropType } from "vue";
|
|
2
4
|
import { onceFrame } from "../runtime/animationBus";
|
|
3
5
|
import { useReportedTransition } from "../composables/useReportedTransition";
|
|
@@ -271,14 +273,7 @@ export default defineComponent({
|
|
|
271
273
|
}}
|
|
272
274
|
>
|
|
273
275
|
{slots.handle ? slots.handle() : (
|
|
274
|
-
<
|
|
275
|
-
<circle cx="4" cy="3" r="1.2" />
|
|
276
|
-
<circle cx="4" cy="7" r="1.2" />
|
|
277
|
-
<circle cx="4" cy="11" r="1.2" />
|
|
278
|
-
<circle cx="10" cy="3" r="1.2" />
|
|
279
|
-
<circle cx="10" cy="7" r="1.2" />
|
|
280
|
-
<circle cx="10" cy="11" r="1.2" />
|
|
281
|
-
</svg>
|
|
276
|
+
<GripVertical size={14} />
|
|
282
277
|
)}
|
|
283
278
|
</span>
|
|
284
279
|
<div class="hk-draggable-grid-content">
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { GripVertical } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { defineComponent, onBeforeUnmount, onMounted, ref, shallowRef, type PropType } from "vue";
|
|
2
4
|
import { onceFrame } from "../runtime/animationBus";
|
|
3
5
|
import { useReportedTransition } from "../composables/useReportedTransition";
|
|
@@ -349,14 +351,7 @@ export default defineComponent({
|
|
|
349
351
|
}}
|
|
350
352
|
>
|
|
351
353
|
{slots.handle ? slots.handle() : (
|
|
352
|
-
<
|
|
353
|
-
<circle cx="4" cy="3" r="1.2" />
|
|
354
|
-
<circle cx="4" cy="7" r="1.2" />
|
|
355
|
-
<circle cx="4" cy="11" r="1.2" />
|
|
356
|
-
<circle cx="10" cy="3" r="1.2" />
|
|
357
|
-
<circle cx="10" cy="7" r="1.2" />
|
|
358
|
-
<circle cx="10" cy="11" r="1.2" />
|
|
359
|
-
</svg>
|
|
354
|
+
<GripVertical size={14} />
|
|
360
355
|
)}
|
|
361
356
|
</span>
|
|
362
357
|
<div class="hk-draggable-list-content">
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Package } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { defineComponent, h, type Component, type PropType } from "vue";
|
|
2
4
|
|
|
3
5
|
import { useI18n } from "../i18n/context";
|
|
@@ -56,19 +58,7 @@ export default defineComponent({
|
|
|
56
58
|
) : props.icon ? (
|
|
57
59
|
h(props.icon, { size: 32, "aria-hidden": true })
|
|
58
60
|
) : (
|
|
59
|
-
<
|
|
60
|
-
viewBox="0 0 24 24"
|
|
61
|
-
fill="none"
|
|
62
|
-
stroke="currentColor"
|
|
63
|
-
stroke-width="1.5"
|
|
64
|
-
stroke-linecap="round"
|
|
65
|
-
stroke-linejoin="round"
|
|
66
|
-
width="48"
|
|
67
|
-
height="48"
|
|
68
|
-
>
|
|
69
|
-
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
|
|
70
|
-
<path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
|
|
71
|
-
</svg>
|
|
61
|
+
<Package size={48} stroke-width={1.5} />
|
|
72
62
|
)}
|
|
73
63
|
</div>
|
|
74
64
|
{props.title ? <p class="hk-empty-title">{props.title}</p> : null}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChevronDown } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { computed, defineComponent, ref, watch } from "vue";
|
|
2
4
|
import "./HkExpansionPanel.scss";
|
|
3
5
|
|
|
@@ -87,9 +89,7 @@ export default defineComponent({
|
|
|
87
89
|
</div>
|
|
88
90
|
)}
|
|
89
91
|
<span class="hk-expansion-panel-chevron" aria-hidden="true">
|
|
90
|
-
<
|
|
91
|
-
<polyline points="6 9 12 15 18 9" />
|
|
92
|
-
</svg>
|
|
92
|
+
<ChevronDown />
|
|
93
93
|
</span>
|
|
94
94
|
</button>
|
|
95
95
|
<div class="hk-expansion-panel-body">
|
|
@@ -64,11 +64,12 @@ afterEach(() => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
describe("HkFab shell", () => {
|
|
67
|
-
it("renders a round button with the aria label and the default chevron
|
|
67
|
+
it("renders a round button with the aria label and the default chevron glyph", () => {
|
|
68
68
|
const m = mountFab({ ariaLabel: "Jump to latest" });
|
|
69
69
|
expect(m.button.getAttribute("aria-label")).toBe("Jump to latest");
|
|
70
|
-
// No icon prop ->
|
|
71
|
-
|
|
70
|
+
// No icon prop -> the standard lucide chevron-down (wave B4: the
|
|
71
|
+
// hand-drawn inline fallback retired).
|
|
72
|
+
expect(m.button.querySelector("svg.lucide-chevron-down")).not.toBeNull();
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
it("mirrors positioning/corner/size/variant props onto data attributes", () => {
|
package/src/components/HkFab.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChevronDown } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
defineComponent,
|
|
3
5
|
h,
|
|
@@ -141,19 +143,9 @@ export default defineComponent({
|
|
|
141
143
|
: RegistryIcon
|
|
142
144
|
? h(RegistryIcon)
|
|
143
145
|
: (
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
<
|
|
147
|
-
viewBox="0 0 24 24"
|
|
148
|
-
fill="none"
|
|
149
|
-
stroke="currentColor"
|
|
150
|
-
stroke-width="2"
|
|
151
|
-
stroke-linecap="round"
|
|
152
|
-
stroke-linejoin="round"
|
|
153
|
-
aria-hidden="true"
|
|
154
|
-
>
|
|
155
|
-
<polyline points="6 9 12 15 18 9" />
|
|
156
|
-
</svg>
|
|
146
|
+
// Wave B4: the hand-drawn fallback retired — the standard
|
|
147
|
+
// lucide component, same channel as every other control.
|
|
148
|
+
<ChevronDown aria-hidden="true" />
|
|
157
149
|
)}
|
|
158
150
|
</span>
|
|
159
151
|
);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChevronDown } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { defineComponent, ref, type PropType } from "vue";
|
|
2
4
|
|
|
3
5
|
import HkLocalePickerPopup from "./HkLocalePickerPopup";
|
|
@@ -50,14 +52,7 @@ export const HkLocalePicker = defineComponent({
|
|
|
50
52
|
onClick={() => (open.value = !open.value)}
|
|
51
53
|
>
|
|
52
54
|
{props.currentLocale.toUpperCase()}
|
|
53
|
-
<
|
|
54
|
-
<path
|
|
55
|
-
d="M2 3.5l3 3 3-3"
|
|
56
|
-
fill="none"
|
|
57
|
-
stroke="currentColor"
|
|
58
|
-
stroke-width="1.2"
|
|
59
|
-
/>
|
|
60
|
-
</svg>
|
|
55
|
+
<ChevronDown size={10} aria-hidden="true" />
|
|
61
56
|
</button>
|
|
62
57
|
<HkLocalePickerPopup
|
|
63
58
|
open={open.value}
|
|
@@ -2,21 +2,19 @@
|
|
|
2
2
|
* Behavioral contract for the DESKTOP modal open/close choreography — run
|
|
3
3
|
* against the COMPILED stylesheet, not the source text.
|
|
4
4
|
*
|
|
5
|
-
* User direction 2026-11-
|
|
6
|
-
* the
|
|
7
|
-
* `translate(…) scaleY(0.9) →
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* body-only uniform `scale(0.9)`, and the 2026-11-06 `scale(1.1, 1)`
|
|
19
|
-
* overshoot.
|
|
5
|
+
* User direction 2026-11-07 (supersedes the 2026-11-05 content-only ride
|
|
6
|
+
* and the 2026-11-06 body scales): ONLY THE HEIGHT COMPRESSES — width is
|
|
7
|
+
* never scaled. The frame settles `translate(…) scaleY(0.9) →
|
|
8
|
+
* translate(…) scaleY(1)` about its center, so the box grows symmetrically
|
|
9
|
+
* and anything centered in it (the content region above all) stays
|
|
10
|
+
* horizontally AND vertically centered at every t; the translate prefix is
|
|
11
|
+
* byte-identical in every state so the ride can never re-seat the frame.
|
|
12
|
+
* `.hk-modal-body` carries NO transform rule of its own: its vertical ride
|
|
13
|
+
* is entirely the frame's scaleY, so its width stays exactly 100% while its
|
|
14
|
+
* height follows the 0.9 → 1 compression. The retired grammars, oldest
|
|
15
|
+
* first: the 2026-11-04 whole-frame `scale(1, 0.9)`, the 2026-11-05
|
|
16
|
+
* body-only uniform `scale(0.9)`, the 2026-11-06 `scale(1.1, 1)` overshoot
|
|
17
|
+
* and its same-day `scale(0.95, 1)` pinch.
|
|
20
18
|
*
|
|
21
19
|
* Why compile: a source-text scanner can be dodged by a later duplicate
|
|
22
20
|
* rule, a block comment, SCSS interpolation, `@extend` or an `@use`d
|
|
@@ -343,12 +341,8 @@ const ENTER_TRANSITION =
|
|
|
343
341
|
const LEAVE_TRANSITION =
|
|
344
342
|
"opacity var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1)), " +
|
|
345
343
|
"transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
|
|
346
|
-
const BODY_ENTER_TRANSITION =
|
|
347
|
-
"transform var(--hk-modal-duration, 0.42s) var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1))";
|
|
348
|
-
const BODY_LEAVE_TRANSITION =
|
|
349
|
-
"transform var(--hk-modal-duration, 0.32s) var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1))";
|
|
350
344
|
|
|
351
|
-
describe("HkModal desktop open/close contract (compiled cascade, user direction 2026-11-
|
|
345
|
+
describe("HkModal desktop open/close contract (compiled cascade, user direction 2026-11-07)", () => {
|
|
352
346
|
it("compiles into a stylesheet with the ride rules present (known-positive)", () => {
|
|
353
347
|
// Guard against a vacuous suite: the compiled sheet must really contain
|
|
354
348
|
// the frame rule, the body rule and the reduced-motion flatten.
|
|
@@ -437,49 +431,27 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
437
431
|
expect(leaveWill).toBe("opacity, transform");
|
|
438
432
|
});
|
|
439
433
|
|
|
440
|
-
it("
|
|
441
|
-
// 2026-11-
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
.
|
|
453
|
-
.
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
expect_("leave-from", "scale(1, 1)");
|
|
461
|
-
// …and the same with the opt-in draggable flag on, where an
|
|
462
|
-
// attribute-qualified rule could otherwise slip past the mocks.
|
|
463
|
-
for (const state of RIDE) {
|
|
464
|
-
const frame = draggableFrameWith(...state.classes);
|
|
465
|
-
const body = child(frame, "hk-modal-body");
|
|
466
|
-
const expected =
|
|
467
|
-
state.name === "enter-from" || state.name === "leave-to" ? "scale(0.95, 1)" : "scale(1, 1)";
|
|
468
|
-
expect(winner(body, "transform", DESKTOP), `draggable ${state.name}`).toBe(expected);
|
|
434
|
+
it("never scales the content region: width stays 100% at every t", () => {
|
|
435
|
+
// 2026-11-07: only the height compresses. The body must carry no
|
|
436
|
+
// transform of its own in ANY ride state — its vertical ride is the
|
|
437
|
+
// frame's scaleY inherited down the tree, so a body transform here
|
|
438
|
+
// would either re-scale the width or double the Y compression.
|
|
439
|
+
const REST_OR_RIDE = [...RIDE, { name: "rest", classes: [] as string[] }];
|
|
440
|
+
// The whole transform family, on BOTH branches (R2 finding 2026-11-07
|
|
441
|
+
// (S2): the draggable branch used to pin `transform` only, so a
|
|
442
|
+
// `[data-draggable] … { scale: … }` longhand stayed green).
|
|
443
|
+
const PROPS = ["transform", "transform-origin", "scale", "translate", "rotate", "zoom", "perspective"];
|
|
444
|
+
for (const state of REST_OR_RIDE) {
|
|
445
|
+
for (const [label, frame] of [
|
|
446
|
+
["", frameWith(...state.classes)],
|
|
447
|
+
["draggable ", draggableFrameWith(...state.classes)],
|
|
448
|
+
] as const) {
|
|
449
|
+
const body = child(frame, "hk-modal-body");
|
|
450
|
+
for (const prop of PROPS) {
|
|
451
|
+
expect(winner(body, prop, DESKTOP), `${label}${state.name} body ${prop}`).toBeUndefined();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
469
454
|
}
|
|
470
|
-
// At rest the body carries no transform at all.
|
|
471
|
-
expect(winner(child(frameWith(), "hk-modal-body"), "transform", DESKTOP)).toBeUndefined();
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it("rides the content scale on the family timing tokens", () => {
|
|
475
|
-
const body = (state: string, prop: string) => {
|
|
476
|
-
const frame = frameWith(...RIDE.find((r) => r.name === state)!.classes);
|
|
477
|
-
return winner(child(frame, "hk-modal-body"), prop, DESKTOP);
|
|
478
|
-
};
|
|
479
|
-
// Whole-value pins, same rationale as the frame transitions above.
|
|
480
|
-
expect(body("enter-from", "transition")).toBe(BODY_ENTER_TRANSITION);
|
|
481
|
-
expect(body("leave-to", "transition")).toBe(BODY_LEAVE_TRANSITION);
|
|
482
|
-
expect(body("enter-from", "will-change")).toBe("transform");
|
|
483
455
|
});
|
|
484
456
|
|
|
485
457
|
it("never transforms the chrome: title bar, sub-header, footer, close button, title glyph", () => {
|
|
@@ -509,7 +481,7 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
509
481
|
}
|
|
510
482
|
});
|
|
511
483
|
|
|
512
|
-
it("flattens the
|
|
484
|
+
it("flattens the frame ride (and any stray body transform) under prefers-reduced-motion", () => {
|
|
513
485
|
const frame = frameWith("hk-modal-content-enter-from", "hk-modal-content-enter-active");
|
|
514
486
|
const body = child(frame, "hk-modal-body");
|
|
515
487
|
expect(winner(body, "transform", DESKTOP_REDUCED)).toBe("none");
|
|
@@ -562,6 +534,35 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
562
534
|
).toBeUndefined();
|
|
563
535
|
});
|
|
564
536
|
|
|
537
|
+
it("never lets ANY rule put a ride transform on the content region", () => {
|
|
538
|
+
// Fail-closed sweep, selector-level and media-independent (R2 finding
|
|
539
|
+
// 2026-11-07 (S2): the cascade mock cannot match `:not()`, a type
|
|
540
|
+
// qualifier, an extra media gate or a pseudo-element, so twelve such
|
|
541
|
+
// mutations stayed green). Every rule that targets the body — in any
|
|
542
|
+
// media query, at any specificity — must never SCALE it. Allowed
|
|
543
|
+
// anywhere: an inert `transform: none` (the reduced-motion guard) and
|
|
544
|
+
// any `transform-origin`, which does nothing without a transform.
|
|
545
|
+
//
|
|
546
|
+
// Class-TOKEN based, not class-dot based: `div.hk-modal-body` (the same
|
|
547
|
+
// compound, type-qualified) puts a letter before the dot and slipped
|
|
548
|
+
// through the first cut, as did `[class~="hk-modal-body"]` (R3 finding
|
|
549
|
+
// 2026-11-07 (S2)). The trailing guard keeps -scroll / -inner out.
|
|
550
|
+
const BODY_TARGET = /(^|[^\w-])hk-modal-body(?![\w-])/;
|
|
551
|
+
const TRANSFORM_FAMILY = /^(transform|transform-origin|translate|scale|rotate|zoom|perspective)$/;
|
|
552
|
+
const INERT = new Set(["none", "auto"]);
|
|
553
|
+
const offenders: string[] = [];
|
|
554
|
+
for (const rule of RULES) {
|
|
555
|
+
if (!BODY_TARGET.test(rule.selector)) continue;
|
|
556
|
+
for (const prop of rule.declarations.keys()) {
|
|
557
|
+
if (!TRANSFORM_FAMILY.test(prop)) continue;
|
|
558
|
+
if (prop === "transform-origin") continue;
|
|
559
|
+
const value = (rule.declarations.get(prop) ?? "").replace(/\s+/g, " ").trim();
|
|
560
|
+
if (!INERT.has(value)) offenders.push(`${rule.selector} { ${prop}: ${value} }`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
expect(offenders, "the content region must never be scaled").toEqual([]);
|
|
564
|
+
});
|
|
565
|
+
|
|
565
566
|
it("never lets ANY rule put a transform on a chrome surface", () => {
|
|
566
567
|
// Fail-closed sweep: selector-level and media-independent, so a chrome
|
|
567
568
|
// transform is caught whatever the combinator, grouping, attribute
|
|
@@ -598,9 +599,11 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
598
599
|
const CHROME_ALLOWED = new Set(["opacity", "transition", "will-change", "transform"]);
|
|
599
600
|
// The contracted transform values a ride rule may carry. Frame: the
|
|
600
601
|
// three translate+scaleY states (reduced-motion flatten included);
|
|
601
|
-
// body:
|
|
602
|
+
// body: nothing (its width is never scaled — 2026-11-07).
|
|
602
603
|
const LEGAL_FRAME = new Set([CENTERING, FRAME_FROM, FRAME_TO]);
|
|
603
|
-
|
|
604
|
+
// The body carries no transform contract any more (2026-11-07): any
|
|
605
|
+
// ride rule targeting it is an offender outright.
|
|
606
|
+
const LEGAL_BODY = new Set<string>([]);
|
|
604
607
|
const offenders: string[] = [];
|
|
605
608
|
// Media-independent on purpose: a ride selector declared at top level
|
|
606
609
|
// (no media query) applies at every width, so it must obey the same
|
|
@@ -641,19 +644,19 @@ describe("HkModal desktop open/close contract (compiled cascade, user direction
|
|
|
641
644
|
});
|
|
642
645
|
|
|
643
646
|
it("keeps the contracted per-axis grammar (no alternate syntax escapes)", () => {
|
|
644
|
-
// 2026-11-
|
|
645
|
-
// body rides
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
// properties — would break the contracted
|
|
649
|
-
// be impossible.
|
|
647
|
+
// 2026-11-07 grammar: the FRAME rides `translate(…) scaleY(s)` and the
|
|
648
|
+
// body rides nothing. Any other expression of the same idea — a
|
|
649
|
+
// single-arg scale or a scaleX on the frame list, a two-arg scale, any
|
|
650
|
+
// body transform, scale3d, a computed matrix, or the independent
|
|
651
|
+
// transform properties — would break the contracted shape (or the
|
|
652
|
+
// width contract) and must be impossible.
|
|
650
653
|
const BODY_ESCAPES = /scale[XYZ]\(|scale3d\(|matrix[23]d?\(/;
|
|
651
654
|
const FRAME_ESCAPES = /scale\([^)]*,|scaleX\(|scale3d\(|matrix[23]d?\(|\brotate\(/;
|
|
652
655
|
for (const state of RIDE) {
|
|
653
656
|
const frame = frameWith(...state.classes);
|
|
654
657
|
const body = child(frame, "hk-modal-body");
|
|
655
658
|
const bodyTransform = winner(body, "transform", DESKTOP) ?? "";
|
|
656
|
-
expect(bodyTransform, `body ${state.name}`).
|
|
659
|
+
expect(bodyTransform, `body ${state.name}`).toBe("");
|
|
657
660
|
expect(bodyTransform, `body ${state.name}`).not.toMatch(BODY_ESCAPES);
|
|
658
661
|
expect(winner(body, "scale", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
659
662
|
expect(winner(body, "translate", DESKTOP), `body ${state.name}`).toBeUndefined();
|
|
@@ -113,23 +113,21 @@
|
|
|
113
113
|
// transform rule of its own; it inherits this single frame scale, which
|
|
114
114
|
// IS the window-height motion the user asked for.
|
|
115
115
|
//
|
|
116
|
-
// The content region
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// would produce.
|
|
116
|
+
// The content region does NOT ride on its own (user direction 2026-11-07,
|
|
117
|
+
// superseding the body scale that shipped in 0.55.303 / 0.55.304): width
|
|
118
|
+
// is never scaled, only height is. `.hk-modal-body` therefore carries NO
|
|
119
|
+
// transform rule in any ride state — its vertical ride is entirely the
|
|
120
|
+
// frame's `scaleY` above, so during the open/close its width stays exactly
|
|
121
|
+
// 100% of the window while its height follows the 0.9 → 1 compression. Two
|
|
122
|
+
// retired grammars before this one: the two-arg `scale(0.95, 1)` pinch and
|
|
123
|
+
// the `scale(1.1, 1)` overshoot.
|
|
125
124
|
//
|
|
126
125
|
// Hosts retune the ride through the `--hk-modal-*` tokens (duration /
|
|
127
126
|
// easing) or the body padding vars — NOT by setting `transform` on
|
|
128
|
-
// `.hk-modal-body` or the frame: the state rules pair matched
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
// reduced motion.
|
|
127
|
+
// `.hk-modal-body` or the frame: the frame's state rules pair a matched
|
|
128
|
+
// function list (`translate + scaleY`) so both ends of the interpolation
|
|
129
|
+
// ride the same shape, and a host's single-class transform would be
|
|
130
|
+
// overridden mid-ride and again under reduced motion.
|
|
133
131
|
//
|
|
134
132
|
// No clip-path and no Y offset ride the open/close: the visible box IS the
|
|
135
133
|
// frame at every t, so nothing is ever sliced off (the 2026-09-14 clip
|
|
@@ -153,7 +151,7 @@
|
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
// Close: the reveal's TRUE reverse — same chrome fade, while the window
|
|
156
|
-
// settles back toward 90% height
|
|
154
|
+
// settles back toward 90% height, width untouched in both directions.
|
|
157
155
|
.hk-modal-content-leave-active {
|
|
158
156
|
transition:
|
|
159
157
|
opacity var(--hk-modal-duration, 0.32s)
|
|
@@ -185,37 +183,6 @@
|
|
|
185
183
|
)
|
|
186
184
|
scaleY(1);
|
|
187
185
|
}
|
|
188
|
-
|
|
189
|
-
// Content region: the one surface with its own transform. The timing
|
|
190
|
-
// lives on the per-state transition so the enter and the leave can
|
|
191
|
-
// differ (0.42s open / 0.32s close) exactly like the chrome fade does.
|
|
192
|
-
.hk-modal-content-enter-active .hk-modal-body {
|
|
193
|
-
transform-origin: 50% 50%;
|
|
194
|
-
transition:
|
|
195
|
-
transform var(--hk-modal-duration, 0.42s)
|
|
196
|
-
var(--hk-modal-ease-out, cubic-bezier(0.33, 1, 0.68, 1));
|
|
197
|
-
will-change: transform;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.hk-modal-content-leave-active .hk-modal-body {
|
|
201
|
-
transform-origin: 50% 50%;
|
|
202
|
-
transition:
|
|
203
|
-
transform var(--hk-modal-duration, 0.32s)
|
|
204
|
-
var(--hk-modal-ease-in, cubic-bezier(0.45, 0, 0.55, 1));
|
|
205
|
-
will-change: transform;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Both ends two-arg `scale`: X starts at 95% and spreads to 1, Y stays 1
|
|
209
|
-
// (the body's vertical ride comes from the frame's scaleY, not itself).
|
|
210
|
-
.hk-modal-content-enter-from .hk-modal-body,
|
|
211
|
-
.hk-modal-content-leave-to .hk-modal-body {
|
|
212
|
-
transform: scale(0.95, 1);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.hk-modal-content-enter-to .hk-modal-body,
|
|
216
|
-
.hk-modal-content-leave-from .hk-modal-body {
|
|
217
|
-
transform: scale(1, 1);
|
|
218
|
-
}
|
|
219
186
|
}
|
|
220
187
|
|
|
221
188
|
// ------
|
|
@@ -704,14 +671,13 @@
|
|
|
704
671
|
calc(-50% + var(--hk-modal-shift-y, 0px))
|
|
705
672
|
);
|
|
706
673
|
}
|
|
707
|
-
// The content
|
|
708
|
-
//
|
|
709
|
-
//
|
|
674
|
+
// The content region carries no transform of its own any more (width is
|
|
675
|
+
// never scaled — 2026-11-07), so only the frame needs flattening above.
|
|
676
|
+
// The body lines stay as a fail-closed guard: if a future state rule ever
|
|
677
|
+
// reintroduces a body transform, reduced motion still flattens it.
|
|
710
678
|
.hk-modal-content .hk-modal-body {
|
|
711
679
|
transform: none;
|
|
712
680
|
transition: none;
|
|
713
|
-
// …and drop the compositor hint the ride would otherwise leave on
|
|
714
|
-
// the (tall) content block for the flattened frame.
|
|
715
681
|
will-change: auto;
|
|
716
682
|
}
|
|
717
683
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChevronRight } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
computed,
|
|
3
5
|
defineComponent,
|
|
@@ -626,18 +628,7 @@ export default defineComponent({
|
|
|
626
628
|
.join(" ");
|
|
627
629
|
|
|
628
630
|
const separator = () => (
|
|
629
|
-
<
|
|
630
|
-
class="hk-modal-breadcrumb-sep"
|
|
631
|
-
width="12"
|
|
632
|
-
height="12"
|
|
633
|
-
viewBox="0 0 24 24"
|
|
634
|
-
fill="none"
|
|
635
|
-
stroke="currentColor"
|
|
636
|
-
stroke-width="2"
|
|
637
|
-
stroke-linecap="round"
|
|
638
|
-
>
|
|
639
|
-
<polyline points="9 18 15 12 9 6" />
|
|
640
|
-
</svg>
|
|
631
|
+
<ChevronRight class="hk-modal-breadcrumb-sep" size={12} />
|
|
641
632
|
);
|
|
642
633
|
|
|
643
634
|
return () =>
|
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
// ONE label column for both surfaces: the overview paints through an
|
|
24
24
|
// empty gutter of this width, so an x% bar sits at the same inline
|
|
25
25
|
// position in the strip and in its lane (the strip locates segments).
|
|
26
|
+
// Wave (2026-11-06 raw-records five): the label column is JS-MEASURED —
|
|
27
|
+
// the component publishes the widest rendered label as a px value on the
|
|
28
|
+
// root (two-phase free/measure/pin). THIS fixed column is the no-JS /
|
|
29
|
+
// pre-measure fallback: a shared width keeps strip-lane x% alignment
|
|
30
|
+
// before the measurement lands (a bare max-content here would resolve
|
|
31
|
+
// 0px in the empty gutter and misalign everything).
|
|
26
32
|
--hk-range-ribbon-label-col: minmax(48px, 84px);
|
|
27
33
|
|
|
28
34
|
display: flex;
|
|
@@ -42,7 +48,9 @@
|
|
|
42
48
|
min-width: 0;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
// The empty first cell that lines the strip up with the lane tracks.
|
|
51
|
+
// The empty first cell that lines the strip up with the lane tracks. Its
|
|
52
|
+
// width comes from the shared var, which the component JS-measures and
|
|
53
|
+
// publishes on the root (two-phase free/measure/pin).
|
|
46
54
|
.hk-range-ribbon-overview-gutter {
|
|
47
55
|
display: block;
|
|
48
56
|
min-width: 0;
|
|
@@ -55,6 +63,29 @@
|
|
|
55
63
|
min-width: 0;
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
.hk-range-ribbon[data-minimal] {
|
|
67
|
+
gap: 0;
|
|
68
|
+
|
|
69
|
+
/* Bare-bar layout: single column, gutter probe out of the way — the
|
|
70
|
+
strip spans the full width (its in-flow content is absolute, so
|
|
71
|
+
without this the max-content column would collapse to zero). */
|
|
72
|
+
.hk-range-ribbon-overview {
|
|
73
|
+
grid-template-columns: minmax(0, 1fr);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.hk-range-ribbon-overview-gutter {
|
|
77
|
+
display: none;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.hk-range-ribbon[data-minimal] .hk-range-ribbon-strip {
|
|
82
|
+
// Minimal mode (raw-records five): the track reads through muted-based
|
|
83
|
+
// tokens — the surface-based track read whitish inside dark phone
|
|
84
|
+
// modals. The inset hairline keeps the frame visible but quiet.
|
|
85
|
+
background: rgb(var(--color-muted) / 25%);
|
|
86
|
+
box-shadow: inset 0 0 0 1px rgb(var(--color-border) / 40%);
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
.hk-range-ribbon-strip {
|
|
59
90
|
position: relative;
|
|
60
91
|
display: block;
|
|
@@ -1466,7 +1466,7 @@ describe("HkRangeRibbon stylesheet contract", () => {
|
|
|
1466
1466
|
it("shares one label column between the overview and the lanes", () => {
|
|
1467
1467
|
expect(
|
|
1468
1468
|
declaration(ruleBody(css, ".hk-range-ribbon"), "--hk-range-ribbon-label-col"),
|
|
1469
|
-
"the root declares the shared
|
|
1469
|
+
"the root declares the shared fallback column (JS publishes the measured px on top)",
|
|
1470
1470
|
).toBe("minmax(48px, 84px)");
|
|
1471
1471
|
const overviewColumns = declaration(
|
|
1472
1472
|
ruleBody(css, ".hk-range-ribbon-overview"),
|
|
@@ -1494,4 +1494,57 @@ describe("HkRangeRibbon stylesheet contract", () => {
|
|
|
1494
1494
|
expect(declaration(endAnchor, "right")).toBe("0");
|
|
1495
1495
|
expect(declaration(endAnchor, "transform")).toBe("none");
|
|
1496
1496
|
});
|
|
1497
|
+
|
|
1498
|
+
// Wave (2026-11-06 raw-records five): MINIMAL MODE - the bare dual-handle
|
|
1499
|
+
// bar. Contract: data-minimal on the root, strip present + windowed at
|
|
1500
|
+
// the full domain, ZERO lane chrome (no lanes block, no lane rows, no
|
|
1501
|
+
// overview minimap, no gutter probe), and the standard mode keeps its
|
|
1502
|
+
// lane chrome + alignment probe.
|
|
1503
|
+
it("minimal mode renders the bare dual-handle bar (no lane chrome)", () => {
|
|
1504
|
+
const root = mountRibbon({ minimal: true });
|
|
1505
|
+
expect(root.getAttribute("data-minimal")).toBe("true");
|
|
1506
|
+
const stripEl = root.querySelector(".hk-range-ribbon-strip")!;
|
|
1507
|
+
expect(stripEl).not.toBeNull();
|
|
1508
|
+
// The strip carries the slider a11y (role/tabindex/label/valuenow).
|
|
1509
|
+
expect(stripEl.getAttribute("aria-label")).toBe("Observation range");
|
|
1510
|
+
// Uncontrolled default window = the FULL domain: valuenow sits at 0
|
|
1511
|
+
// (domain start) and the valuetext reads the whole range.
|
|
1512
|
+
expect(stripEl.getAttribute("aria-valuenow")).toBe("0");
|
|
1513
|
+
expect(stripEl.getAttribute("aria-valuetext")).toContain("0%");
|
|
1514
|
+
// The lanes BLOCK is skipped entirely (no lane rows at all).
|
|
1515
|
+
expect(root.querySelector(".hk-range-ribbon-lanes")).toBeNull();
|
|
1516
|
+
expect(root.querySelector(".hk-range-ribbon-lane")).toBeNull();
|
|
1517
|
+
// The overview minimap container shells remain but render EMPTY, and
|
|
1518
|
+
// the gutter probe is display:none (visibility is the semantic).
|
|
1519
|
+
const minimap = root.querySelector(".hk-range-ribbon-overview-lanes");
|
|
1520
|
+
expect(minimap!.children.length).toBe(0);
|
|
1521
|
+
// (The gutter probe's display:none in minimal mode is enforced by the
|
|
1522
|
+
// static scss assertion at the end of this file — happy-dom cannot
|
|
1523
|
+
// resolve the nested attribute selector.)
|
|
1524
|
+
expect(root.querySelector(".hk-range-ribbon-overview-segment")).toBeNull();
|
|
1525
|
+
// Uncontrolled default window = the FULL domain (edge to edge).
|
|
1526
|
+
// Standard consumers keep the lane chrome + the alignment probe.
|
|
1527
|
+
const std = mountRibbon({});
|
|
1528
|
+
expect(std.querySelector(".hk-range-ribbon-lane")).not.toBeNull();
|
|
1529
|
+
expect(std.querySelector(".hk-range-ribbon-overview-gutter")).not.toBeNull();
|
|
1530
|
+
// Default LANES vocabulary (cpu/boot/parse/io/write/flush).
|
|
1531
|
+
expect(std.textContent).toContain("CPU");
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
// Static scss contract: in minimal mode the gutter probe is display:none
|
|
1535
|
+
// and the overview collapses to a single column (the strip spans the
|
|
1536
|
+
// full width).
|
|
1537
|
+
it("minimal mode overrides the overview grid in the sheet", () => {
|
|
1538
|
+
const scss = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "HkRangeRibbon.scss"), "utf-8");
|
|
1539
|
+
// The nested scss compiles to a single-column overview + a hidden
|
|
1540
|
+
// gutter probe in minimal mode; assert the SOURCE structure (the flat
|
|
1541
|
+
// compiled selector does not appear verbatim in the nested sheet).
|
|
1542
|
+
expect(scss).toContain(".hk-range-ribbon[data-minimal] {");
|
|
1543
|
+
expect(scss).toMatch(
|
|
1544
|
+
/[\s\S]*?\.hk-range-ribbon\[data-minimal\] \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\)/,
|
|
1545
|
+
);
|
|
1546
|
+
expect(scss).toMatch(
|
|
1547
|
+
/\.hk-range-ribbon\[data-minimal\] \{[\s\S]*?\.hk-range-ribbon-overview-gutter \{[\s\S]*?display: none/,
|
|
1548
|
+
);
|
|
1549
|
+
});
|
|
1497
1550
|
});
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { RotateCcw } from "lucide-vue-next";
|
|
2
|
-
import {
|
|
3
|
-
computed,
|
|
4
|
-
defineComponent,
|
|
5
|
-
onBeforeUnmount,
|
|
6
|
-
onMounted,
|
|
7
|
-
ref,
|
|
8
|
-
useId,
|
|
9
|
-
watch,
|
|
10
|
-
type PropType,
|
|
11
|
-
} from "vue";
|
|
2
|
+
import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, ref, type PropType, useId, watch } from "vue";
|
|
12
3
|
|
|
13
4
|
import { useI18n } from "../i18n/context";
|
|
14
5
|
import HIconButton from "./HkIconButton";
|
|
@@ -269,6 +260,14 @@ export default defineComponent({
|
|
|
269
260
|
windowEnabled: { type: Boolean, default: true },
|
|
270
261
|
/** Narrowest window the ribbon will produce, in domain fractions. */
|
|
271
262
|
minWindow: { type: Number, default: MIN_WINDOW_FALLBACK },
|
|
263
|
+
/** Wave (2026-11-06 raw-records five): MINIMAL MODE — a bare dual-handle
|
|
264
|
+
* range bar. No lane rows, no overview minimap, no label gutter; the
|
|
265
|
+
* strip spans the full width and its track uses muted-based tokens
|
|
266
|
+
* (the surface-based track read whitish inside dark phone modals).
|
|
267
|
+
* The uncontrolled default window is already the FULL domain, so a
|
|
268
|
+
* minimal bar starts pinned edge to edge. `segments` / `selectedId`
|
|
269
|
+
* are IGNORED in this mode (no segment DOM to select from). */
|
|
270
|
+
minimal: { type: Boolean, default: false },
|
|
272
271
|
},
|
|
273
272
|
emits: {
|
|
274
273
|
select: (_id: string) => true,
|
|
@@ -277,6 +276,7 @@ export default defineComponent({
|
|
|
277
276
|
setup(props, { emit }) {
|
|
278
277
|
const { t } = useI18n();
|
|
279
278
|
const stripRef = ref<HTMLElement | null>(null);
|
|
279
|
+
|
|
280
280
|
const internalWindow = ref<WindowRange | null>(null);
|
|
281
281
|
/** Id of the segment whose tip card is floating (hover or focus). */
|
|
282
282
|
const tipSegmentId = ref<string | null>(null);
|
|
@@ -548,6 +548,53 @@ export default defineComponent({
|
|
|
548
548
|
if (tipSegmentId.value === id) tipSegmentId.value = null;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
+
// Wave (raw-records five): the label column is JS-MEASURED. The fixed
|
|
552
|
+
// minmax(48px,84px) floor left a dead band next to short labels, and a
|
|
553
|
+
// CSS max-content cannot align two independent grids (each lane sizes
|
|
554
|
+
// to its own label; the overview gutter has no text to size from — a
|
|
555
|
+
// hidden-probe attempt measured misaligned in real Chrome). Measuring
|
|
556
|
+
// the widest rendered label and publishing it as the shared var gives
|
|
557
|
+
// BOTH surfaces the same resolved column: adaptive and aligned.
|
|
558
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
559
|
+
const pinnedCol = ref<string | null>(null);
|
|
560
|
+
/** TWO-PHASE measure: FREE the column first. A pinned px width clips
|
|
561
|
+
* long labels, and a clipped label's offsetWidth equals the clip - so
|
|
562
|
+
* a naive offsetWidth max can never GROW after the first pin (and a
|
|
563
|
+
* smaller max leaves the dead band). Freeing lets labels reflow to
|
|
564
|
+
* natural width; the next frame reads the true text width
|
|
565
|
+
* (scrollWidth == offsetWidth when unclipped) and re-pins only when
|
|
566
|
+
* the value changed (converges - no ResizeObserver loop). */
|
|
567
|
+
const syncLabelCol = () => {
|
|
568
|
+
const root = rootRef.value;
|
|
569
|
+
if (!root) return;
|
|
570
|
+
root.style.setProperty("--hk-range-ribbon-label-col", "max-content");
|
|
571
|
+
requestAnimationFrame(() => {
|
|
572
|
+
const r = rootRef.value;
|
|
573
|
+
if (!r) return;
|
|
574
|
+
const labels = r.querySelectorAll<HTMLElement>(".hk-range-ribbon-lane-label");
|
|
575
|
+
let max = 0;
|
|
576
|
+
for (const el of labels) {
|
|
577
|
+
max = Math.max(max, el.offsetWidth, el.scrollWidth);
|
|
578
|
+
}
|
|
579
|
+
if (max <= 0) return;
|
|
580
|
+
const next = `${max}px`;
|
|
581
|
+
// Guard on the CURRENT INLINE value, not the last measurement:
|
|
582
|
+
// after the FREE phase the inline value is "max-content", and an
|
|
583
|
+
// unchanged measurement must STILL re-pin (the re-pin restores the
|
|
584
|
+
// px column the free took away - skipping it because "40px == 40px"
|
|
585
|
+
// left the bar resting misaligned at freed max-content, measured in
|
|
586
|
+
// real Chrome: field 20 vs tracks 41/60).
|
|
587
|
+
if (r.style.getPropertyValue("--hk-range-ribbon-label-col") !== next) {
|
|
588
|
+
pinnedCol.value = next;
|
|
589
|
+
r.style.setProperty("--hk-range-ribbon-label-col", next);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
};
|
|
593
|
+
const labelObserver =
|
|
594
|
+
typeof ResizeObserver !== "undefined"
|
|
595
|
+
? new ResizeObserver(() => syncLabelCol())
|
|
596
|
+
: null;
|
|
597
|
+
|
|
551
598
|
onMounted(() => {
|
|
552
599
|
// Window-level listeners: a pointer captured by a drag target still
|
|
553
600
|
// emits moves/ups here, and a drag that leaves the strip keeps
|
|
@@ -556,9 +603,22 @@ export default defineComponent({
|
|
|
556
603
|
window.addEventListener("pointermove", onPointerMove);
|
|
557
604
|
window.addEventListener("pointerup", endGesture);
|
|
558
605
|
window.addEventListener("pointercancel", endGesture);
|
|
606
|
+
// Publish the measured label column (see syncLabelCol above): after
|
|
607
|
+
// the first paint, on any lane-label resize (locale swap, viewport
|
|
608
|
+
// font change), and when the lanes vocabulary itself changes.
|
|
609
|
+
syncLabelCol();
|
|
610
|
+
labelObserver?.observe(rootRef.value!);
|
|
611
|
+
watch(
|
|
612
|
+
lanes,
|
|
613
|
+
() => {
|
|
614
|
+
nextTick(syncLabelCol);
|
|
615
|
+
},
|
|
616
|
+
{ deep: true },
|
|
617
|
+
);
|
|
559
618
|
});
|
|
560
619
|
|
|
561
620
|
onBeforeUnmount(() => {
|
|
621
|
+
labelObserver?.disconnect();
|
|
562
622
|
window.removeEventListener("pointermove", onPointerMove);
|
|
563
623
|
window.removeEventListener("pointerup", endGesture);
|
|
564
624
|
window.removeEventListener("pointercancel", endGesture);
|
|
@@ -657,7 +717,7 @@ export default defineComponent({
|
|
|
657
717
|
{/* The overview paints the FULL domain only — it never follows
|
|
658
718
|
the zoom, so the window's position stays locatable. */}
|
|
659
719
|
<div class="hk-range-ribbon-overview-lanes" aria-hidden="true">
|
|
660
|
-
{lanes.value.map((lane) => (
|
|
720
|
+
{!props.minimal && lanes.value.map((lane) => (
|
|
661
721
|
<div class="hk-range-ribbon-overview-row" key={lane.key} data-lane={lane.key}>
|
|
662
722
|
{(byLane.get(lane.key) ?? []).map((placed) => {
|
|
663
723
|
const box = fullDomainBox(placed);
|
|
@@ -701,7 +761,9 @@ export default defineComponent({
|
|
|
701
761
|
|
|
702
762
|
return (
|
|
703
763
|
<div
|
|
764
|
+
ref={rootRef}
|
|
704
765
|
class="hk-range-ribbon"
|
|
766
|
+
data-minimal={props.minimal || undefined}
|
|
705
767
|
data-windowed={win ? "true" : undefined}
|
|
706
768
|
data-window-enabled={props.windowEnabled ? "true" : "false"}
|
|
707
769
|
onKeydown={onRootKeyDown}
|
|
@@ -710,6 +772,11 @@ export default defineComponent({
|
|
|
710
772
|
(empty gutter first), so x% means the same inline position on
|
|
711
773
|
both surfaces — the strip can be used to locate a bar. */}
|
|
712
774
|
<div class="hk-range-ribbon-overview">
|
|
775
|
+
{/* The gutter's width IS the shared label column: the
|
|
776
|
+
component JS-measures the widest rendered label and
|
|
777
|
+
publishes it on the root (two-phase free/measure/pin), so
|
|
778
|
+
this empty cell keeps the strip's x% aligned with the
|
|
779
|
+
lanes' x% at any label width. */}
|
|
713
780
|
<span class="hk-range-ribbon-overview-gutter" aria-hidden="true" />
|
|
714
781
|
<div class="hk-range-ribbon-overview-field">
|
|
715
782
|
{strip}
|
|
@@ -780,6 +847,7 @@ export default defineComponent({
|
|
|
780
847
|
</span>
|
|
781
848
|
)}
|
|
782
849
|
</div>
|
|
850
|
+
{!props.minimal && (
|
|
783
851
|
<div class="hk-range-ribbon-lanes">
|
|
784
852
|
{lanes.value.map((lane) => (
|
|
785
853
|
<div class="hk-range-ribbon-lane" key={lane.key} data-lane={lane.key}>
|
|
@@ -795,6 +863,7 @@ export default defineComponent({
|
|
|
795
863
|
</div>
|
|
796
864
|
))}
|
|
797
865
|
</div>
|
|
866
|
+
)}
|
|
798
867
|
</div>
|
|
799
868
|
);
|
|
800
869
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { User } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { credentialAutocomplete } from "../runtime/credentialAutofill";
|
|
2
4
|
import { defineComponent, ref } from "vue";
|
|
3
5
|
import { useI18n } from "../i18n/context";
|
|
@@ -131,20 +133,7 @@ export const HkSignInCard = defineComponent({
|
|
|
131
133
|
slots.usernameIcon ? (
|
|
132
134
|
slots.usernameIcon()
|
|
133
135
|
) : (
|
|
134
|
-
<
|
|
135
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
136
|
-
viewBox="0 0 24 24"
|
|
137
|
-
fill="none"
|
|
138
|
-
stroke="currentColor"
|
|
139
|
-
stroke-width="2"
|
|
140
|
-
stroke-linecap="round"
|
|
141
|
-
stroke-linejoin="round"
|
|
142
|
-
class="s-auth-card-field-icon"
|
|
143
|
-
aria-hidden="true"
|
|
144
|
-
>
|
|
145
|
-
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
|
|
146
|
-
<circle cx="12" cy="7" r="4"></circle>
|
|
147
|
-
</svg>
|
|
136
|
+
<User class="s-auth-card-field-icon" aria-hidden="true" />
|
|
148
137
|
),
|
|
149
138
|
}}
|
|
150
139
|
</HkInput>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Check } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { defineComponent, h, type Component, type PropType } from "vue";
|
|
2
4
|
|
|
3
5
|
import "./HkStepGuide.scss";
|
|
@@ -77,20 +79,7 @@ export default defineComponent({
|
|
|
77
79
|
>
|
|
78
80
|
<span class="hk-step-guide-ordinal">
|
|
79
81
|
{isDone ? (
|
|
80
|
-
<
|
|
81
|
-
class="hk-step-guide-check"
|
|
82
|
-
viewBox="0 0 24 24"
|
|
83
|
-
aria-hidden="true"
|
|
84
|
-
>
|
|
85
|
-
<path
|
|
86
|
-
d="M20 6L9 17l-5-5"
|
|
87
|
-
fill="none"
|
|
88
|
-
stroke="currentColor"
|
|
89
|
-
stroke-width="3"
|
|
90
|
-
stroke-linecap="round"
|
|
91
|
-
stroke-linejoin="round"
|
|
92
|
-
/>
|
|
93
|
-
</svg>
|
|
82
|
+
<Check class="hk-step-guide-check" stroke-width={3} aria-hidden="true" />
|
|
94
83
|
) : (
|
|
95
84
|
idx + 1
|
|
96
85
|
)}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Check, ChevronUp, GripVertical } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, watch, type PropType } from "vue";
|
|
2
4
|
|
|
3
5
|
import { useI18n } from "../i18n/context";
|
|
@@ -770,17 +772,7 @@ export default defineComponent({
|
|
|
770
772
|
]}
|
|
771
773
|
>
|
|
772
774
|
{allChecked.value && (
|
|
773
|
-
<
|
|
774
|
-
class="hk-table-checkbox-icon"
|
|
775
|
-
viewBox="0 0 24 24"
|
|
776
|
-
fill="none"
|
|
777
|
-
stroke="currentColor"
|
|
778
|
-
stroke-width="3"
|
|
779
|
-
stroke-linecap="round"
|
|
780
|
-
stroke-linejoin="round"
|
|
781
|
-
>
|
|
782
|
-
<polyline points="20 6 9 17 4 12" />
|
|
783
|
-
</svg>
|
|
775
|
+
<Check class="hk-table-checkbox-icon" stroke-width={3} />
|
|
784
776
|
)}
|
|
785
777
|
</span>
|
|
786
778
|
</label>
|
|
@@ -802,20 +794,10 @@ export default defineComponent({
|
|
|
802
794
|
>
|
|
803
795
|
<span class="hk-table-header-label">{col.title}</span>
|
|
804
796
|
{canSort && sortKey.value === col.key && (
|
|
805
|
-
<
|
|
806
|
-
class={[
|
|
797
|
+
<ChevronUp class={[
|
|
807
798
|
"hk-table-sort-icon",
|
|
808
799
|
sortDirection.value === "desc" ? "hk-table-sort-icon-desc" : "",
|
|
809
|
-
]}
|
|
810
|
-
viewBox="0 0 24 24"
|
|
811
|
-
fill="none"
|
|
812
|
-
stroke="currentColor"
|
|
813
|
-
stroke-width="2"
|
|
814
|
-
stroke-linecap="round"
|
|
815
|
-
stroke-linejoin="round"
|
|
816
|
-
>
|
|
817
|
-
<polyline points="18 15 12 9 6 15" />
|
|
818
|
-
</svg>
|
|
800
|
+
]} />
|
|
819
801
|
)}
|
|
820
802
|
</th>
|
|
821
803
|
);
|
|
@@ -874,14 +856,7 @@ export default defineComponent({
|
|
|
874
856
|
onPointerdown={(e: PointerEvent) => onHandlePointerdown(e, index)}
|
|
875
857
|
onKeydown={(e: KeyboardEvent) => onHandleKeydown(e, index)}
|
|
876
858
|
>
|
|
877
|
-
<
|
|
878
|
-
<circle cx="3.5" cy="3" r="1.2" />
|
|
879
|
-
<circle cx="8.5" cy="3" r="1.2" />
|
|
880
|
-
<circle cx="3.5" cy="7" r="1.2" />
|
|
881
|
-
<circle cx="8.5" cy="7" r="1.2" />
|
|
882
|
-
<circle cx="3.5" cy="11" r="1.2" />
|
|
883
|
-
<circle cx="8.5" cy="11" r="1.2" />
|
|
884
|
-
</svg>
|
|
859
|
+
<GripVertical size={14} aria-hidden="true" />
|
|
885
860
|
</span>
|
|
886
861
|
</td>
|
|
887
862
|
)}
|
|
@@ -902,17 +877,7 @@ export default defineComponent({
|
|
|
902
877
|
]}
|
|
903
878
|
>
|
|
904
879
|
{selectedRowKeys.value.has(rowKey) && (
|
|
905
|
-
<
|
|
906
|
-
class="hk-table-checkbox-icon"
|
|
907
|
-
viewBox="0 0 24 24"
|
|
908
|
-
fill="none"
|
|
909
|
-
stroke="currentColor"
|
|
910
|
-
stroke-width="3"
|
|
911
|
-
stroke-linecap="round"
|
|
912
|
-
stroke-linejoin="round"
|
|
913
|
-
>
|
|
914
|
-
<polyline points="20 6 9 17 4 12" />
|
|
915
|
-
</svg>
|
|
880
|
+
<Check class="hk-table-checkbox-icon" stroke-width={3} />
|
|
916
881
|
)}
|
|
917
882
|
</span>
|
|
918
883
|
</label>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChevronRight } from "lucide-vue-next";
|
|
2
|
+
|
|
1
3
|
import { defineComponent, ref, watch, h, type Component, type PropType, type VNodeChild } from "vue";
|
|
2
4
|
|
|
3
5
|
import "./HkTree.scss";
|
|
@@ -158,9 +160,7 @@ const TreeNodeRow = ({
|
|
|
158
160
|
onClick={(e: MouseEvent) => { e.stopPropagation(); if (hasChildren) onToggle(node.id); }}
|
|
159
161
|
>
|
|
160
162
|
{hasChildren ? (
|
|
161
|
-
<
|
|
162
|
-
<path d="M6 4l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
163
|
-
</svg>
|
|
163
|
+
<ChevronRight class="hk-tree-chevron" data-open={isExpanded || undefined} size={12} stroke-width={1.5} />
|
|
164
164
|
) : (
|
|
165
165
|
<span class="hk-tree-leaf" />
|
|
166
166
|
)}
|