@celestia-island/hikari 0.55.307 → 0.55.309

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.55.307",
3
+ "version": "0.55.309",
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",
@@ -332,7 +332,14 @@ export default defineComponent({
332
332
  // finding). Riding the block keeps the scrollport's content
333
333
  // untransformed; the block's own poke below the frame lands
334
334
  // off-screen on a bottom-docked sheet.
335
- if (bodyRef.value) out.push({ el: bodyRef.value });
335
+ // While the desktop height ride owns the body's paint (its scaleY
336
+ // IS the choreography), the morph must not write an inline transform
337
+ // on the same element: an inline style outranks the ride's class
338
+ // rule, and the close would stop compressing (2026-11-07 user
339
+ // report: the window folded "together with the chrome"). The body
340
+ // ride matters for the ≤767px sheet sweep, where the ride geometry
341
+ // is disabled — so the two never legitimately overlap.
342
+ if (bodyRef.value && rideStateClass() === null) out.push({ el: bodyRef.value });
336
343
  return out;
337
344
  },
338
345
  // Republish the morph's real sweep span and its landing on the body
@@ -1126,6 +1133,7 @@ export default defineComponent({
1126
1133
  <div
1127
1134
  ref={contentRef}
1128
1135
  class={["hk-modal-content", "hk-ride", rideStateClass(), props.contentClass, ...machine.classesFor("hk-modal-content")]}
1136
+ data-hk-ride-ready={ride.hasCache() ? "" : undefined}
1129
1137
  role="dialog"
1130
1138
  aria-modal="true"
1131
1139
  aria-label={resolvedSurfaceName.value}
@@ -1133,6 +1141,8 @@ export default defineComponent({
1133
1141
  style={{
1134
1142
  maxWidth: resolvedWidth.value,
1135
1143
  zIndex: contentZ.value,
1144
+ ...ride.styleVars(),
1145
+
1136
1146
  }}
1137
1147
  onClick={onContentClick}
1138
1148
  tabindex={-1}
@@ -862,6 +862,35 @@ describe("HkRangeRibbon reset", () => {
862
862
  expect(second.defaultPrevented).toBe(true);
863
863
  });
864
864
 
865
+ it("treats a full-domain window as nothing to reset (raw-records rest state)", async () => {
866
+ // A minimal-style host rests at the controlled FULL selection [0, 1]:
867
+ // the grips paint, but there is no narrower selection to come back to.
868
+ // Escape there must reach the host (the dialog closes), not "reset" the
869
+ // bar it already shows — before 2026-11-07 every press swallowed the key.
870
+ const host = mountHost([0, 1]);
871
+ const strip = host.root.querySelector<HTMLElement>(".hk-range-ribbon-strip")!;
872
+ const event = keydown(strip, "Escape");
873
+ await nextTick();
874
+ expect(host.windowChange, "full selection: nothing resets").not.toHaveBeenCalled();
875
+ expect(event.defaultPrevented, "a host dialog must still see Escape").toBe(false);
876
+
877
+ // The reset BUTTON agrees with Escape: nothing to reset at [0, 1].
878
+ expect(host.root.querySelector(".hk-range-ribbon-reset")).toBeNull();
879
+ // The grips stay (they are the selection itself, not a reset affordance).
880
+ expect(host.root.querySelector(".hk-range-ribbon-handle")).not.toBeNull();
881
+
882
+ // A real sub-domain still resets — and the chip is back for it.
883
+ await host.setWindow([0.3, 0.7]);
884
+ expect(host.root.querySelector(".hk-range-ribbon-reset")).not.toBeNull();
885
+ const second = keydown(host.root.querySelector<HTMLElement>(".hk-range-ribbon-strip")!, "Escape");
886
+ await nextTick();
887
+ expect(host.windowChange).toHaveBeenLastCalledWith(null);
888
+ expect(second.defaultPrevented).toBe(true);
889
+ // Back at the full selection the chip is gone again.
890
+ await host.setWindow([0, 1]);
891
+ expect(host.root.querySelector(".hk-range-ribbon-reset")).toBeNull();
892
+ });
893
+
865
894
  it("cancels an in-flight brush with Escape and consumes the key", async () => {
866
895
  const host = mountHost(null);
867
896
  const { strip, x } = stubStrip(host.root);
@@ -1496,10 +1525,13 @@ describe("HkRangeRibbon stylesheet contract", () => {
1496
1525
  });
1497
1526
 
1498
1527
  // 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.
1528
+ // bar. Contract: data-minimal on the root, strip present, ZERO lane chrome
1529
+ // (no lanes block, no lane rows, no overview minimap, no gutter probe),
1530
+ // and the standard mode keeps its lane chrome + alignment probe. The
1531
+ // uncontrolled default window is NULL here — the grips need a controlled
1532
+ // non-null window (the chest raw-records adapter feeds [0, 1] for its
1533
+ // "default = everything selected" reading); the aria readings below come
1534
+ // from the strip's null-window fallbacks, not from a real window.
1503
1535
  it("minimal mode renders the bare dual-handle bar (no lane chrome)", () => {
1504
1536
  const root = mountRibbon({ minimal: true });
1505
1537
  expect(root.getAttribute("data-minimal")).toBe("true");
@@ -1507,8 +1539,9 @@ describe("HkRangeRibbon stylesheet contract", () => {
1507
1539
  expect(stripEl).not.toBeNull();
1508
1540
  // The strip carries the slider a11y (role/tabindex/label/valuenow).
1509
1541
  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.
1542
+ // No window ⇒ the strip's aria falls back to the full range ("0" /
1543
+ // "0% – 100%"): a truthful reading for an unfiltered bar, even though
1544
+ // no grips paint without a controlled window.
1512
1545
  expect(stripEl.getAttribute("aria-valuenow")).toBe("0");
1513
1546
  expect(stripEl.getAttribute("aria-valuetext")).toContain("0%");
1514
1547
  // The lanes BLOCK is skipped entirely (no lane rows at all).
@@ -1522,7 +1555,6 @@ describe("HkRangeRibbon stylesheet contract", () => {
1522
1555
  // static scss assertion at the end of this file — happy-dom cannot
1523
1556
  // resolve the nested attribute selector.)
1524
1557
  expect(root.querySelector(".hk-range-ribbon-overview-segment")).toBeNull();
1525
- // Uncontrolled default window = the FULL domain (edge to edge).
1526
1558
  // Standard consumers keep the lane chrome + the alignment probe.
1527
1559
  const std = mountRibbon({});
1528
1560
  expect(std.querySelector(".hk-range-ribbon-lane")).not.toBeNull();
@@ -264,9 +264,12 @@ export default defineComponent({
264
264
  * range bar. No lane rows, no overview minimap, no label gutter; the
265
265
  * strip spans the full width and its track uses muted-based tokens
266
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). */
267
+ * The grips paint only for a NON-null window, so an uncontrolled
268
+ * minimal bar starts as a bare track; a host that wants the "default =
269
+ * everything selected" reading feeds the controlled window `[0, 1]`
270
+ * (the chest raw-records adapter does exactly that).
271
+ * `segments` / `selectedId` are IGNORED in this mode (no segment DOM to
272
+ * select from). */
270
273
  minimal: { type: Boolean, default: false },
271
274
  },
272
275
  emits: {
@@ -350,9 +353,21 @@ export default defineComponent({
350
353
  return true;
351
354
  }
352
355
 
353
- /** Returns true when a window actually existed (and was cleared), so the
354
- * caller can leave Escape alone when the host needs it. */
356
+ /** A window that covers the whole domain IS the unfiltered state — there
357
+ * is nothing to reset back to (2026-11-07, the raw-records consumer: a
358
+ * minimal bar paints its grips for a non-null window, so its rest state
359
+ * is the FULL selection `[0, 1]`; without this check Escape "reset" it
360
+ * every press, swallowed the key, and the hosting dialog could never
361
+ * close while the bar held focus). */
362
+ function isFullDomain(win: WindowRange | null): boolean {
363
+ return win !== null && win[0] <= WINDOW_EPSILON && win[1] >= 1 - WINDOW_EPSILON;
364
+ }
365
+
366
+ /** Returns true when a window actually existed AND was narrower than the
367
+ * full domain (and was cleared), so the caller can leave Escape alone
368
+ * when the host needs it — a full-domain window has nothing to reset. */
355
369
  function resetWindow(): boolean {
370
+ if (isFullDomain(activeWindow.value)) return false;
356
371
  if (!commitIfChanged(null)) return false;
357
372
  // A reset that interrupts a drag (Escape mid-brush) must drop the
358
373
  // gesture too, or the next pointermove re-brushes from the stale
@@ -818,7 +833,10 @@ export default defineComponent({
818
833
  focus; this sheet only pins the seat (position) and a
819
834
  surface backing via its override vars. The glyph slots
820
835
  as a lucide component (the house-normal integration:
821
- every in-tree HkIconButton slots its glyph). */}
836
+ every in-tree HkIconButton slots its glyph). A
837
+ full-domain window has nothing to reset, so the chip
838
+ only renders for a REAL sub-domain selection. */}
839
+ {win && !isFullDomain(win) && (
822
840
  <HIconButton
823
841
  class="hk-range-ribbon-reset"
824
842
  size={24}
@@ -829,6 +847,7 @@ export default defineComponent({
829
847
  >
830
848
  {{ icon: () => <RotateCcw aria-hidden="true" /> }}
831
849
  </HIconButton>
850
+ )}
832
851
  </>
833
852
  )}
834
853
  </div>
@@ -76,6 +76,11 @@
76
76
  height: var(--hk-ride-content, auto);
77
77
  }
78
78
 
79
+ .hk-ride[data-hk-ride-ready].hk-ride--from > .hk-ride-chrome-start,
80
+ .hk-ride[data-hk-ride-ready].hk-ride--to > .hk-ride-chrome-start {
81
+ flex-shrink: 0;
82
+ }
83
+
79
84
  .hk-ride[data-hk-ride-ready].hk-ride--from > .hk-ride-chrome-end,
80
85
  .hk-ride[data-hk-ride-ready].hk-ride--to > .hk-ride-chrome-end {
81
86
  position: absolute;
@@ -70,6 +70,14 @@ export interface HeightRide {
70
70
  * ride needs); calling it mid-ride re-measures the live, compressed
71
71
  * box and is therefore a no-op unless `force` is passed. */
72
72
  refresh(force?: boolean): boolean;
73
+ /** Inline style vars for the CURRENT cache, or null when nothing has
74
+ * been measured yet. Consumers bind this in their render so the very
75
+ * first painted frame of a remount already carries the ride (a post-
76
+ * mount publish is one paint too late: the enter-from frame would show
77
+ * a full-height frame with unscaled content — read as "clipping"). */
78
+ styleVars(): Record<string, string> | null;
79
+ /** True when a measured split exists (drives the ready attribute). */
80
+ hasCache(): boolean;
73
81
  /** Re-apply the LAST measured split without touching the DOM boxes.
74
82
  * A surface that unmounts its frame on close loses the inline variables
75
83
  * with it; republishing on the next mount is what makes the very first
@@ -138,15 +146,12 @@ export function useHeightRide(
138
146
  if (child === c || !(child instanceof HTMLElement)) continue;
139
147
  chromeNatural += child.offsetHeight;
140
148
  }
141
- // At rest the frame is at (or pinned to) its natural height, so the
142
- // content region's natural height is the honest difference there; mid
143
- // flight only the content's own box is trustworthy (the frame is short
144
- // by design). Both branches are frame-height independent when it
145
- // matters — a compressed frame never feeds the next ride.
146
- const contentNatural = isInFlight(f)
147
- ? c.offsetHeight
148
- : Math.max(0, f.offsetHeight - chromeNatural);
149
- const measured = contentNatural > 0 ? contentNatural : c.offsetHeight;
149
+ // The content region's own layout box is the ONLY honest source. With
150
+ // no ride class the body is flex-natural; while a ride class is present
151
+ // the box is locked to the published var (stable, no new information —
152
+ // and the frame's height is the ride's own output, so deriving the
153
+ // content from it would feed every early error forward forever).
154
+ const measured = c.offsetHeight;
150
155
  if (measured <= 0) return false;
151
156
  contentH = measured;
152
157
  chrome = chromeNatural;
@@ -172,8 +177,19 @@ export function useHeightRide(
172
177
  return true;
173
178
  };
174
179
 
180
+ const styleVars = (): Record<string, string> | null =>
181
+ contentH <= 0
182
+ ? null
183
+ : {
184
+ "--hk-ride-chrome": `${chrome}px`,
185
+ "--hk-ride-content": `${contentH}px`,
186
+ "--hk-ride-factor": String(factorOf()),
187
+ };
188
+
175
189
  return {
176
190
  refresh,
191
+ styleVars,
192
+ hasCache: () => contentH > 0,
177
193
  publish,
178
194
  chromeHeight: () => chrome,
179
195
  contentHeight: () => contentH,