@jsenv/navi 0.28.10 → 0.28.11

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.
@@ -5356,7 +5356,7 @@ const stateSignal = (defaultValue, options = {}) => {
5356
5356
  const existingEntry = globalSignalRegistry.get(signalIdString);
5357
5357
  {
5358
5358
  throw new Error(
5359
- `Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${existingEntry.options.getDefaultValue()})`,
5359
+ `Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${existingEntry.options.getDefaultValue()}). If this is the same stateSignal() call site running twice, the module was evaluated twice — check the network tab for the same file requested both bare and with "?hot=".`,
5360
5360
  );
5361
5361
  }
5362
5362
  }
@@ -33946,10 +33946,13 @@ installImportMetaCssBuild(import.meta);const css$E = /* css */`
33946
33946
  black
33947
33947
  );
33948
33948
  /* Readonly */
33949
+ /* Fading toward the surface, not toward white: "washed out" means
33950
+ closer to the paper behind the button, whatever color that paper is
33951
+ (white here IS the surface only in the light theme). */
33949
33952
  --button-border-color-readonly: color-mix(
33950
33953
  in srgb,
33951
33954
  var(--button-border-color) 30%,
33952
- white
33955
+ var(--navi-surface-color)
33953
33956
  );
33954
33957
  --button-background-color-readonly: var(--button-background-color);
33955
33958
  --button-color-readonly: color-mix(
@@ -43668,6 +43671,10 @@ const lengthValue = value => typeof value === "number" ? `${value}px` : value;
43668
43671
  *
43669
43672
  * getTrackedItemByIndex(index): synchronous O(1) lookup of a visible item by
43670
43673
  * its visible rank. Returns undefined when index is out of range.
43674
+ *
43675
+ * peekItems(): the items as they stand right now, without waiting for the
43676
+ * deferred notification — what a sibling rendering after the items must read
43677
+ * to paint them in the same commit.
43671
43678
  */
43672
43679
 
43673
43680
  const useItemTracker = ({ onChange } = {}) => {
@@ -43770,7 +43777,8 @@ const createItemTracker = (onChange) => {
43770
43777
  visibleItemsSignal.value = visibleItems;
43771
43778
  someChange = true;
43772
43779
  }
43773
- const noMatchCountModified = noMatchCountSignal.peek() !== newNoMatchCount;
43780
+ const noMatchCountModified =
43781
+ noMatchCountSignal.peek() !== newNoMatchCount;
43774
43782
  if (noMatchCountModified) {
43775
43783
  noMatchCountSignal.value = newNoMatchCount;
43776
43784
  someChange = true;
@@ -43964,9 +43972,32 @@ const createItemTracker = (onChange) => {
43964
43972
  return registrations.get(key);
43965
43973
  };
43966
43974
 
43975
+ // The items as they stand right now, notification pending or not — same
43976
+ // content as itemsSignal, minus the wait.
43977
+ //
43978
+ // Items register during their own render, while the signal is only updated
43979
+ // on a deferred microtask (see notify): a sibling rendering after them would
43980
+ // otherwise paint from an empty list and correct itself a frame later. That
43981
+ // frame is visible whenever the painted size feeds a layout decision — a
43982
+ // dialog sizing itself on its content measures the empty version and shifts
43983
+ // once the real one lands. Reading this instead makes the first paint the
43984
+ // right one. Callers must still subscribe to itemsSignal to re-render on
43985
+ // LATER changes; this is the value to display, not the notification.
43986
+ const peekItems = () => {
43987
+ if (!notifyScheduled) {
43988
+ return itemsSignal.peek();
43989
+ }
43990
+ const items = [];
43991
+ for (const key of allOrderedKeys) {
43992
+ items.push(allRegistrations.get(key));
43993
+ }
43994
+ return items;
43995
+ };
43996
+
43967
43997
  return {
43968
43998
  useTrackItem,
43969
43999
  getTrackedItemByIndex,
44000
+ peekItems,
43970
44001
  itemsSignal,
43971
44002
  visibleItemsSignal,
43972
44003
  countSignal,
@@ -50733,12 +50764,16 @@ const css$k = /* css */`
50733
50764
  as rows scroll under the pointer. */
50734
50765
  user-select: none;
50735
50766
  -webkit-tap-highlight-color: transparent;
