@jsenv/navi 0.27.5 → 0.27.6

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.
@@ -3,7 +3,7 @@ import { isValidElement, createContext, h, toChildArray, render, Fragment, clone
3
3
  import { useErrorBoundary, useLayoutEffect, useEffect, useContext, useMemo, useRef, useState, useCallback, useId } from "preact/hooks";
4
4
  import { jsxs, jsx, Fragment as Fragment$1 } from "preact/jsx-runtime";
5
5
  import { signal, effect, computed, batch, useSignal } from "@preact/signals";
6
- import { createIterableWeakSet, createEventGroupLogger, mergeOneStyle, normalizeStyle, createPubSub, findEvent, dispatchInternalCustomEvent, getElementSignature, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, measureLongestVisualLineWidth, findFocusDelegateTarget, getKeyboardEventDefaultAction, resolveCSSSize, activeElementSignal, hasCSSSizeUnit, resolveOklchLightness, contrastColor, dispatchCustomEvent, initFocusGroup, elementIsFocusable, findFocusable, trapScrollInside, trapFocusInside, snapToPixel, scrollIntoViewScoped, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, measureWidestChildRow } from "@jsenv/dom";
6
+ import { createIterableWeakSet, createEventGroupLogger, mergeOneStyle, normalizeStyle, createPubSub, findEvent, dispatchInternalCustomEvent, getElementSignature, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, measureLongestVisualLineWidth, findFocusDelegateTarget, getKeyboardEventDefaultAction, resolveCSSSize, activeElementSignal, hasCSSSizeUnit, resolveOklchLightness, contrastColor, dispatchCustomEvent, initFocusGroup, elementIsFocusable, findFocusable, snapToPixel, trapScrollInside, trapFocusInside, scrollIntoViewScoped, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, measureWidestChildRow } from "@jsenv/dom";
7
7
  export { contrastColor, startDragToReorder } from "@jsenv/dom";
8
8
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
9
9
  import { createValidity } from "@jsenv/validity";
@@ -24713,6 +24713,7 @@ const CONTROL_ATTRIBUTE_SET = new Set([
24713
24713
  "navi-input-type",
24714
24714
  "navi-control-proxy-for",
24715
24715
  "aria-controls",
24716
+ "tabIndex",
24716
24717
 
24717
24718
  "data-callout-arrow-x",
24718
24719
  "data-callout-point-to-border-box",
@@ -28653,8 +28654,9 @@ installImportMetaCssBuild(import.meta);const css$z = /* css */`
28653
28654
  --margin: 3px 3px 0 5px;
28654
28655
  --outline-offset: 1px;
28655
28656
  --outline-width: 2px;
28656
- --width: 0.815em;
28657
- --height: 0.815em;
28657
+ /* Rounding ensures outline is visually a nice circle */
28658
+ --width: round(0.815em, 1px);
28659
+ --height: round(0.815em, 1px);
28658
28660
 
28659
28661
  --color-mix-light: black;
28660
28662
  --color-mix-dark: white;
@@ -31623,9 +31625,18 @@ const Dialog = props => {
31623
31625
  const debugFocus = useDebugFocus();
31624
31626
  const openedRef = useRef(false);
31625
31627
  const [addCleanup, cleanup] = useCleanup();
31626
- const open = e => {
31628
+ const open = (e, {
31629
+ anchor
31630
+ }) => {
31631
+ const effectiveAnchor = anchor || document.documentElement;
31627
31632
  debugPopup(`"${e.type}" on ${getElementSignature(e.target)} -> openDialog`);
31628
31633
  const dialogEl = ref.current;
31634
+ const {
31635
+ width,
31636
+ height
31637
+ } = effectiveAnchor.getBoundingClientRect();
31638
+ dialogEl.style.setProperty("--anchor-width", `${snapToPixel(width)}px`);
31639
+ dialogEl.style.setProperty("--anchor-height", `${snapToPixel(height)}px`);
31629
31640
  dialogEl.showModal();
31630
31641
  focusFirstAutofocusOrFocusable(dialogEl, debugFocus, e);
31631
31642
  if (scrollTrap) {
@@ -31647,7 +31658,9 @@ const Dialog = props => {
31647
31658
  event: e
31648
31659
  });
31649
31660
  };
31650
- const onRequestOpen = e => {
31661
+ const onRequestOpen = (e, {
31662
+ anchor
31663
+ }) => {
31651
31664
  const dialogEl = ref.current;
31652
31665
  if (!dialogEl) {
31653
31666
  return;
@@ -31655,7 +31668,9 @@ const Dialog = props => {
31655
31668
  if (openedRef.current) {
31656
31669
  return;
31657
31670
  }
31658
- open(e);
31671
+ open(e, {
31672
+ anchor
31673
+ });
31659
31674
  };
31660
31675
  const onRequestClose = e => {
31661
31676
  const dialogEl = ref.current;
@@ -31687,7 +31702,12 @@ const Dialog = props => {
31687
31702
  onRequestClose(e);
31688
31703
  },
31689
31704
  onnavi_request_open: e => {
31690
- onRequestOpen(e);
31705
+ const {
31706
+ anchor
31707
+ } = e.detail;
31708
+ onRequestOpen(e, {
31709
+ anchor
31710
+ });
31691
31711
  },
31692
31712
  onnavi_request_close: e => {
31693
31713
  onRequestClose(e);
@@ -31960,7 +31980,10 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
31960
31980
  /* The list scrolls inside the popover */
31961
31981
  .navi_list_container {
31962
31982
  width: 100%;
31963
- border-radius: inherit;
31983
+ border-radius: max(
31984
+ 0px,
31985
+ var(--picker-border-radius) - var(--picker-border-width)
31986
+ );
31964
31987
  overflow: auto;
31965
31988
  overscroll-behavior: none;
31966
31989
  }
@@ -32006,6 +32029,7 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
32006
32029
  /* dialog */
32007
32030
  &[aria-haspopup="dialog"] {
32008
32031
  .navi_picker_dialog {
32032
+ min-width: var(--anchor-width, 0px);
32009
32033
  max-height: 95dvh;
32010
32034
  padding: 0;
32011
32035
  background: var(--picker-background-color);
@@ -32030,9 +32054,12 @@ installImportMetaCssBuild(import.meta);const css$p = /* css */`
32030
32054
 
32031
32055
  .navi_list_container {
32032
32056
  width: 100%;
32033
- border: none;
32034
- border-radius: 0;
32035
- outline: none;
32057
+ border-radius: max(
32058
+ 0px,
32059
+ var(--picker-border-radius) - var(--picker-border-width)
32060
+ );
32061
+ overflow: auto;
32062
+ overscroll-behavior: none;
32036
32063
  }
32037
32064
 
32038
32065
  /* .navi_list_container {
@@ -33816,11 +33843,10 @@ const css$m = /* css */`
33816
33843
  .navi_list_fallback,
33817
33844
  .navi_list_no_match_fallback {
33818
33845
  order: -1;
33846
+ color: light-dark(#888, #aaa);
33819
33847
  &[navi-default] {
33820
33848
  display: inline;
33821
33849
  padding: var(--list-item-padding);
33822
- color: light-dark(#888, #aaa);
33823
- font-size: 0.9em;
33824
33850
  text-align: center;
33825
33851
  user-select: none;
33826
33852
  }
@@ -34139,7 +34165,9 @@ const useListScrollSync = ({
34139
34165
  }
34140
34166
  const scrollItemIntoView = itemEl => {
34141
34167
  const trigger = `"${event.type}" on ${getElementSignature(event.target)} (${reason})`;
34142
- const block = event.type === "keydown" ? "nearest" : "center";
34168
+ // When we display the list we prefer to have selected item at the center
34169
+ // otherwise, usually when focused by arrow nav, we want to keep it into view close to the nearest edge
34170
+ const block = event.type === "navi_displayed" ? "center" : "nearest";
34143
34171
  const scrollToItemCall = `${getElementSignature(itemEl)}.scrollIntoView({ block: "${block}", container: "nearest" })`;
34144
34172
  const listScrollContainerEl = ref.current.querySelector(`.navi_list_scroll_container`);
34145
34173
  debugScroll(`${trigger} -> ${scrollToItemCall}`);
@@ -34223,12 +34251,16 @@ const useListScrollSync = ({
34223
34251
  const firstSelected = items.find(i => i.selected);
34224
34252
  if (firstSelected) {
34225
34253
  scrollToItem(firstSelected, {
34226
- event: openEvent,
34254
+ event: new CustomEvent("navi_displayed", {
34255
+ detail: {
34256
+ originalEvent: openEvent
34257
+ }
34258
+ }),
34227
34259
  reason: "scroll to selected"
34228
34260
  });
34229
34261
  } else {
34230
34262
  scrollToItem(items[0], {
34231
- event: new CustomEvent("navi_list_nav_top_on_displayed", {
34263
+ event: new CustomEvent("navi_displayed", {
34232
34264
  detail: {
34233
34265
  originalEvent: openEvent
34234
34266
  }
@@ -34661,12 +34693,25 @@ const ListItemRealOrVoid = props => {
34661
34693
  const listItemVnode = jsx(ListItemReal, {
34662
34694
  ...props
34663
34695
  });
34664
- // Use group-scoped visible index for separator when inside a group,
34665
- // so separators are only rendered between items within the same group.
34666
- const separatorIndex = groupVisibleIndex !== null ? groupVisibleIndex : visibleIndex;
34667
- if (!separator || separatorIndex === 0) {
34696
+ // For separator decision, we need to know "am I the first visible item?".
34697
+ // We deliberately do NOT use tracker's visibleIndex here because, during a
34698
+ // reorder render pass (e.g. items resorted by search score), other items
34699
+ // still have stale keyToExplicitOrder values — the binary search reads
34700
+ // those stale values and computes wrong indices. The result is that no
34701
+ // item gets visibleIndex === 0 and a spurious <hr> appears at the top.
34702
+ //
34703
+ // Instead we use the parent-provided index, which is race-free:
34704
+ // - global list: props.index === 0 means "first by explicit order"
34705
+ // (parent passes sequential indices starting at 0; filtered items
34706
+ // are already pushed to the end by useSearchText)
34707
+ // - inside a group: each group has its own item tracker and group
34708
+ // items don't reorder, so groupVisibleIndex is reliable
34709
+ const isFirstInList = groupVisibleIndex === null ? props.index === 0 : groupVisibleIndex === 0;
34710
+ if (!separator || isFirstInList) {
34668
34711
  return listItemVnode;
34669
34712
  }
34713
+ // separatorIndex is only used as the function-form argument (gap index)
34714
+ const separatorIndex = groupVisibleIndex === null ? visibleIndex : groupVisibleIndex;
34670
34715
  const separatorVnode = typeof separator === "function" ? separator(separatorIndex - 1) : separator;
34671
34716
  return jsxs(Fragment$1, {
34672
34717
  children: [separatorVnode, listItemVnode]
@@ -34716,6 +34761,8 @@ const ListItemReal = props => {
34716
34761
  "navi-list-item-real": "",
34717
34762
  ...rest,
34718
34763
  index: undefined,
34764
+ selected: undefined,
34765
+ matchScore: undefined,
34719
34766
  hidden: hidden,
34720
34767
  ref: ref,
34721
34768
  children: children
@@ -35561,22 +35608,34 @@ installImportMetaCssBuild(import.meta);const css$k = /* css */`
35561
35608
  --x-picker-border-color: var(--picker-border-color);
35562
35609
  --x-picker-padding-top: var(
35563
35610
  --picker-padding-top,
35564
- var(--picker-padding-y, var(--picker-padding-y-default))
35611
+ var(
35612
+ --picker-padding-y,
35613
+ var(--picker-padding, var(--picker-padding-y-default))
35614
+ )
35565
35615
  );
35566
35616
  --x-picker-padding-right-base: var(
35567
35617
  --picker-padding-right,
35568
- var(--picker-padding-x, var(--picker-padding-x-default))
35618
+ var(
35619
+ --picker-padding-x,
35620
+ var(--picker-padding, var(--picker-padding-x-default))
35621
+ )
35569
35622
  );
35570
35623
  --x-picker-padding-right: calc(
35571
35624
  var(--x-picker-padding-right-base) + var(--picker-right-slot-size) - 2px
35572
35625
  );
35573
35626
  --x-picker-padding-left: var(
35574
35627
  --picker-padding-left,
35575
- var(--picker-padding-x, var(--picker-padding-x-default))
35628
+ var(
35629
+ --picker-padding-x,
35630
+ var(--picker-padding, var(--picker-padding-x-default))
35631
+ )
35576
35632
  );
35577
35633
  --x-picker-padding-bottom: var(
35578
35634
  --picker-padding-bottom,
35579
- var(--picker-padding-y, var(--picker-padding-y-default))
35635
+ var(
35636
+ --picker-padding-y,
35637
+ var(--picker-padding, var(--picker-padding-y-default))
35638
+ )
35580
35639
  );
35581
35640
  --x-picker-color: var(--picker-color);
35582
35641