@jsenv/navi 0.24.1 → 0.25.0

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.
@@ -6292,51 +6292,37 @@ const DIMENSION_PROPS = {
6292
6292
  expand: applyOnTwoProps("expandX", "expandY"),
6293
6293
  shrink: applyOnTwoProps("shrinkX", "shrinkY"),
6294
6294
  // apply after width/height to override if both are set
6295
- expandX: (value, { parentBoxFlow, boxFlow }) => {
6295
+ expandX: (value, { parentBoxFlow }) => {
6296
6296
  if (!value) {
6297
6297
  return null;
6298
6298
  }
6299
6299
  const inHorizontalFlexFlow =
6300
6300
  parentBoxFlow === "flex-x" || parentBoxFlow === "inline-flex-x";
6301
- const selfHorizontalFlexFlow =
6302
- boxFlow === "flex-x" || boxFlow === "inline-flex-x";
6303
- if (selfHorizontalFlexFlow || inHorizontalFlexFlow) {
6304
- if (!inHorizontalFlexFlow) {
6305
- if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
6306
- return { alignSelf: "stretch" };
6307
- }
6308
- return { flexGrow: 1, flexBasis: "0%" };
6301
+ if (!inHorizontalFlexFlow) {
6302
+ // Can't use flexGrow parent is not flex-x
6303
+ if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
6304
+ return { alignSelf: "stretch" };
6309
6305
  }
6310
- return { flexGrow: 1, flexBasis: "0%" }; // Grow horizontally in column
6311
- }
6312
- if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
6313
- return { alignSelf: "stretch" }; // Stretch to cross-axis width in flex-y
6306
+ return { width: "100%" };
6314
6307
  }
6315
- return { width: "100%" }; // Take full width outside flex
6308
+ // Parent is flex-x: grow as flex item
6309
+ return { flexGrow: 1, flexBasis: "0%" };
6316
6310
  },
6317
- expandY: (value, { parentBoxFlow, boxFlow }) => {
6311
+ expandY: (value, { parentBoxFlow }) => {
6318
6312
  if (!value) {
6319
6313
  return null;
6320
6314
  }
6321
6315
  const inVerticalFlexFlow =
6322
6316
  parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y";
6323
- const inHorizontalFlexFlow =
6324
- parentBoxFlow === "flex-x" || parentBoxFlow === "inline-flex-x";
6325
- const selfVerticalFlexFlow =
6326
- boxFlow === "flex-y" || boxFlow === "inline-flex-y";
6327
- if (selfVerticalFlexFlow || inVerticalFlexFlow) {
6328
- if (!inVerticalFlexFlow) {
6329
- if (inHorizontalFlexFlow) {
6330
- return { alignSelf: "stretch" };
6331
- }
6332
- return { flexGrow: 1, flexBasis: "0%" };
6317
+ if (!inVerticalFlexFlow) {
6318
+ // Can't use flexGrow parent is not flex-y
6319
+ if (parentBoxFlow === "flex-x" || parentBoxFlow === "inline-flex-x") {
6320
+ return { alignSelf: "stretch" };
6333
6321
  }
6334
- return { flexGrow: 1, flexBasis: "0%" }; // Grow vertically in row
6322
+ return { height: "100%" };
6335
6323
  }
6336
- if (inHorizontalFlexFlow) {
6337
- return { alignSelf: "stretch" }; // Stretch to cross-axis height in flex-x
6338
- }
6339
- return { height: "100%" }; // Take full height outside flex
6324
+ // Parent is flex-y: grow as flex item
6325
+ return { flexGrow: 1, flexBasis: "0%" };
6340
6326
  },
6341
6327
  shrinkX: (value) => {
6342
6328
  if (!value || value === "0") {
@@ -6751,7 +6737,6 @@ const negativeEntries = (map) => {
6751
6737
  return result;
6752
6738
  };
6753
6739
 
6754
-
6755
6740
  // Unified design scale using t-shirt sizes with rem units for accessibility.
6756
6741
  // This scale is used for spacing to create visual harmony
6757
6742
  // and consistent proportions throughout the design system.
@@ -7226,8 +7211,9 @@ const PSEUDO_CLASSES = {
7226
7211
  attribute: "data-invalid",
7227
7212
  test: (el) => el.matches(":invalid"),
7228
7213
  },
7229
- ":-navi-highlighted": {
7230
- attribute: "data-highlighted",
7214
+ "::highlight": {},
7215
+ ":-navi-pointed": {
7216
+ attribute: "data-pointed",
7231
7217
  },
7232
7218
  ":-navi-selected": {
7233
7219
  attribute: "data-selected",
@@ -17175,15 +17161,23 @@ const ONE_OF_CONSTRAINT = {
17175
17161
  if (allowedValues.has(fieldValue)) {
17176
17162
  return null;
17177
17163
  }
17164
+ const visibleOptions = listEl.querySelectorAll(
17165
+ "[role='option']:not([hidden])",
17166
+ );
17167
+ const isNoMatch = visibleOptions.length === 0;
17178
17168
  const message = field.getAttribute("data-one-of-message");
17179
- if (message) {
17180
- return message;
17169
+ const noMatchMessage = field.getAttribute("data-one-of-no-match-message");
17170
+ if (isNoMatch) {
17171
+ return (
17172
+ noMatchMessage || `Aucune suggestion ne correspond à votre saisie.`
17173
+ );
17181
17174
  }
17182
- return `Veuillez choisir une valeur parmi les suggestions.`;
17175
+ return message || `Veuillez choisir une valeur parmi les suggestions.`;
17183
17176
  },
17184
17177
  };
17185
17178
  CONSTRAINT_ATTRIBUTE_SET.add("data-one-of");
17186
17179
  CONSTRAINT_ATTRIBUTE_SET.add("data-one-of-message");
17180
+ CONSTRAINT_ATTRIBUTE_SET.add("data-one-of-no-match-message");
17187
17181
 
17188
17182
  const collectAllowedValues = (listEl) => {
17189
17183
  const values = new Set();
@@ -25832,6 +25826,7 @@ const css$j = /* css */`
25832
25826
  display: inline-flex;
25833
25827
  margin: 0;
25834
25828
  padding: 0;
25829
+ align-items: center;
25835
25830
  justify-content: center;
25836
25831
  font-size: var(--font-size);
25837
25832
  background: none;
@@ -25987,8 +25982,8 @@ Object.assign(PSEUDO_CLASSES, {
25987
25982
  const InputPseudoElements = ["::-navi-loader"];
25988
25983
  const InputChildPropSet = new Set([...fieldPropSet]);
25989
25984
  const InputTextualBasic = props => {
25990
- if (props.combobox) {
25991
- return jsx(InputTextualCombobox, {
25985
+ if (props.suggestions) {
25986
+ return jsx(InputTextualWithSuggestions, {
25992
25987
  ...props
25993
25988
  });
25994
25989
  }
@@ -25996,8 +25991,8 @@ const InputTextualBasic = props => {
25996
25991
  ...props
25997
25992
  });
25998
25993
  };
25999
- const InputTextualCombobox = ({
26000
- combobox,
25994
+ const InputTextualWithSuggestions = ({
25995
+ suggestions,
26001
25996
  onInput,
26002
25997
  onFocus,
26003
25998
  onBlur,
@@ -26005,53 +26000,53 @@ const InputTextualCombobox = ({
26005
26000
  }) => {
26006
26001
  const defaultRef = useRef();
26007
26002
  const ref = rest.ref || defaultRef;
26008
- const [comboboxOpen, setComboboxOpen] = useState(false);
26009
- const comboboxOpenRef = useRef(false);
26010
- comboboxOpenRef.current = comboboxOpen;
26003
+ const [suggestionsOpen, setSuggestionsOpen] = useState(false);
26004
+ const suggestionsOpenRef = useRef(false);
26005
+ suggestionsOpenRef.current = suggestionsOpen;
26011
26006
  const showPopover = e => {
26012
- if (comboboxOpenRef.current) {
26007
+ if (suggestionsOpenRef.current) {
26013
26008
  return;
26014
26009
  }
26015
26010
  console.debug(`showPopover (e.type:${e.type})`);
26016
- const popoverEl = document.getElementById(combobox);
26011
+ const popoverEl = document.getElementById(suggestions);
26017
26012
  positionPopover();
26018
26013
  popoverEl.showPopover();
26019
- comboboxOpenRef.current = true;
26020
- setComboboxOpen(true);
26014
+ suggestionsOpenRef.current = true;
26015
+ setSuggestionsOpen(true);
26021
26016
  window.addEventListener("scroll", positionPopover, {
26022
26017
  capture: true,
26023
26018
  passive: true
26024
26019
  });
26025
26020
  };
26026
26021
  const hidePopover = e => {
26027
- if (!comboboxOpenRef.current) {
26022
+ if (!suggestionsOpenRef.current) {
26028
26023
  return;
26029
26024
  }
26030
26025
  console.debug(`hidePopover (e.type:${e.type})`);
26031
- comboboxOpenRef.current = false;
26032
- setComboboxOpen(false);
26026
+ suggestionsOpenRef.current = false;
26027
+ setSuggestionsOpen(false);
26033
26028
  window.removeEventListener("scroll", positionPopover, {
26034
26029
  capture: true
26035
26030
  });
26036
- const popoverEl = document.getElementById(combobox);
26031
+ const popoverEl = document.getElementById(suggestions);
26037
26032
  if (popoverEl) {
26038
- popoverEl.dispatchEvent(new CustomEvent("combobox-clear"));
26033
+ popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_clear"));
26039
26034
  popoverEl.hidePopover();
26040
26035
  }
26041
- setComboboxOpen(false);
26036
+ setSuggestionsOpen(false);
26042
26037
  };
26043
26038
  const positionPopover = () => {
26044
26039
  const input = ref.current;
26045
26040
  const rect = input.getBoundingClientRect();
26046
- const popoverEl = document.getElementById(combobox);
26041
+ const popoverEl = document.getElementById(suggestions);
26047
26042
  if (popoverEl) {
26048
26043
  popoverEl.style.top = `${rect.bottom + 2}px`;
26049
26044
  popoverEl.style.left = `${rect.left}px`;
26050
26045
  popoverEl.style.width = `${rect.width}px`;
26051
26046
  }
26052
26047
  };
26053
- const dispatchToOptionList = customEvent => {
26054
- const popoverEl = document.getElementById(combobox);
26048
+ const dispatchToSuggestionList = customEvent => {
26049
+ const popoverEl = document.getElementById(suggestions);
26055
26050
  if (!popoverEl) {
26056
26051
  return false;
26057
26052
  }
@@ -26060,14 +26055,14 @@ const InputTextualCombobox = ({
26060
26055
  };
26061
26056
  useKeyboardShortcuts(ref, [{
26062
26057
  key: "arrowdown",
26063
- description: "Open popover and highlight next option",
26058
+ description: "Open popover and point to next suggestion",
26064
26059
  handler: e => {
26065
26060
  showPopover(e);
26066
- const popoverEl = document.getElementById(combobox);
26061
+ const popoverEl = document.getElementById(suggestions);
26067
26062
  if (!popoverEl) {
26068
26063
  return false;
26069
26064
  }
26070
- popoverEl.dispatchEvent(new CustomEvent("combobox-navigate", {
26065
+ popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_navigate", {
26071
26066
  detail: {
26072
26067
  direction: "down"
26073
26068
  }
@@ -26076,10 +26071,10 @@ const InputTextualCombobox = ({
26076
26071
  }
26077
26072
  }, {
26078
26073
  key: "arrowup",
26079
- description: "Open popover and highlight previous option",
26074
+ description: "Open popover and point to previous suggestion",
26080
26075
  handler: e => {
26081
26076
  showPopover(e);
26082
- return dispatchToOptionList(new CustomEvent("combobox-navigate", {
26077
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
26083
26078
  detail: {
26084
26079
  direction: "up"
26085
26080
  }
@@ -26087,12 +26082,12 @@ const InputTextualCombobox = ({
26087
26082
  }
26088
26083
  }, {
26089
26084
  key: "home",
26090
- description: "Highlight first option",
26085
+ description: "Point to first suggestion",
26091
26086
  handler: () => {
26092
- if (!comboboxOpenRef.current) {
26087
+ if (!suggestionsOpenRef.current) {
26093
26088
  return false;
26094
26089
  }
26095
- return dispatchToOptionList(new CustomEvent("combobox-navigate", {
26090
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
26096
26091
  detail: {
26097
26092
  direction: "first"
26098
26093
  }
@@ -26100,12 +26095,12 @@ const InputTextualCombobox = ({
26100
26095
  }
26101
26096
  }, {
26102
26097
  key: "end",
26103
- description: "Highlight last option",
26098
+ description: "Point to last suggestion",
26104
26099
  handler: () => {
26105
- if (!comboboxOpenRef.current) {
26100
+ if (!suggestionsOpenRef.current) {
26106
26101
  return false;
26107
26102
  }
26108
- return dispatchToOptionList(new CustomEvent("combobox-navigate", {
26103
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
26109
26104
  detail: {
26110
26105
  direction: "last"
26111
26106
  }
@@ -26113,12 +26108,12 @@ const InputTextualCombobox = ({
26113
26108
  }
26114
26109
  }, {
26115
26110
  key: "enter",
26116
- description: "Confirm highlighted option",
26111
+ description: "Confirm pointed suggestion",
26117
26112
  handler: () => {
26118
- if (!comboboxOpenRef.current) {
26113
+ if (!suggestionsOpenRef.current) {
26119
26114
  return false;
26120
26115
  }
26121
- return dispatchToOptionList(new CustomEvent("combobox-confirm", {
26116
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_confirm", {
26122
26117
  cancelable: true
26123
26118
  }));
26124
26119
  }
@@ -26126,7 +26121,7 @@ const InputTextualCombobox = ({
26126
26121
  key: "escape",
26127
26122
  description: "Close popover",
26128
26123
  handler: e => {
26129
- if (!comboboxOpenRef.current) {
26124
+ if (!suggestionsOpenRef.current) {
26130
26125
  return false;
26131
26126
  }
26132
26127
  hidePopover(e);
@@ -26135,7 +26130,7 @@ const InputTextualCombobox = ({
26135
26130
  }]);
26136
26131
  useEffect(() => {
26137
26132
  const inputEl = ref.current;
26138
- const popoverEl = document.getElementById(combobox);
26133
+ const popoverEl = document.getElementById(suggestions);
26139
26134
  if (!popoverEl) {
26140
26135
  return undefined;
26141
26136
  }
@@ -26146,18 +26141,18 @@ const InputTextualCombobox = ({
26146
26141
  }));
26147
26142
  hidePopover(e);
26148
26143
  };
26149
- popoverEl.addEventListener("combobox-selected", onSelected);
26144
+ popoverEl.addEventListener("navi_suggestion_list_selected", onSelected);
26150
26145
  return () => {
26151
- popoverEl.removeEventListener("combobox-selected", onSelected);
26146
+ popoverEl.removeEventListener("navi_suggestion_list_selected", onSelected);
26152
26147
  };
26153
- }, [combobox]);
26148
+ }, [suggestions]);
26154
26149
  return jsx(InputTextualPlain, {
26155
26150
  ref: ref,
26156
26151
  role: "combobox",
26157
26152
  autoComplete: "off",
26158
- "aria-controls": combobox,
26153
+ "aria-controls": suggestions,
26159
26154
  "aria-haspopup": "listbox",
26160
- "aria-expanded": comboboxOpen,
26155
+ "aria-expanded": suggestionsOpen,
26161
26156
  "aria-autocomplete": "list",
26162
26157
  onnavi_callout_open: e => {
26163
26158
  hidePopover(e);
@@ -26215,9 +26210,6 @@ const InputTextualPlain = props => {
26215
26210
  const innerOnInput = useStableCallback(onInput);
26216
26211
  const autoId = useId();
26217
26212
  const innerId = rest.id || autoId;
26218
- const {
26219
- ...remainingRest
26220
- } = remainingProps;
26221
26213
  const renderInput = inputProps => {
26222
26214
  return jsx(Box, {
26223
26215
  ...inputProps,
@@ -26286,7 +26278,7 @@ const InputTextualPlain = props => {
26286
26278
  baseChildPropSet: InputChildPropSet,
26287
26279
  "data-start-icon": innerIcon ? "" : undefined,
26288
26280
  "data-end-icon": cancelButton ? "" : undefined,
26289
- ...remainingRest,
26281
+ ...remainingProps,
26290
26282
  ref: undefined,
26291
26283
  children: [jsx(LoaderBackground, {
26292
26284
  loading: innerLoading,
@@ -27115,69 +27107,84 @@ const createItemTracker = () => {
27115
27107
  return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
27116
27108
  };
27117
27109
 
27118
- installImportMetaCssBuild(import.meta);const [useOptionItemTrackerProvider, useTrackOption] = createItemTracker();
27110
+ installImportMetaCssBuild(import.meta);const [useSuggestionItemTrackerProvider, useTrackSuggestion] = createItemTracker();
27119
27111
 
27120
27112
  /**
27121
- * OptionList + Option: a composable accessible listbox.
27113
+ * SuggestionList + Suggestion: a composable accessible listbox.
27122
27114
  *
27123
27115
  * Usage:
27124
- * <OptionList id="my-list" value={selected} onChange={setSelected}>
27125
- * <Option value="a">Option A</Option>
27126
- * <Option value="b">Option B</Option>
27127
- * </OptionList>
27116
+ * <SuggestionList id="my-list" value={selected} onChange={setSelected}>
27117
+ * <Suggestion value="a">Option A</Suggestion>
27118
+ * <Suggestion value="b">Option B</Suggestion>
27119
+ * </SuggestionList>
27120
+ *
27121
+ * CSS vars on .navi_suggestion_list:
27122
+ * --suggestion-list-border-radius, --suggestion-list-border-width, --suggestion-list-border-color, --suggestion-list-background-color, --suggestion-list-max-height
27128
27123
  *
27129
- * CSS vars on .navi_option_list:
27130
- * --border-radius, --border-width, --border-color, --background-color, --max-height
27124
+ * CSS vars on .navi_suggestion:
27125
+ * --suggestion-padding, --suggestion-color, --suggestion-background-color, --suggestion-font-weight
27126
+ * --suggestion-color-hover, --suggestion-background-color-hover
27127
+ * --suggestion-color-pointed, --suggestion-background-color-pointed
27128
+ * --suggestion-color-selected, --suggestion-background-color-selected, --suggestion-font-weight-selected
27129
+ * --suggestion-color-pointed-selected, --suggestion-background-color-pointed-selected
27130
+ * --suggestion-color-highlight, --suggestion-background-color-highlight
27131
27131
  *
27132
- * CSS vars on .navi_option:
27133
- * --padding, --color, --background-color, --font-weight
27134
- * --color-hover, --background-color-hover
27135
- * --color-highlighted, --background-color-highlighted
27136
- * --color-selected, --background-color-selected, --font-weight-selected
27137
- * --color-highlighted-selected, --background-color-highlighted-selected
27132
+ * CSS vars on .navi_suggestion_group_label:
27133
+ * --suggestion-group-label-padding, --suggestion-group-label-color, --suggestion-group-label-font-size, --suggestion-group-label-font-weight
27138
27134
  */
27139
27135
 
27140
27136
  const css$g = /* css */`
27141
27137
  @layer navi {
27142
- .navi_option_list {
27143
- --border-radius: 4px;
27144
- --border-width: 1px;
27145
- --border-color: light-dark(#ccc, #555);
27146
- --background-color: light-dark(#fff, #1e1e1e);
27147
- --max-height: 220px;
27148
- }
27149
- .navi_option {
27150
- --padding: 8px 12px;
27151
- --color: inherit;
27152
- --background-color: transparent;
27153
- --font-weight: inherit;
27138
+ .navi_suggestion_list {
27139
+ --suggestion-list-border-radius: 4px;
27140
+ --suggestion-list-border-width: 1px;
27141
+ --suggestion-list-border-color: light-dark(#ccc, #555);
27142
+ --suggestion-list-background-color: light-dark(#fff, #1e1e1e);
27143
+ --suggestion-list-max-height: 220px;
27144
+ }
27145
+ .navi_suggestion_group_label {
27146
+ --suggestion-group-label-padding: 4px 12px 2px;
27147
+ --suggestion-group-label-color: light-dark(#888, #aaa);
27148
+ --suggestion-group-label-font-size: 0.75em;
27149
+ --suggestion-group-label-font-weight: 600;
27150
+ }
27151
+ .navi_suggestion {
27152
+ --suggestion-padding: 8px 12px;
27153
+ --suggestion-color: inherit;
27154
+ --suggestion-background-color: transparent;
27155
+ --suggestion-font-weight: inherit;
27154
27156
 
27155
27157
  /* Hover (mouse) */
27156
- --color-hover: var(--color);
27157
- --background-color-hover: light-dark(#f5f5f5, #2a2a2a);
27158
+ --suggestion-color-hover: var(--suggestion-color);
27159
+ --suggestion-background-color-hover: light-dark(#f5f5f5, #2a2a2a);
27158
27160
 
27159
- /* Highlighted (keyboard navigation cursor) */
27160
- --color-highlighted: var(--color);
27161
- --background-color-highlighted: light-dark(#e8f0fe, #1c3a6e);
27161
+ /* Pointed (keyboard navigation position) */
27162
+ --suggestion-color-pointed: var(--suggestion-color);
27163
+ --suggestion-background-color-pointed: light-dark(#e8f0fe, #1c3a6e);
27162
27164
 
27163
27165
  /* Selected */
27164
- --color-selected: light-dark(#1a73e8, #7baaf7);
27165
- --background-color-selected: light-dark(#e8f0fe, #1c3a6e);
27166
- --font-weight-selected: 500;
27167
-
27168
- /* Highlighted + selected */
27169
- --color-highlighted-selected: var(--color-selected);
27170
- --background-color-highlighted-selected: light-dark(#d2e3fc, #174ea6);
27166
+ --suggestion-color-selected: light-dark(#1a73e8, #7baaf7);
27167
+ --suggestion-background-color-selected: light-dark(#e8f0fe, #1c3a6e);
27168
+ --suggestion-font-weight-selected: 500;
27169
+
27170
+ /* Highlight (CSS Highlight API match) */
27171
+ --suggestion-color-highlight: inherit;
27172
+ --suggestion-background-color-highlight: #ffe066;
27173
+ --suggestion-color-pointed-selected: var(--suggestion-color-selected);
27174
+ --suggestion-background-color-pointed-selected: light-dark(
27175
+ #d2e3fc,
27176
+ #174ea6
27177
+ );
27171
27178
  }
27172
27179
  }
27173
27180
 
27174
- .navi_option_list {
27175
- --x-border-radius: var(--border-radius);
27176
- --x-border-width: var(--border-width);
27177
- --x-border-color: var(--border-color);
27178
- --x-background-color: var(--background-color);
27181
+ .navi_suggestion_list {
27182
+ --x-border-radius: var(--suggestion-list-border-radius);
27183
+ --x-border-width: var(--suggestion-list-border-width);
27184
+ --x-border-color: var(--suggestion-list-border-color);
27185
+ --x-background-color: var(--suggestion-list-background-color);
27179
27186
  box-sizing: border-box;
27180
- max-height: var(--max-height);
27187
+ max-height: var(--suggestion-list-max-height);
27181
27188
 
27182
27189
  margin: 0;
27183
27190
  padding: 0;
@@ -27197,12 +27204,16 @@ const css$g = /* css */`
27197
27204
  border: none;
27198
27205
  }
27199
27206
  }
27200
- .navi_option {
27201
- --x-color: var(--color);
27202
- --x-background-color: var(--background-color);
27203
- --x-font-weight: var(--font-weight);
27207
+ ::highlight(navi-suggestion-match) {
27208
+ color: var(--suggestion-color-highlight);
27209
+ background-color: var(--suggestion-background-color-highlight);
27210
+ }
27211
+ .navi_suggestion {
27212
+ --x-color: var(--suggestion-color);
27213
+ --x-background-color: var(--suggestion-background-color);
27214
+ --x-font-weight: var(--suggestion-font-weight);
27204
27215
 
27205
- padding: var(--padding);
27216
+ padding: var(--suggestion-padding);
27206
27217
  color: var(--x-color);
27207
27218
  font-weight: var(--x-font-weight);
27208
27219
  background-color: var(--x-background-color);
@@ -27210,49 +27221,134 @@ const css$g = /* css */`
27210
27221
  user-select: none;
27211
27222
 
27212
27223
  &:hover {
27213
- --x-color: var(--color-hover);
27214
- --x-background-color: var(--background-color-hover);
27224
+ --x-color: var(--suggestion-color-hover);
27225
+ --x-background-color: var(--suggestion-background-color-hover);
27215
27226
  }
27216
27227
 
27217
- &[data-highlighted] {
27218
- --x-color: var(--color-highlighted);
27219
- --x-background-color: var(--background-color-highlighted);
27228
+ &[data-pointed] {
27229
+ --x-color: var(--suggestion-color-pointed);
27230
+ --x-background-color: var(--suggestion-background-color-pointed);
27220
27231
  }
27221
27232
 
27222
27233
  &[data-selected] {
27223
- --x-color: var(--color-selected);
27224
- --x-background-color: var(--background-color-selected);
27225
- --x-font-weight: var(--font-weight-selected);
27234
+ --x-color: var(--suggestion-color-selected);
27235
+ --x-background-color: var(--suggestion-background-color-selected);
27236
+ --x-font-weight: var(--suggestion-font-weight-selected);
27226
27237
  }
27227
27238
 
27228
- &[data-highlighted][data-selected] {
27229
- --x-color: var(--color-highlighted-selected);
27230
- --x-background-color: var(--background-color-highlighted-selected);
27239
+ &[data-pointed][data-selected] {
27240
+ --x-color: var(--suggestion-color-pointed-selected);
27241
+ --x-background-color: var(--suggestion-background-color-pointed-selected);
27231
27242
  }
27232
27243
  }
27244
+ .navi_suggestion_group_label {
27245
+ position: sticky;
27246
+ top: 0;
27247
+ z-index: 1;
27248
+ display: block;
27249
+ padding: var(--suggestion-group-label-padding);
27250
+ color: var(--suggestion-group-label-color);
27251
+ font-weight: var(--suggestion-group-label-font-weight);
27252
+ font-size: var(--suggestion-group-label-font-size);
27253
+ text-transform: uppercase;
27254
+ letter-spacing: 0.05em;
27255
+ background-color: var(--suggestion-group-label-background-color);
27256
+ user-select: none;
27257
+ }
27233
27258
  `;
27259
+ const SuggestionListStyleCSSVars = {
27260
+ borderRadius: "--suggestion-list-border-radius",
27261
+ borderWidth: "--suggestion-list-border-width",
27262
+ borderColor: "--suggestion-list-border-color",
27263
+ backgroundColor: "--suggestion-list-background-color",
27264
+ maxHeight: "--suggestion-list-max-height"
27265
+ };
27266
+ const SuggestionStyleCSSVars = {
27267
+ "padding": "--suggestion-padding",
27268
+ "color": "--suggestion-color",
27269
+ "backgroundColor": "--suggestion-background-color",
27270
+ "fontWeight": "--suggestion-font-weight",
27271
+ ":-navi-pointed": {
27272
+ color: "--suggestion-color-pointed",
27273
+ backgroundColor: "--suggestion-background-color-pointed"
27274
+ },
27275
+ ":hover": {
27276
+ color: "--suggestion-color-hover",
27277
+ backgroundColor: "--suggestion-background-color-hover"
27278
+ },
27279
+ ":-navi-selected": {
27280
+ color: "--suggestion-color-selected",
27281
+ backgroundColor: "--suggestion-background-color-selected",
27282
+ fontWeight: "--suggestion-font-weight-selected"
27283
+ },
27284
+ "::highlight": {
27285
+ color: "--suggestion-color-highlight",
27286
+ backgroundColor: "--suggestion-background-color-highlight"
27287
+ }
27288
+ };
27234
27289
 
27235
27290
  /**
27236
27291
  * Context OptionList provides downward to its Option children.
27237
27292
  */
27238
- const OptionListContext = createContext(null);
27239
- const OptionList = ({
27293
+ const SuggestionListContext = createContext(null);
27294
+ const SuggestionList = ({
27240
27295
  popover,
27241
27296
  onChange: onChangeProp,
27297
+ highlight,
27242
27298
  children,
27243
27299
  ...rest
27244
27300
  }) => {
27245
- import.meta.css = [css$g, "@jsenv/navi/src/field/option_list.jsx"];
27246
- const ItemTrackerProvider = useOptionItemTrackerProvider();
27247
- const [highlightedValue, setHighlightedValue] = useState(null);
27248
- const highlightedValueRef = useRef(null);
27249
- highlightedValueRef.current = highlightedValue;
27301
+ import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
27302
+ const ItemTrackerProvider = useSuggestionItemTrackerProvider();
27303
+ const [pointedValue, setPointedValue] = useState(null);
27304
+ const pointedValueRef = useRef(null);
27305
+ pointedValueRef.current = pointedValue;
27250
27306
  const ownId = useId();
27251
27307
  const id = rest.id ?? ownId;
27252
- const listRef = useRef(null);
27308
+ const defaultRef = useRef(null);
27309
+ const ref = rest.ref || defaultRef;
27310
+ useLayoutEffect(() => {
27311
+ if (!CSS.highlights) {
27312
+ return undefined;
27313
+ }
27314
+ if (!highlight) {
27315
+ CSS.highlights.delete("navi-suggestion-match");
27316
+ return undefined;
27317
+ }
27318
+ const listEl = ref.current;
27319
+ if (!listEl) {
27320
+ return undefined;
27321
+ }
27322
+ const ranges = [];
27323
+ const lowerHighlight = highlight.toLowerCase();
27324
+ for (const suggestionEl of listEl.querySelectorAll("[role='option']")) {
27325
+ const walker = document.createTreeWalker(suggestionEl, NodeFilter.SHOW_TEXT);
27326
+ let node;
27327
+ while (node = walker.nextNode()) {
27328
+ const text = node.textContent;
27329
+ const lowerText = text.toLowerCase();
27330
+ let index = lowerText.indexOf(lowerHighlight);
27331
+ while (index !== -1) {
27332
+ const range = new Range();
27333
+ range.setStart(node, index);
27334
+ range.setEnd(node, index + highlight.length);
27335
+ ranges.push(range);
27336
+ index = lowerText.indexOf(lowerHighlight, index + 1);
27337
+ }
27338
+ }
27339
+ }
27340
+ if (ranges.length === 0) {
27341
+ CSS.highlights.delete("navi-suggestion-match");
27342
+ } else {
27343
+ CSS.highlights.set("navi-suggestion-match", new Highlight(...ranges));
27344
+ }
27345
+ return () => {
27346
+ CSS.highlights.delete("navi-suggestion-match");
27347
+ };
27348
+ }, [highlight, children]);
27253
27349
  const effectiveOnChange = popover ? value => {
27254
27350
  onChangeProp?.(value);
27255
- listRef.current?.dispatchEvent(new CustomEvent("combobox-selected", {
27351
+ ref.current?.dispatchEvent(new CustomEvent("navi_suggestion_list_selected", {
27256
27352
  detail: {
27257
27353
  value
27258
27354
  },
@@ -27266,17 +27362,17 @@ const OptionList = ({
27266
27362
  if (values.length === 0) {
27267
27363
  return false;
27268
27364
  }
27269
- const current = highlightedValueRef.current;
27365
+ const current = pointedValueRef.current;
27270
27366
  if (direction === "down") {
27271
27367
  const idx = current === null ? -1 : values.indexOf(current);
27272
- setHighlightedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
27368
+ setPointedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
27273
27369
  } else if (direction === "up") {
27274
27370
  const idx = current === null ? -1 : values.indexOf(current);
27275
- setHighlightedValue(values[idx > 0 ? idx - 1 : 0]);
27371
+ setPointedValue(values[idx > 0 ? idx - 1 : 0]);
27276
27372
  } else if (direction === "first") {
27277
- setHighlightedValue(values[0]);
27373
+ setPointedValue(values[0]);
27278
27374
  } else if (direction === "last") {
27279
- setHighlightedValue(values[values.length - 1]);
27375
+ setPointedValue(values[values.length - 1]);
27280
27376
  }
27281
27377
  return true;
27282
27378
  };
@@ -27284,53 +27380,53 @@ const OptionList = ({
27284
27380
  // Listen for commands dispatched by a linked Input (combobox mode)
27285
27381
  const noopRef = useRef(null);
27286
27382
  useEffect(() => {
27287
- if (!popover || !listRef.current) {
27383
+ if (!popover || !ref.current) {
27288
27384
  return undefined;
27289
27385
  }
27290
- const el = listRef.current;
27386
+ const el = ref.current;
27291
27387
  const onNavigate = e => {
27292
27388
  navigate(e.detail.direction);
27293
27389
  };
27294
27390
  const onConfirm = e => {
27295
- const current = highlightedValueRef.current;
27391
+ const current = pointedValueRef.current;
27296
27392
  if (current !== null) {
27297
27393
  onChangeRef.current?.(current);
27298
27394
  e.preventDefault();
27299
27395
  }
27300
27396
  };
27301
27397
  const onClear = () => {
27302
- setHighlightedValue(null);
27398
+ setPointedValue(null);
27303
27399
  };
27304
- el.addEventListener("combobox-navigate", onNavigate);
27305
- el.addEventListener("combobox-confirm", onConfirm);
27306
- el.addEventListener("combobox-clear", onClear);
27400
+ el.addEventListener("navi_suggestion_list_navigate", onNavigate);
27401
+ el.addEventListener("navi_suggestion_list_confirm", onConfirm);
27402
+ el.addEventListener("navi_suggestion_list_clear", onClear);
27307
27403
  return () => {
27308
- el.removeEventListener("combobox-navigate", onNavigate);
27309
- el.removeEventListener("combobox-confirm", onConfirm);
27310
- el.removeEventListener("combobox-clear", onClear);
27404
+ el.removeEventListener("navi_suggestion_list_navigate", onNavigate);
27405
+ el.removeEventListener("navi_suggestion_list_confirm", onConfirm);
27406
+ el.removeEventListener("navi_suggestion_list_clear", onClear);
27311
27407
  };
27312
27408
  }, [popover]);
27313
- useKeyboardShortcuts(popover ? noopRef : listRef, [{
27409
+ useKeyboardShortcuts(popover ? noopRef : ref, [{
27314
27410
  key: "arrowdown",
27315
- description: "Highlight next option",
27411
+ description: "Point to next suggestion",
27316
27412
  handler: () => navigate("down")
27317
27413
  }, {
27318
27414
  key: "arrowup",
27319
- description: "Highlight previous option",
27415
+ description: "Point to previous suggestion",
27320
27416
  handler: () => navigate("up")
27321
27417
  }, {
27322
27418
  key: "home",
27323
- description: "Highlight first option",
27419
+ description: "Point to first suggestion",
27324
27420
  handler: () => navigate("first")
27325
27421
  }, {
27326
27422
  key: "end",
27327
- description: "Highlight last option",
27423
+ description: "Point to last suggestion",
27328
27424
  handler: () => navigate("last")
27329
27425
  }, {
27330
27426
  key: "enter",
27331
- description: "Select highlighted option",
27427
+ description: "Confirm pointed suggestion",
27332
27428
  handler: () => {
27333
- const current = highlightedValueRef.current;
27429
+ const current = pointedValueRef.current;
27334
27430
  if (current === null) {
27335
27431
  return false;
27336
27432
  }
@@ -27339,87 +27435,91 @@ const OptionList = ({
27339
27435
  }
27340
27436
  }, {
27341
27437
  key: "escape",
27342
- description: "Clear highlighted option",
27438
+ description: "Clear pointed suggestion",
27343
27439
  handler: () => {
27344
- setHighlightedValue(null);
27440
+ setPointedValue(null);
27345
27441
  return true;
27346
27442
  }
27347
27443
  }]);
27348
- const optionListContext = {
27349
- highlightedValue,
27350
- setHighlightedValue,
27444
+ const suggestionListContext = {
27445
+ pointedValue,
27446
+ setPointedValue,
27351
27447
  onSelect: effectiveOnChange
27352
27448
  };
27353
27449
  return jsx(Box, {
27354
27450
  as: "ul",
27355
- ref: listRef,
27451
+ ref: ref,
27356
27452
  id: id,
27357
27453
  role: "listbox",
27358
27454
  tabIndex: popover ? -1 : 0,
27359
27455
  popover: popover ? "manual" : undefined,
27360
27456
  ...rest,
27361
- baseClassName: "navi_option_list",
27362
- children: jsx(OptionListContext.Provider, {
27363
- value: optionListContext,
27457
+ baseClassName: "navi_suggestion_list",
27458
+ styleCSSVars: SuggestionListStyleCSSVars,
27459
+ children: jsx(SuggestionListContext.Provider, {
27460
+ value: suggestionListContext,
27364
27461
  children: jsx(ItemTrackerProvider, {
27365
27462
  children: children
27366
27463
  })
27367
27464
  })
27368
27465
  });
27369
27466
  };
27370
- const OPTION_PSEUDO_CLASSES = [":-navi-highlighted", ":-navi-selected"];
27371
- const Option = ({
27467
+ const SUGGESTION_PSEUDO_CLASSES = [":-navi-pointed", ":-navi-selected"];
27468
+ const SUGGESTION_PSEUDO_ELEMENTS = ["::highlight"];
27469
+ const Suggestion = ({
27372
27470
  value,
27373
27471
  selected,
27374
27472
  hidden,
27375
27473
  children,
27376
27474
  ...rest
27377
27475
  }) => {
27378
- import.meta.css = [css$g, "@jsenv/navi/src/field/option_list.jsx"];
27379
- const optionId = useId();
27380
- const id = rest.id || optionId;
27381
- useTrackOption({
27476
+ import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
27477
+ const suggestionId = useId();
27478
+ const id = rest.id || suggestionId;
27479
+ useTrackSuggestion({
27382
27480
  value,
27383
- optionId: id,
27481
+ suggestionId: id,
27384
27482
  hidden
27385
27483
  });
27386
27484
  const {
27387
- highlightedValue,
27388
- setHighlightedValue,
27485
+ pointedValue,
27486
+ setPointedValue,
27389
27487
  onSelect
27390
- } = useContext(OptionListContext);
27391
- const isHighlighted = highlightedValue === value;
27392
- const optionRef = useRef(null);
27488
+ } = useContext(SuggestionListContext);
27489
+ const isPointed = pointedValue === value;
27490
+ const suggestionRef = useRef(null);
27393
27491
  useEffect(() => {
27394
- const optionEl = optionRef.current;
27395
- if (isHighlighted && optionEl) {
27396
- optionEl.scrollIntoView({
27492
+ const suggestionEl = suggestionRef.current;
27493
+ if (isPointed && suggestionEl) {
27494
+ suggestionEl.scrollIntoView({
27397
27495
  block: "nearest"
27398
27496
  });
27399
27497
  }
27400
- }, [isHighlighted]);
27498
+ }, [isPointed]);
27401
27499
  return jsx(Box, {
27402
27500
  as: "li",
27403
- ref: optionRef,
27404
- baseClassName: "navi_option",
27405
- id: optionId,
27501
+ ref: suggestionRef,
27502
+ baseClassName: "navi_suggestion",
27503
+ id: suggestionId,
27406
27504
  role: "option",
27407
27505
  "aria-selected": selected,
27408
27506
  "aria-hidden": hidden ? true : undefined,
27409
27507
  hidden: hidden,
27410
27508
  basePseudoState: {
27411
- ":-navi-highlighted": isHighlighted,
27509
+ ":-navi-pointed": isPointed,
27412
27510
  ":-navi-selected": selected
27413
27511
  },
27414
- pseudoClasses: OPTION_PSEUDO_CLASSES,
27512
+ pseudoClasses: SUGGESTION_PSEUDO_CLASSES,
27513
+ pseudoElements: SUGGESTION_PSEUDO_ELEMENTS,
27514
+ styleCSSVars: SuggestionStyleCSSVars,
27415
27515
  onMouseEnter: () => {
27416
27516
  if (!hidden) {
27417
- setHighlightedValue(value);
27517
+ setPointedValue(value);
27418
27518
  }
27419
27519
  },
27420
27520
  onMouseLeave: () => {
27421
27521
  if (!hidden) {
27422
- setHighlightedValue(null);
27522
+ setPointedValue(null);
27423
27523
  }
27424
27524
  },
27425
27525
  onMouseDown: e => {
@@ -27432,6 +27532,39 @@ const Option = ({
27432
27532
  children: children
27433
27533
  });
27434
27534
  };
27535
+ const SuggestionGroup = ({
27536
+ label,
27537
+ children,
27538
+ ...rest
27539
+ }) => {
27540
+ import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
27541
+ const groupId = useId();
27542
+ return jsxs("li", {
27543
+ role: "presentation",
27544
+ ...rest,
27545
+ children: [jsx("span", {
27546
+ id: groupId,
27547
+ role: "presentation",
27548
+ "aria-hidden": "true",
27549
+ style: {
27550
+ display: "contents"
27551
+ },
27552
+ children: typeof label === "string" ? jsx("span", {
27553
+ className: "navi_suggestion_group_label",
27554
+ children: label
27555
+ }) : label
27556
+ }), jsx("ul", {
27557
+ role: "group",
27558
+ "aria-labelledby": groupId,
27559
+ style: {
27560
+ margin: 0,
27561
+ padding: 0,
27562
+ listStyle: "none"
27563
+ },
27564
+ children: children
27565
+ })]
27566
+ });
27567
+ };
27435
27568
 
27436
27569
  const RadioList = props => {
27437
27570
  const uiStateController = useUIGroupStateController(props, "radio_list", {
@@ -33402,5 +33535,5 @@ const UserSvg = () => jsx("svg", {
33402
33535
  })
33403
33536
  });
33404
33537
 
33405
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, CloseSvg, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, Loading, MessageBox, Meter, Nav, Option, OptionList, Paragraph, Quantity, QuantityIntl, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, filterTableSelection, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDarkBackgroundAttribute, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
33538
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, CloseSvg, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, Loading, MessageBox, Meter, Nav, Paragraph, Quantity, QuantityIntl, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, SidePanel, StarSvg, Suggestion, SuggestionGroup, SuggestionList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, actionRunEffect, addCustomMessage, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createIntl, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, filterTableSelection, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, route, routeAction, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, syncOwnedResourceToSignals, syncResourceToSignals, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDarkBackgroundAttribute, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useOrderedColumns, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSidePanelClose, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
33406
33539
  //# sourceMappingURL=jsenv_navi.js.map