@jsenv/navi 0.29.494 → 0.29.496

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.
@@ -45384,17 +45384,22 @@ document.body.addEventListener(
45384
45384
  * on screen. A registry keyed by id then holds the newcomer, and loses the
45385
45385
  * first control when the newcomer leaves. navi renders on the client only, so
45386
45386
  * a counter is all the uniqueness needs.
45387
+ *
45388
+ * Held in a ref, not in useState: @preact/signals' shouldComponentUpdate
45389
+ * skips the render of a component holding hook state whose props did not
45390
+ * change, so an id kept as state would change when its caller renders.
45387
45391
  */
45388
45392
 
45389
45393
 
45390
45394
  let generatedIdCount = 0;
45391
45395
 
45392
45396
  const useGeneratedId = () => {
45393
- const [id] = useState(() => {
45397
+ const idRef = useRef(null);
45398
+ if (idRef.current === null) {
45394
45399
  generatedIdCount++;
45395
- return `navi_${generatedIdCount}`;
45396
- });
45397
- return id;
45400
+ idRef.current = `navi_${generatedIdCount}`;
45401
+ }
45402
+ return idRef.current;
45398
45403
  };
45399
45404
 
45400
45405
  /**
@@ -78988,12 +78993,14 @@ const PickerCustomResolver = props => {
78988
78993
  return renderResolver(PickerNative, props);
78989
78994
  }
78990
78995
  if (props.mode === "callout") {
78991
- // A tooltip is an icon one presses, unless told otherwise. Own-property
78992
- // rather than undefined: an explicit variant={undefined} asks for the
78993
- // field-like drawing back. "circle" is the icon variant with the status
78994
- // icon drawn round — a word about the trigger, not a drawing of its own.
78995
- if (!Object.hasOwn(props, "variant")) {
78996
+ // A tooltip is an icon one presses, unless told otherwise; "picker" asks
78997
+ // for the field-like drawing back. "circle" is the icon variant with the
78998
+ // status icon drawn round — a word about the trigger, not a drawing of its
78999
+ // own.
79000
+ if (props.variant === undefined) {
78996
79001
  props.variant = "icon";
79002
+ } else if (props.variant === "picker") {
79003
+ props.variant = undefined;
78997
79004
  }
78998
79005
  const circle = props.variant === "circle";
78999
79006
  if (circle) {
@@ -80484,11 +80491,10 @@ const PickerConfirmResolver = props => {
80484
80491
  openWhileReadOnly = false
80485
80492
  } = props;
80486
80493
  // A button, unless told otherwise: nothing is picked here, the popup is a
80487
- // question, and a question is asked by pressing something. "picker" — or an
80488
- // explicit `variant={undefined}` — is how a caller asks for the field-like
80489
- // drawing back; a destructuring default could not tell that from nothing
80490
- // being said.
80491
- const variant = Object.hasOwn(props, "variant") ? props.variant === "picker" ? undefined : props.variant : "button";
80494
+ // question, and a question is asked by pressing something. "picker" is how a
80495
+ // caller asks for the field-like drawing back — a word, not an undefined, so
80496
+ // a wrapper forwarding `variant={variant}` keeps the button.
80497
+ const variant = props.variant === undefined ? "button" : props.variant === "picker" ? undefined : props.variant;
80492
80498
 
80493
80499
  // From the picker's own input, as its press would have been: the input is
80494
80500
  // what carries `command`/`commandFor` in the DOM, and what a form sending
@@ -92879,9 +92885,8 @@ const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
92879
92885
  * not sit in — an object on a map saying
92880
92886
  * `triggerNaviCommand(pickerEl, "--navi-open", event, { anchor, value })` —
92881
92887
  * therefore wants an element of its own to stretch into rather than the
92882
- * nearest positioned ancestor it happens to find. `"picker"` (or an explicit
92883
- * `variant={undefined}`) asks a confirm or callout picker for the field-like
92884
- * drawing every other picker has.
92888
+ * nearest positioned ancestor it happens to find. `"picker"` asks a confirm
92889
+ * or callout picker for the field-like drawing every other picker has.
92885
92890
  * @param {boolean} [cta] A call to action: filled with the accent, its border
92886
92891
  * matching, as `cta` draws a Button — for a confirm picker that is a row's
92887
92892
  * main action.
@@ -96709,12 +96714,18 @@ const buildMatchInfo = (searchText, items, matchFn) => {
96709
96714
  * needed. `data.role === "presentation"` keeps it out entirely.
96710
96715
  * Returns the visible rank (position among visible items), or -1 when hidden
96711
96716
  * or filtered.
96717
+ * An item rendered without an index keeps the position it has, and a new one
96718
+ * goes last: for a host that cannot know the order at render and gives it
96719
+ * afterwards with setOrder.
96712
96720
  * Signals and onChange are deferred to a microtask so multiple items updating
96713
96721
  * in one commit cause only one notification.
96714
96722
  *
96715
96723
  * getTrackedItemByIndex(index): synchronous O(1) lookup of a visible item by
96716
96724
  * its visible rank. Returns undefined when index is out of range.
96717
96725
  *
96726
+ * setOrder(ids): gives each tracked item its position from its rank in
96727
+ * `ids`.
96728
+ *
96718
96729
  * peekItems(): the items as they stand right now, without waiting for the
96719
96730
  * deferred notification — what a sibling rendering after the items must read
96720
96731
  * to paint them in the same commit.
@@ -96995,8 +97006,14 @@ const createItemTracker = (onChange) => {
96995
97006
  // that determines this item's position among siblings.
96996
97007
  // Returns the item's visible rank among non-hidden items, or -1 when hidden.
96997
97008
  const useTrackItem = (data) => {
96998
- const { id, index } = data;
97009
+ const { id } = data;
96999
97010
  const key = keyForId(id);
97011
+ let { index } = data;
97012
+ if (index === undefined) {
97013
+ index = keyToExplicitOrder.has(key)
97014
+ ? keyToExplicitOrder.get(key)
97015
+ : Infinity;
97016
+ }
97000
97017
 
97001
97018
  syncItem(key, index, data);
97002
97019
  notify();
@@ -97014,6 +97031,24 @@ const createItemTracker = (onChange) => {
97014
97031
  return keyToOrderedIndex.get(key) ?? -1;
97015
97032
  };
97016
97033
 
97034
+ const setOrder = (ids) => {
97035
+ let changed = false;
97036
+ for (let i = 0; i < ids.length; i++) {
97037
+ const key = idToKey.get(ids[i]);
97038
+ if (key === undefined || !allRegistrations.has(key)) {
97039
+ continue;
97040
+ }
97041
+ if (keyToExplicitOrder.get(key) === i) {
97042
+ continue;
97043
+ }
97044
+ syncItem(key, i, allRegistrations.get(key));
97045
+ changed = true;
97046
+ }
97047
+ if (changed) {
97048
+ notify();
97049
+ }
97050
+ };
97051
+
97017
97052
  const getTrackedItemByIndex = (index) => {
97018
97053
  const key = orderedKeys[index];
97019
97054
  if (key === undefined) {
@@ -97047,6 +97082,7 @@ const createItemTracker = (onChange) => {
97047
97082
  return {
97048
97083
  useTrackItem,
97049
97084
  getTrackedItemByIndex,
97085
+ setOrder,
97050
97086
  peekItems,
97051
97087
  itemsSignal,
97052
97088
  visibleItemsSignal,
@@ -97444,8 +97480,8 @@ const getNumericRange = items => {
97444
97480
 
97445
97481
  // Wheel.Item registers its {value, label} here so WheelUI knows the full ordered
97446
97482
  // list of items regardless of how children are wrapped (providers, fragments…).
97447
- // indexRef gives each item its position: WheelUI resets it to 0 every render and
97448
- // each item reads-and-increments it as it renders, in document order.
97483
+ // The position comes after the commit, from the marker each item leaves where it
97484
+ // stands (see WheelUI).
97449
97485
  const WheelItemTrackerContext = createContext(null);
97450
97486
 
97451
97487
  // Lets a WheelGroup push shared presentation (glass, orientation) down to every
@@ -98067,15 +98103,21 @@ function WheelUI(props) {
98067
98103
  // (children may be wrapped). This ordered list is the source of truth for the
98068
98104
  // whole wheel — the rendered rows are a small recycled window over it.
98069
98105
  const tracker = useItemTracker();
98070
- const indexRef = useRef(0);
98071
- indexRef.current = 0;
98072
- const trackerContextRef = useRef(null);
98073
- if (!trackerContextRef.current) {
98074
- trackerContextRef.current = {
98075
- tracker,
98076
- indexRef
98077
- };
98078
- }
98106
+ // The order is read from the markers the items leave in the DOM, not from the
98107
+ // order they render in: an item whose props did not change may skip a render
98108
+ // (signals' shouldComponentUpdate), and a render count would then rank the
98109
+ // ones that did render first.
98110
+ useLayoutEffect(() => {
98111
+ const el = ref.current;
98112
+ if (!el) {
98113
+ return;
98114
+ }
98115
+ const ids = [];
98116
+ for (const marker of el.querySelectorAll("[data-navi-wheel-item]")) {
98117
+ ids.push(marker.getAttribute("data-navi-wheel-item"));
98118
+ }
98119
+ tracker.setOrder(ids);
98120
+ });
98079
98121
  const trackedItems = tracker.itemsSignal.value;
98080
98122
  const trackedItemsRef = useRef(trackedItems);
98081
98123
  trackedItemsRef.current = trackedItems;
@@ -98875,7 +98917,7 @@ function WheelUI(props) {
98875
98917
  className: "navi_wheel_pane",
98876
98918
  "data-side": "start"
98877
98919
  }), jsx(WheelItemTrackerContext.Provider, {
98878
- value: trackerContextRef.current,
98920
+ value: tracker,
98879
98921
  children: jsx(ControlFacadeChildrenWrapper, {
98880
98922
  facadeController: facadeController,
98881
98923
  children: children
@@ -98988,9 +99030,10 @@ const WheelWindow = ({
98988
99030
  * Wheel.Item — a value in a Wheel. Must be used inside <Wheel>.
98989
99031
  *
98990
99032
  * Registration only: it records its {value, label, itemProps} with the wheel and
98991
- * renders NOTHING. The wheel is virtualized — it renders a small recycled window
98992
- * of rows and fills each from this tracked data — so the value list is the source
98993
- * of truth, not a per-item DOM node. Selection lives on the wheel's value.
99033
+ * renders no row, only a hidden marker the wheel reads its position from. The
99034
+ * wheel is virtualized — it renders a small recycled window of rows and fills
99035
+ * each from this tracked data — so the value list is the source of truth, not a
99036
+ * per-item DOM node. Selection lives on the wheel's value.
98994
99037
  *
98995
99038
  * @type {import("preact").FunctionComponent<{
98996
99039
  * value: any,
@@ -99007,16 +99050,17 @@ const WheelItem = ({
99007
99050
  const idDefault = useGeneratedId();
99008
99051
  const resolvedId = id || idDefault;
99009
99052
  // Wheel.Item must live inside a Wheel, so the context is always present.
99010
- const trackerContext = useContext(WheelItemTrackerContext);
99011
- const index = trackerContext.indexRef.current++;
99012
- trackerContext.tracker.useTrackItem({
99053
+ const tracker = useContext(WheelItemTrackerContext);
99054
+ tracker.useTrackItem({
99013
99055
  id: resolvedId,
99014
- index,
99015
99056
  value,
99016
99057
  label: children,
99017
99058
  itemProps: rest
99018
99059
  });
99019
- return null;
99060
+ return jsx("span", {
99061
+ hidden: true,
99062
+ "data-navi-wheel-item": resolvedId
99063
+ });
99020
99064
  };
99021
99065
  Wheel.Item = WheelItem;
99022
99066