@jsenv/navi 0.24.2 → 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);
@@ -27112,69 +27107,84 @@ const createItemTracker = () => {
27112
27107
  return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
27113
27108
  };
27114
27109
 
27115
- installImportMetaCssBuild(import.meta);const [useOptionItemTrackerProvider, useTrackOption] = createItemTracker();
27110
+ installImportMetaCssBuild(import.meta);const [useSuggestionItemTrackerProvider, useTrackSuggestion] = createItemTracker();
27116
27111
 
27117
27112
  /**
27118
- * OptionList + Option: a composable accessible listbox.
27113
+ * SuggestionList + Suggestion: a composable accessible listbox.
27119
27114
  *
27120
27115
  * Usage:
27121
- * <OptionList id="my-list" value={selected} onChange={setSelected}>
27122
- * <Option value="a">Option A</Option>
27123
- * <Option value="b">Option B</Option>
27124
- * </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
27125
27123
  *
27126
- * CSS vars on .navi_option_list:
27127
- * --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
27128
27131
  *
27129
- * CSS vars on .navi_option:
27130
- * --padding, --color, --background-color, --font-weight
27131
- * --color-hover, --background-color-hover
27132
- * --color-highlighted, --background-color-highlighted
27133
- * --color-selected, --background-color-selected, --font-weight-selected
27134
- * --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
27135
27134
  */
27136
27135
 
27137
27136
  const css$g = /* css */`
27138
27137
  @layer navi {
27139
- .navi_option_list {
27140
- --border-radius: 4px;
27141
- --border-width: 1px;
27142
- --border-color: light-dark(#ccc, #555);
27143
- --background-color: light-dark(#fff, #1e1e1e);
27144
- --max-height: 220px;
27145
- }
27146
- .navi_option {
27147
- --padding: 8px 12px;
27148
- --color: inherit;
27149
- --background-color: transparent;
27150
- --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;
27151
27156
 
27152
27157
  /* Hover (mouse) */
27153
- --color-hover: var(--color);
27154
- --background-color-hover: light-dark(#f5f5f5, #2a2a2a);
27158
+ --suggestion-color-hover: var(--suggestion-color);
27159
+ --suggestion-background-color-hover: light-dark(#f5f5f5, #2a2a2a);
27155
27160
 
27156
- /* Highlighted (keyboard navigation cursor) */
27157
- --color-highlighted: var(--color);
27158
- --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);
27159
27164
 
27160
27165
  /* Selected */
27161
- --color-selected: light-dark(#1a73e8, #7baaf7);
27162
- --background-color-selected: light-dark(#e8f0fe, #1c3a6e);
27163
- --font-weight-selected: 500;
27164
-
27165
- /* Highlighted + selected */
27166
- --color-highlighted-selected: var(--color-selected);
27167
- --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
+ );
27168
27178
  }
27169
27179
  }
27170
27180
 
27171
- .navi_option_list {
27172
- --x-border-radius: var(--border-radius);
27173
- --x-border-width: var(--border-width);
27174
- --x-border-color: var(--border-color);
27175
- --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);
27176
27186
  box-sizing: border-box;
27177
- max-height: var(--max-height);
27187
+ max-height: var(--suggestion-list-max-height);
27178
27188
 
27179
27189
  margin: 0;
27180
27190
  padding: 0;
@@ -27194,12 +27204,16 @@ const css$g = /* css */`
27194
27204
  border: none;
27195
27205
  }
27196
27206
  }
27197
- .navi_option {
27198
- --x-color: var(--color);
27199
- --x-background-color: var(--background-color);
27200
- --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);
27201
27215
 
27202
- padding: var(--padding);
27216
+ padding: var(--suggestion-padding);
27203
27217
  color: var(--x-color);
27204
27218
  font-weight: var(--x-font-weight);
27205
27219
  background-color: var(--x-background-color);