50736
- /* Rendering virtualization: only the rows within each wheel's own viewport
50737
- are painted; the rest (clipped by the viewport's overflow) are skipped.
50738
- The box is still laid out (fixed main-axis size below), so offsets, snap
50739
- and the wrap math are unaffected this cuts paint/compositing cost so a
50740
- wheel with many values (or a page full of wheels) scrolls smoothly. */
50741
- content-visibility: auto;
50767
+ /* NO content-visibility here, ever. content-visibility: auto was tried
50768
+ (to skip painting clipped rows) and it breaks the first opening of any
50769
+ popup holding a wheel: rows born inside a closed dialog are "skipped"
50770
+ with no last-remembered size, so they measure as empty (padding-only) on
50771
+ the open's own layout the dialog positions itself against that width
50772
+ and visibly slides once the real width lands. The un-skip only happens
50773
+ at the next update-the-rendering step: no forced reflow can read the
50774
+ true size any earlier, making the bug unfixable from the dialog side.
50775
+ There is also nothing left to win: the window renders only
50776
+ visibleCount + 2 recycled rows, all in or against the viewport. */
50742
50777
  }
50743
50778
 
50744
50779
  /* Orientation-specific sizing/layout. The mask worn by the base layer: the
@@ -51706,23 +51741,16 @@ function WheelUI(props) {
51706
51741
  const trackedItems = tracker.itemsSignal.value;
51707
51742
  const trackedItemsRef = useRef(trackedItems);
51708
51743
  trackedItemsRef.current = trackedItems;
51709
- const itemCount = trackedItems.length;
51710
51744
 
51711
51745
  // Virtualization: render only visibleCount + 2 rows (one buffer each side to
51712
51746
  // cover the partial rows a scroll reveals + a re-render's one-frame lag) and
51713
- // recycle them, filling each from trackedItems. `centerRowSignal` holds the row
51714
- // currently at the center (round(pos / itemSize)); it changes only when the
51715
- // wheel crosses a row (not every frame), so the window re-renders on demand, not
51716
- // per frame. WheelWindow derives its top slot (base) from that row and its own
51717
- // windowSize so a windowSize change (items still registering) recomputes base
51718
- // instead of leaving the top rows unrendered. The per-frame transform is
51719
- // imperative (--wheel-offset); renderedBaseRef mirrors the base the DOM shows so
51720
- // the transform stays consistent with it.
51721
- // visibleCount + 2, but never more rows than there are values.
51722
- let windowSize = visibleCount + 2;
51723
- if (windowSize > itemCount) {
51724
- windowSize = itemCount;
51725
- }
51747
+ // recycle them. `centerRowSignal` holds the row currently at the center
51748
+ // (round(pos / itemSize)); it changes only when the wheel crosses a row (not
51749
+ // every frame), so the window re-renders on demand, not per frame. WheelWindow
51750
+ // owns both its size and its top slot (base), derived from that row and from the
51751
+ // items it reads off the tracker itself see its own comment. The per-frame
51752
+ // transform is imperative (--wheel-offset); renderedBaseRef mirrors the base the
51753
+ // DOM shows so the transform stays consistent with it.
51726
51754
  const centerRowSignal = useSignal(0);
51727
51755
  const renderedBaseRef = useRef(0);
51728
51756
  // Row size in px, measured once from a rendered slot (rows are uniform).
@@ -52426,8 +52454,8 @@ function WheelUI(props) {
52426
52454
  className: "navi_wheel_list",
52427
52455
  children: jsx(WheelWindow, {
52428
52456
  centerRowSignal: centerRowSignal,
52429
- windowSize: windowSize,
52430
- trackedItems: trackedItems,
52457
+ visibleCount: visibleCount,
52458
+ tracker: tracker,
52431
52459
  isLoop: isLoop,
52432
52460
  onBaseCommit: commitRenderedBase
52433
52461
  })
@@ -52440,8 +52468,8 @@ function WheelUI(props) {
52440
52468
  className: "navi_wheel_list",
52441
52469
  children: jsx(WheelWindow, {
52442
52470
  centerRowSignal: centerRowSignal,
52443
- windowSize: windowSize,
52444
- trackedItems: trackedItems,
52471
+ visibleCount: visibleCount,
52472
+ tracker: tracker,
52445
52473
  isLoop: isLoop
52446
52474
  })
52447
52475
  })
@@ -52458,21 +52486,38 @@ const WHEEL_PSEUDO_CLASSES = [":focus-within", ":focus-visible", ":read-only", "
52458
52486
 
52459
52487
  // The recycled window: a fixed set of `windowSize` <li> slots (one per visible
52460
52488
  // row + 2 buffer), keyed by slot position so each DOM node is reused and only its
52461
- // content re-renders. `base` (the top slot's value index) is derived here from the
52462
- // center row + windowSize computing it here, not upstream, means a windowSize
52463
- // change (items still registering) recomputes base correctly. Each slot shows
52464
- // trackedItems[base + slot] (wrapped when looping). It reads centerRowSignal, so
52465
- // it re-renders ONLY when the wheel crosses a row, not every frame the per-frame
52466
- // motion is the imperative transform in applyOffset. On each new base it calls
52467
- // onBaseCommit so the transform re-syncs to the freshly rendered rows same-frame.
52489
+ // content re-renders. `base` (the top slot's value index) and windowSize are both
52490
+ // derived here rather than upstream, from the items read straight off the tracker:
52491
+ // this component renders right after the Wheel.Item children, which register
52492
+ // during their own render, so it can draw the real rows in the very first commit.
52493
+ // Taking them from the parent instead would mean drawing an empty wheel first (the
52494
+ // parent renders before its children, and the tracker's signal only lands on a
52495
+ // microtask) a wheel with no width, which is enough to misplace whatever sizes
52496
+ // itself on it, e.g. a dialog centered on its content.
52497
+ // Each slot shows items[base + slot] (wrapped when looping). It reads
52498
+ // centerRowSignal, so it re-renders ONLY when the wheel crosses a row, not every
52499
+ // frame — the per-frame motion is the imperative transform in applyOffset. On each
52500
+ // new base it calls onBaseCommit so the transform re-syncs to the freshly rendered
52501
+ // rows same-frame.
52468
52502
  const WheelWindow = ({
52469
52503
  centerRowSignal,
52470
- windowSize,
52471
- trackedItems,
52504
+ visibleCount,
52505
+ tracker,
52472
52506
  isLoop,
52473
52507
  onBaseCommit
52474
52508
  }) => {
52509
+ // Subscribing is what re-renders this window when items are added or removed
52510
+ // later; what to draw right now is read synchronously below.
52511
+ // eslint-disable-next-line no-unused-expressions
52512
+ tracker.itemsSignal.value;
52513
+ const trackedItems = tracker.peekItems();
52475
52514
  const count = trackedItems.length;
52515
+ // visibleCount + 2 (one buffer row each side), but never more rows than there
52516
+ // are values.
52517
+ let windowSize = visibleCount + 2;
52518
+ if (windowSize > count) {
52519
+ windowSize = count;
52520
+ }
52476
52521
  let base = centerRowSignal.value - Math.floor(windowSize / 2);
52477
52522
  // Non-loop: keep the window inside the value range so every slot maps to a real
52478
52523
  // value; the transform then leaves the blank runway above the first / below the
@@ -57972,9 +58017,9 @@ const css$2 = /* css */`
57972
58017
  .navi_card_layout {
57973
58018
  --layout-margin: 30px;
57974
58019
  --layout-padding: 20px;
57975
- --layout-background: white;
58020
+ --layout-background: var(--navi-surface-color);
57976
58021
  --layout-border-width: 2px;
57977
- --layout-border-color: lightgrey;
58022
+ --layout-border-color: var(--navi-popup-border-color);
57978
58023
  --layout-border-radius: 10px;
57979
58024
  --layout-min-width: 300px;
57980
58025
  --layout-min-height: auto;
@@ -58093,7 +58138,9 @@ installImportMetaCssBuild(import.meta);const css$1 = /* css */`
58093
58138
  @layer navi {
58094
58139
  .navi_viewport_layout {
58095
58140
  --layout-padding: 40px;
58096
- --layout-background: white;
58141
+ /* The page's own paper: the shared surface token, so a dark theme gets a
58142
+ dark page without every screen overriding it. */
58143
+ --layout-background: var(--navi-surface-color);
58097
58144
  }
58098
58145
  }
58099
58146
 
@@ -58300,14 +58347,17 @@ const css = /* css */`
58300
58347
  z-index: 1;
58301
58348
  background-color: var(--navi-popup-background-color);
58302
58349
  }
58350
+ /* The separator token, not the popup border: these lines split two
58351
+ regions of the panel's own surface (the head/foot from what scrolls
58352
+ under them), they are not the panel's edge. */
58303
58353
  .navi_side_panel_head {
58304
58354
  top: 0;
58305
- border-bottom: 1px solid var(--navi-popup-border-color);
58355
+ border-bottom: 1px solid var(--navi-separator-color-default);
58306
58356
  }
58307
58357
  .navi_side_panel_foot {
58308
58358
  bottom: 0;
58309
58359
  padding: 12px 16px;
58310
- border-top: 1px solid var(--navi-popup-border-color);
58360
+ border-top: 1px solid var(--navi-separator-color-default);
58311
58361
  }
58312
58362
  }
58313
58363
  `;