@@ -27207,49 +27221,134 @@ const css$g = /* css */`
27207
27221
  user-select: none;
27208
27222
 
27209
27223
  &:hover {
27210
- --x-color: var(--color-hover);
27211
- --x-background-color: var(--background-color-hover);
27224
+ --x-color: var(--suggestion-color-hover);
27225
+ --x-background-color: var(--suggestion-background-color-hover);
27212
27226
  }
27213
27227
 
27214
- &[data-highlighted] {
27215
- --x-color: var(--color-highlighted);
27216
- --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);
27217
27231
  }
27218
27232
 
27219
27233
  &[data-selected] {
27220
- --x-color: var(--color-selected);
27221
- --x-background-color: var(--background-color-selected);
27222
- --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);
27223
27237
  }
27224
27238
 
27225
- &[data-highlighted][data-selected] {
27226
- --x-color: var(--color-highlighted-selected);
27227
- --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);
27228
27242
  }
27229
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
+ }
27230
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
+ };
27231
27289
 
27232
27290
  /**
27233
27291
  * Context OptionList provides downward to its Option children.
27234
27292
  */
27235
- const OptionListContext = createContext(null);
27236
- const OptionList = ({
27293
+ const SuggestionListContext = createContext(null);
27294
+ const SuggestionList = ({
27237
27295
  popover,
27238
27296
  onChange: onChangeProp,
27297
+ highlight,
27239
27298
  children,
27240
27299
  ...rest
27241
27300
  }) => {
27242
- import.meta.css = [css$g, "@jsenv/navi/src/field/option_list.jsx"];
27243
- const ItemTrackerProvider = useOptionItemTrackerProvider();
27244
- const [highlightedValue, setHighlightedValue] = useState(null);
27245
- const highlightedValueRef = useRef(null);
27246
- 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;
27247
27306
  const ownId = useId();
27248
27307
  const id = rest.id ?? ownId;
27249
- 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]);
27250
27349
  const effectiveOnChange = popover ? value => {
27251
27350
  onChangeProp?.(value);
27252
- listRef.current?.dispatchEvent(new CustomEvent("combobox-selected", {
27351
+ ref.current?.dispatchEvent(new CustomEvent("navi_suggestion_list_selected", {
27253
27352
  detail: {
27254
27353
  value
27255
27354
  },
@@ -27263,17 +27362,17 @@ const OptionList = ({
27263
27362
  if (values.length === 0) {
27264
27363
  return false;
27265
27364
  }
27266
- const current = highlightedValueRef.current;
27365
+ const current = pointedValueRef.current;
27267
27366
  if (direction === "down") {
27268
27367
  const idx = current === null ? -1 : values.indexOf(current);
27269
- setHighlightedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
27368
+ setPointedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
27270
27369
  } else if (direction === "up") {
27271
27370
  const idx = current === null ? -1 : values.indexOf(current);
27272
- setHighlightedValue(values[idx > 0 ? idx - 1 : 0]);
27371
+ setPointedValue(values[idx > 0 ? idx - 1 : 0]);
27273
27372
  } else if (direction === "first") {
27274
- setHighlightedValue(values[0]);
27373
+ setPointedValue(values[0]);
27275
27374
  } else if (direction === "last") {
27276
- setHighlightedValue(values[values.length - 1]);
27375
+ setPointedValue(values[values.length - 1]);
27277
27376
  }
27278
27377
  return true;
27279
27378
  };
@@ -27281,53 +27380,53 @@ const OptionList = ({
27281
27380
  // Listen for commands dispatched by a linked Input (combobox mode)
27282
27381
  const noopRef = useRef(null);
27283
27382
  useEffect(() => {
27284
- if (!popover || !listRef.current) {
27383
+ if (!popover || !ref.current) {
27285
27384
  return undefined;
27286
27385
  }
27287
- const el = listRef.current;
27386
+ const el = ref.current;
27288
27387
  const onNavigate = e => {
27289
27388
  navigate(e.detail.direction);
27290
27389
  };
27291
27390
  const onConfirm = e => {
27292
- const current = highlightedValueRef.current;
27391
+ const current = pointedValueRef.current;
27293
27392
  if (current !== null) {
27294
27393
  onChangeRef.current?.(current);
27295
27394
  e.preventDefault();
27296
27395
  }
27297
27396
  };
27298
27397
  const onClear = () => {
27299
- setHighlightedValue(null);
27398
+ setPointedValue(null);
27300
27399
  };
27301
- el.addEventListener("combobox-navigate", onNavigate);
27302
- el.addEventListener("combobox-confirm", onConfirm);
27303
- 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);
27304
27403
  return () => {
27305
- el.removeEventListener("combobox-navigate", onNavigate);
27306
- el.removeEventListener("combobox-confirm", onConfirm);
27307
- 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);
27308
27407
  };
27309
27408
  }, [popover]);
27310
- useKeyboardShortcuts(popover ? noopRef : listRef, [{
27409
+ useKeyboardShortcuts(popover ? noopRef : ref, [{
27311
27410
  key: "arrowdown",
27312
- description: "Highlight next option",
27411
+ description: "Point to next suggestion",
27313
27412
  handler: () => navigate("down")
27314
27413
  }, {
27315
27414
  key: "arrowup",
27316
- description: "Highlight previous option",
27415
+ description: "Point to previous suggestion",
27317
27416
  handler: () => navigate("up")
27318
27417
  }, {
27319
27418
  key: "home",
27320
- description: "Highlight first option",
27419
+ description: "Point to first suggestion",
27321
27420
  handler: () => navigate("first")
27322
27421
  }, {
27323
27422
  key: "end",
27324
- description: "Highlight last option",
27423
+ description: "Point to last suggestion",
27325
27424
  handler: () => navigate("last")
27326
27425
  }, {
27327
27426
  key: "enter",
27328
- description: "Select highlighted option",
27427
+ description: "Confirm pointed suggestion",
27329
27428
  handler: () => {
27330
- const current = highlightedValueRef.current;
27429
+ const current = pointedValueRef.current;
27331
27430
  if (current === null) {
27332
27431
  return false;
27333
27432
  }
@@ -27336,87 +27435,91 @@ const OptionList = ({
27336
27435
  }
27337
27436
  }, {
27338
27437
  key: "escape",
27339
- description: "Clear highlighted option",
27438
+ description: "Clear pointed suggestion",
27340
27439
  handler: () => {
27341
- setHighlightedValue(null);
27440
+ setPointedValue(null);
27342
27441
  return true;
27343
27442
  }
27344
27443
  }]);
27345
- const optionListContext = {
27346
- highlightedValue,
27347
- setHighlightedValue,
27444
+ const suggestionListContext = {
27445
+ pointedValue,
27446
+ setPointedValue,
27348
27447
  onSelect: effectiveOnChange
27349
27448
  };
27350
27449
  return jsx(Box, {
27351
27450
  as: "ul",
27352
- ref: listRef,
27451
+ ref: ref,
27353
27452
  id: id,
27354
27453
  role: "listbox",
27355
27454
  tabIndex: popover ? -1 : 0,
27356
27455
  popover: popover ? "manual" : undefined,
27357
27456
  ...rest,
27358
- baseClassName: "navi_option_list",
27359
- children: jsx(OptionListContext.Provider, {
27360
- value: optionListContext,
27457
+ baseClassName: "navi_suggestion_list",
27458
+ styleCSSVars: SuggestionListStyleCSSVars,
27459
+ children: jsx(SuggestionListContext.Provider, {
27460
+ value: suggestionListContext,
27361
27461
  children: jsx(ItemTrackerProvider, {
27362
27462
  children: children
27363
27463
  })
27364
27464
  })
27365
27465
  });
27366
27466
  };
27367
- const OPTION_PSEUDO_CLASSES = [":-navi-highlighted", ":-navi-selected"];
27368
- const Option = ({
27467
+ const SUGGESTION_PSEUDO_CLASSES = [":-navi-pointed", ":-navi-selected"];
27468
+ const SUGGESTION_PSEUDO_ELEMENTS = ["::highlight"];
27469
+ const Suggestion = ({
27369
27470
  value,
27370
27471
  selected,
27371
27472
  hidden,
27372
27473
  children,
27373
27474
  ...rest
27374
27475
  }) => {
27375
- import.meta.css = [css$g, "@jsenv/navi/src/field/option_list.jsx"];
27376
- const optionId = useId();
27377
- const id = rest.id || optionId;
27378
- 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({
27379
27480
  value,
27380
- optionId: id,
27481
+ suggestionId: id,
27381
27482
  hidden
27382
27483
  });
27383
27484
  const {
27384
- highlightedValue,
27385
- setHighlightedValue,
27485
+ pointedValue,
27486
+ setPointedValue,
27386
27487
  onSelect
27387
- } = useContext(OptionListContext);
27388
- const isHighlighted = highlightedValue === value;
27389
- const optionRef = useRef(null);
27488
+ } = useContext(SuggestionListContext);
27489
+ const isPointed = pointedValue === value;
27490
+ const suggestionRef = useRef(null);
27390
27491
  useEffect(() => {
27391
- const optionEl = optionRef.current;
27392
- if (isHighlighted && optionEl) {
27393
- optionEl.scrollIntoView({
27492
+ const suggestionEl = suggestionRef.current;
27493
+ if (isPointed && suggestionEl) {
27494
+ suggestionEl.scrollIntoView({
27394
27495
  block: "nearest"
27395
27496
  });
27396
27497
  }
27397
- }, [isHighlighted]);
27498
+ }, [isPointed]);
27398
27499
  return jsx(Box, {
27399
27500
  as: "li",
27400
- ref: optionRef,
27401
- baseClassName: "navi_option",
27402
- id: optionId,
27501
+ ref: suggestionRef,
27502
+ baseClassName: "navi_suggestion",
27503
+ id: suggestionId,
27403
27504
  role: "option",
27404
27505
  "aria-selected": selected,
27405
27506
  "aria-hidden": hidden ? true : undefined,
27406
27507
  hidden: hidden,
27407
27508
  basePseudoState: {
27408
- ":-navi-highlighted": isHighlighted,
27509
+ ":-navi-pointed": isPointed,
27409
27510
  ":-navi-selected": selected
27410
27511
  },
27411
- pseudoClasses: OPTION_PSEUDO_CLASSES,
27512
+ pseudoClasses: SUGGESTION_PSEUDO_CLASSES,
27513
+ pseudoElements: SUGGESTION_PSEUDO_ELEMENTS,
27514
+ styleCSSVars: SuggestionStyleCSSVars,
27412
27515
  onMouseEnter: () => {
27413
27516
  if (!hidden) {
27414
- setHighlightedValue(value);
27517
+ setPointedValue(value);
27415
27518
  }
27416
27519
  },
27417
27520
  onMouseLeave: () => {
27418
27521
  if (!hidden) {
27419
- setHighlightedValue(null);
27522
+ setPointedValue(null);
27420
27523
  }
27421
27524
  },
27422
27525
  onMouseDown: e => {
@@ -27429,6 +27532,39 @@ const Option = ({
27429
27532
  children: children
27430
27533
  });
27431
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
+ };
27432
27568
 
27433
27569
  const RadioList = props => {
27434
27570
  const uiStateController = useUIGroupStateController(props, "radio_list", {
@@ -33399,5 +33535,5 @@ const UserSvg = () => jsx("svg", {
33399
33535
  })
33400
33536
  });
33401
33537
 
33402
- 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 };
33403
33539
  //# sourceMappingURL=jsenv_navi.js.map