@jsenv/navi 0.24.2 → 0.25.1

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,49 @@ 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%" };
6309
- }
6310
- return { flexGrow: 1, flexBasis: "0%" }; // Grow horizontally in column
6301
+ if (inHorizontalFlexFlow) {
6302
+ // Parent is flex-x: grow as flex item
6303
+ return { flexGrow: 1, flexBasis: "0%" };
6311
6304
  }
6312
6305
  if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
6313
- return { alignSelf: "stretch" }; // Stretch to cross-axis width in flex-y
6306
+ return {
6307
+ alignSelf: "stretch",
6308
+ // Here flex grow is "useless" for the item itself
6309
+ // buuut it would allow children (hello ".navi_text_bold_wrapper")
6310
+ // to inherit expand behavior
6311
+ flexGrow: 1,
6312
+ };
6314
6313
  }
6315
- return { width: "100%" }; // Take full width outside flex
6314
+ // Can't use flexGrow parent is not flex-x
6315
+ return { width: "100%" };
6316
6316
  },
6317
- expandY: (value, { parentBoxFlow, boxFlow }) => {
6317
+ expandY: (value, { parentBoxFlow }) => {
6318
6318
  if (!value) {
6319
6319
  return null;
6320
6320
  }
6321
6321
  const inVerticalFlexFlow =
6322
6322
  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%" };
6333
- }
6334
- return { flexGrow: 1, flexBasis: "0%" }; // Grow vertically in row
6323
+ if (inVerticalFlexFlow) {
6324
+ // Parent is flex-y: grow as flex item
6325
+ return { flexGrow: 1, flexBasis: "0%" };
6335
6326
  }
6336
- if (inHorizontalFlexFlow) {
6337
- return { alignSelf: "stretch" }; // Stretch to cross-axis height in flex-x
6327
+ if (parentBoxFlow === "flex-x" || parentBoxFlow === "inline-flex-x") {
6328
+ return {
6329
+ alignSelf: "stretch",
6330
+ // Here flex grow is "useless" for the item itself
6331
+ // buuut it would allow children (hello ".navi_text_bold_wrapper")
6332
+ // to inherit expand behavior
6333
+ flexGrow: 1,
6334
+ };
6338
6335
  }
6339
- return { height: "100%" }; // Take full height outside flex
6336
+ // Can't use flexGrow parent is not flex-y
6337
+ return { height: "100%" };
6340
6338
  },
6341
6339
  shrinkX: (value) => {
6342
6340
  if (!value || value === "0") {
@@ -6751,7 +6749,6 @@ const negativeEntries = (map) => {
6751
6749
  return result;
6752
6750
  };
6753
6751
 
6754
-
6755
6752
  // Unified design scale using t-shirt sizes with rem units for accessibility.
6756
6753
  // This scale is used for spacing to create visual harmony
6757
6754
  // and consistent proportions throughout the design system.
@@ -7226,8 +7223,9 @@ const PSEUDO_CLASSES = {
7226
7223
  attribute: "data-invalid",
7227
7224
  test: (el) => el.matches(":invalid"),
7228
7225
  },
7229
- ":-navi-highlighted": {
7230
- attribute: "data-highlighted",
7226
+ "::highlight": {},
7227
+ ":-navi-pointed": {
7228
+ attribute: "data-pointed",
7231
7229
  },
7232
7230
  ":-navi-selected": {
7233
7231
  attribute: "data-selected",
@@ -17175,15 +17173,23 @@ const ONE_OF_CONSTRAINT = {
17175
17173
  if (allowedValues.has(fieldValue)) {
17176
17174
  return null;
17177
17175
  }
17176
+ const visibleOptions = listEl.querySelectorAll(
17177
+ "[role='option']:not([hidden])",
17178
+ );
17179
+ const isNoMatch = visibleOptions.length === 0;
17178
17180
  const message = field.getAttribute("data-one-of-message");
17179
- if (message) {
17180
- return message;
17181
+ const noMatchMessage = field.getAttribute("data-one-of-no-match-message");
17182
+ if (isNoMatch) {
17183
+ return (
17184
+ noMatchMessage || `Aucune suggestion ne correspond à votre saisie.`
17185
+ );
17181
17186
  }
17182
- return `Veuillez choisir une valeur parmi les suggestions.`;
17187
+ return message || `Veuillez choisir une valeur parmi les suggestions.`;
17183
17188
  },
17184
17189
  };
17185
17190
  CONSTRAINT_ATTRIBUTE_SET.add("data-one-of");
17186
17191
  CONSTRAINT_ATTRIBUTE_SET.add("data-one-of-message");
17192
+ CONSTRAINT_ATTRIBUTE_SET.add("data-one-of-no-match-message");
17187
17193
 
17188
17194
  const collectAllowedValues = (listEl) => {
17189
17195
  const values = new Set();
@@ -20409,6 +20415,7 @@ const css$v = /* css */`
20409
20415
  min-height: inherit;
20410
20416
  flex-grow: inherit;
20411
20417
  align-items: inherit;
20418
+ align-self: inherit;
20412
20419
  justify-content: inherit;
20413
20420
  gap: inherit;
20414
20421
  text-align: inherit;
@@ -25832,6 +25839,7 @@ const css$j = /* css */`
25832
25839
  display: inline-flex;
25833
25840
  margin: 0;
25834
25841
  padding: 0;
25842
+ align-items: center;
25835
25843
  justify-content: center;
25836
25844
  font-size: var(--font-size);
25837
25845
  background: none;
@@ -25987,8 +25995,8 @@ Object.assign(PSEUDO_CLASSES, {
25987
25995
  const InputPseudoElements = ["::-navi-loader"];
25988
25996
  const InputChildPropSet = new Set([...fieldPropSet]);
25989
25997
  const InputTextualBasic = props => {
25990
- if (props.combobox) {
25991
- return jsx(InputTextualCombobox, {
25998
+ if (props.suggestions) {
25999
+ return jsx(InputTextualWithSuggestions, {
25992
26000
  ...props
25993
26001
  });
25994
26002
  }
@@ -25996,8 +26004,8 @@ const InputTextualBasic = props => {
25996
26004
  ...props
25997
26005
  });
25998
26006
  };
25999
- const InputTextualCombobox = ({
26000
- combobox,
26007
+ const InputTextualWithSuggestions = ({
26008
+ suggestions,
26001
26009
  onInput,
26002
26010
  onFocus,
26003
26011
  onBlur,
@@ -26005,53 +26013,53 @@ const InputTextualCombobox = ({
26005
26013
  }) => {
26006
26014
  const defaultRef = useRef();
26007
26015
  const ref = rest.ref || defaultRef;
26008
- const [comboboxOpen, setComboboxOpen] = useState(false);
26009
- const comboboxOpenRef = useRef(false);
26010
- comboboxOpenRef.current = comboboxOpen;
26016
+ const [suggestionsOpen, setSuggestionsOpen] = useState(false);
26017
+ const suggestionsOpenRef = useRef(false);
26018
+ suggestionsOpenRef.current = suggestionsOpen;
26011
26019
  const showPopover = e => {
26012
- if (comboboxOpenRef.current) {
26020
+ if (suggestionsOpenRef.current) {
26013
26021
  return;
26014
26022
  }
26015
26023
  console.debug(`showPopover (e.type:${e.type})`);
26016
- const popoverEl = document.getElementById(combobox);
26024
+ const popoverEl = document.getElementById(suggestions);
26017
26025
  positionPopover();
26018
26026
  popoverEl.showPopover();
26019
- comboboxOpenRef.current = true;
26020
- setComboboxOpen(true);
26027
+ suggestionsOpenRef.current = true;
26028
+ setSuggestionsOpen(true);
26021
26029
  window.addEventListener("scroll", positionPopover, {
26022
26030
  capture: true,
26023
26031
  passive: true
26024
26032
  });
26025
26033
  };
26026
26034
  const hidePopover = e => {
26027
- if (!comboboxOpenRef.current) {
26035
+ if (!suggestionsOpenRef.current) {
26028
26036
  return;
26029
26037
  }
26030
26038
  console.debug(`hidePopover (e.type:${e.type})`);
26031
- comboboxOpenRef.current = false;
26032
- setComboboxOpen(false);
26039
+ suggestionsOpenRef.current = false;
26040
+ setSuggestionsOpen(false);
26033
26041
  window.removeEventListener("scroll", positionPopover, {
26034
26042
  capture: true
26035
26043
  });
26036
- const popoverEl = document.getElementById(combobox);
26044
+ const popoverEl = document.getElementById(suggestions);
26037
26045
  if (popoverEl) {
26038
- popoverEl.dispatchEvent(new CustomEvent("combobox-clear"));
26046
+ popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_clear"));
26039
26047
  popoverEl.hidePopover();
26040
26048
  }
26041
- setComboboxOpen(false);
26049
+ setSuggestionsOpen(false);
26042
26050
  };
26043
26051
  const positionPopover = () => {
26044
26052
  const input = ref.current;
26045
26053
  const rect = input.getBoundingClientRect();
26046
- const popoverEl = document.getElementById(combobox);
26054
+ const popoverEl = document.getElementById(suggestions);
26047
26055
  if (popoverEl) {
26048
26056
  popoverEl.style.top = `${rect.bottom + 2}px`;
26049
26057
  popoverEl.style.left = `${rect.left}px`;
26050
26058
  popoverEl.style.width = `${rect.width}px`;
26051
26059
  }
26052
26060
  };
26053
- const dispatchToOptionList = customEvent => {
26054
- const popoverEl = document.getElementById(combobox);
26061
+ const dispatchToSuggestionList = customEvent => {
26062
+ const popoverEl = document.getElementById(suggestions);
26055
26063
  if (!popoverEl) {
26056
26064
  return false;
26057
26065
  }
@@ -26060,14 +26068,14 @@ const InputTextualCombobox = ({
26060
26068
  };
26061
26069
  useKeyboardShortcuts(ref, [{
26062
26070
  key: "arrowdown",
26063
- description: "Open popover and highlight next option",
26071
+ description: "Open popover and point to next suggestion",
26064
26072
  handler: e => {
26065
26073
  showPopover(e);
26066
- const popoverEl = document.getElementById(combobox);
26074
+ const popoverEl = document.getElementById(suggestions);
26067
26075
  if (!popoverEl) {
26068
26076
  return false;
26069
26077
  }
26070
- popoverEl.dispatchEvent(new CustomEvent("combobox-navigate", {
26078
+ popoverEl.dispatchEvent(new CustomEvent("navi_suggestion_list_navigate", {
26071
26079
  detail: {
26072
26080
  direction: "down"
26073
26081
  }
@@ -26076,10 +26084,10 @@ const InputTextualCombobox = ({
26076
26084
  }
26077
26085
  }, {
26078
26086
  key: "arrowup",
26079
- description: "Open popover and highlight previous option",
26087
+ description: "Open popover and point to previous suggestion",
26080
26088
  handler: e => {
26081
26089
  showPopover(e);
26082
- return dispatchToOptionList(new CustomEvent("combobox-navigate", {
26090
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
26083
26091
  detail: {
26084
26092
  direction: "up"
26085
26093
  }
@@ -26087,12 +26095,12 @@ const InputTextualCombobox = ({
26087
26095
  }
26088
26096
  }, {
26089
26097
  key: "home",
26090
- description: "Highlight first option",
26098
+ description: "Point to first suggestion",
26091
26099
  handler: () => {
26092
- if (!comboboxOpenRef.current) {
26100
+ if (!suggestionsOpenRef.current) {
26093
26101
  return false;
26094
26102
  }
26095
- return dispatchToOptionList(new CustomEvent("combobox-navigate", {
26103
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
26096
26104
  detail: {
26097
26105
  direction: "first"
26098
26106
  }
@@ -26100,12 +26108,12 @@ const InputTextualCombobox = ({
26100
26108
  }
26101
26109
  }, {
26102
26110
  key: "end",
26103
- description: "Highlight last option",
26111
+ description: "Point to last suggestion",
26104
26112
  handler: () => {
26105
- if (!comboboxOpenRef.current) {
26113
+ if (!suggestionsOpenRef.current) {
26106
26114
  return false;
26107
26115
  }
26108
- return dispatchToOptionList(new CustomEvent("combobox-navigate", {
26116
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_navigate", {
26109
26117
  detail: {
26110
26118
  direction: "last"
26111
26119
  }
@@ -26113,12 +26121,12 @@ const InputTextualCombobox = ({
26113
26121
  }
26114
26122
  }, {
26115
26123
  key: "enter",
26116
- description: "Confirm highlighted option",
26124
+ description: "Confirm pointed suggestion",
26117
26125
  handler: () => {
26118
- if (!comboboxOpenRef.current) {
26126
+ if (!suggestionsOpenRef.current) {
26119
26127
  return false;
26120
26128
  }
26121
- return dispatchToOptionList(new CustomEvent("combobox-confirm", {
26129
+ return dispatchToSuggestionList(new CustomEvent("navi_suggestion_list_confirm", {
26122
26130
  cancelable: true
26123
26131
  }));
26124
26132
  }
@@ -26126,7 +26134,7 @@ const InputTextualCombobox = ({
26126
26134
  key: "escape",
26127
26135
  description: "Close popover",
26128
26136
  handler: e => {
26129
- if (!comboboxOpenRef.current) {
26137
+ if (!suggestionsOpenRef.current) {
26130
26138
  return false;
26131
26139
  }
26132
26140
  hidePopover(e);
@@ -26135,7 +26143,7 @@ const InputTextualCombobox = ({
26135
26143
  }]);
26136
26144
  useEffect(() => {
26137
26145
  const inputEl = ref.current;
26138
- const popoverEl = document.getElementById(combobox);
26146
+ const popoverEl = document.getElementById(suggestions);
26139
26147
  if (!popoverEl) {
26140
26148
  return undefined;
26141
26149
  }
@@ -26146,18 +26154,18 @@ const InputTextualCombobox = ({
26146
26154
  }));
26147
26155
  hidePopover(e);
26148
26156
  };
26149
- popoverEl.addEventListener("combobox-selected", onSelected);
26157
+ popoverEl.addEventListener("navi_suggestion_list_selected", onSelected);
26150
26158
  return () => {
26151
- popoverEl.removeEventListener("combobox-selected", onSelected);
26159
+ popoverEl.removeEventListener("navi_suggestion_list_selected", onSelected);
26152
26160
  };
26153
- }, [combobox]);
26161
+ }, [suggestions]);
26154
26162
  return jsx(InputTextualPlain, {
26155
26163
  ref: ref,
26156
26164
  role: "combobox",
26157
26165
  autoComplete: "off",
26158
- "aria-controls": combobox,
26166
+ "aria-controls": suggestions,
26159
26167
  "aria-haspopup": "listbox",
26160
- "aria-expanded": comboboxOpen,
26168
+ "aria-expanded": suggestionsOpen,
26161
26169
  "aria-autocomplete": "list",
26162
26170
  onnavi_callout_open: e => {
26163
26171
  hidePopover(e);
@@ -27112,69 +27120,84 @@ const createItemTracker = () => {
27112
27120
  return [useItemTrackerProvider, useTrackItem, useTrackedItem, useTrackedItems];
27113
27121
  };
27114
27122
 
27115
- installImportMetaCssBuild(import.meta);const [useOptionItemTrackerProvider, useTrackOption] = createItemTracker();
27123
+ installImportMetaCssBuild(import.meta);const [useSuggestionItemTrackerProvider, useTrackSuggestion] = createItemTracker();
27116
27124
 
27117
27125
  /**
27118
- * OptionList + Option: a composable accessible listbox.
27126
+ * SuggestionList + Suggestion: a composable accessible listbox.
27119
27127
  *
27120
27128
  * 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>
27129
+ * <SuggestionList id="my-list" value={selected} onChange={setSelected}>
27130
+ * <Suggestion value="a">Option A</Suggestion>
27131
+ * <Suggestion value="b">Option B</Suggestion>
27132
+ * </SuggestionList>
27125
27133
  *
27126
- * CSS vars on .navi_option_list:
27127
- * --border-radius, --border-width, --border-color, --background-color, --max-height
27134
+ * CSS vars on .navi_suggestion_list:
27135
+ * --suggestion-list-border-radius, --suggestion-list-border-width, --suggestion-list-border-color, --suggestion-list-background-color, --suggestion-list-max-height
27128
27136
  *
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
27137
+ * CSS vars on .navi_suggestion:
27138
+ * --suggestion-padding, --suggestion-color, --suggestion-background-color, --suggestion-font-weight
27139
+ * --suggestion-color-hover, --suggestion-background-color-hover
27140
+ * --suggestion-color-pointed, --suggestion-background-color-pointed
27141
+ * --suggestion-color-selected, --suggestion-background-color-selected, --suggestion-font-weight-selected
27142
+ * --suggestion-color-pointed-selected, --suggestion-background-color-pointed-selected
27143
+ * --suggestion-color-highlight, --suggestion-background-color-highlight
27144
+ *
27145
+ * CSS vars on .navi_suggestion_group_label:
27146
+ * --suggestion-group-label-padding, --suggestion-group-label-color, --suggestion-group-label-font-size, --suggestion-group-label-font-weight
27135
27147
  */
27136
27148
 
27137
27149
  const css$g = /* css */`
27138
27150
  @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;
27151
+ .navi_suggestion_list {
27152
+ --suggestion-list-border-radius: 4px;
27153
+ --suggestion-list-border-width: 1px;
27154
+ --suggestion-list-border-color: light-dark(#ccc, #555);
27155
+ --suggestion-list-background-color: light-dark(#fff, #1e1e1e);
27156
+ --suggestion-list-max-height: 220px;
27157
+ }
27158
+ .navi_suggestion_group_label {
27159
+ --suggestion-group-label-padding: 4px 12px 2px;
27160
+ --suggestion-group-label-color: light-dark(#888, #aaa);
27161
+ --suggestion-group-label-font-size: 0.75em;
27162
+ --suggestion-group-label-font-weight: 600;
27163
+ }
27164
+ .navi_suggestion {
27165
+ --suggestion-padding: 8px 12px;
27166
+ --suggestion-color: inherit;
27167
+ --suggestion-background-color: transparent;
27168
+ --suggestion-font-weight: inherit;
27151
27169
 
27152
27170
  /* Hover (mouse) */
27153
- --color-hover: var(--color);
27154
- --background-color-hover: light-dark(#f5f5f5, #2a2a2a);
27171
+ --suggestion-color-hover: var(--suggestion-color);
27172
+ --suggestion-background-color-hover: light-dark(#f5f5f5, #2a2a2a);
27155
27173
 
27156
- /* Highlighted (keyboard navigation cursor) */
27157
- --color-highlighted: var(--color);
27158
- --background-color-highlighted: light-dark(#e8f0fe, #1c3a6e);
27174
+ /* Pointed (keyboard navigation position) */
27175
+ --suggestion-color-pointed: var(--suggestion-color);
27176
+ --suggestion-background-color-pointed: light-dark(#e8f0fe, #1c3a6e);
27159
27177
 
27160
27178
  /* 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);
27179
+ --suggestion-color-selected: light-dark(#1a73e8, #7baaf7);
27180
+ --suggestion-background-color-selected: light-dark(#e8f0fe, #1c3a6e);
27181
+ --suggestion-font-weight-selected: 500;
27182
+
27183
+ /* Highlight (CSS Highlight API match) */
27184
+ --suggestion-color-highlight: inherit;
27185
+ --suggestion-background-color-highlight: #ffe066;
27186
+ --suggestion-color-pointed-selected: var(--suggestion-color-selected);
27187
+ --suggestion-background-color-pointed-selected: light-dark(
27188
+ #d2e3fc,
27189
+ #174ea6
27190
+ );
27168
27191
  }
27169
27192
  }
27170
27193
 
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);
27194
+ .navi_suggestion_list {
27195
+ --x-border-radius: var(--suggestion-list-border-radius);
27196
+ --x-border-width: var(--suggestion-list-border-width);
27197
+ --x-border-color: var(--suggestion-list-border-color);
27198
+ --x-background-color: var(--suggestion-list-background-color);
27176
27199
  box-sizing: border-box;
27177
- max-height: var(--max-height);
27200
+ max-height: var(--suggestion-list-max-height);
27178
27201
 
27179
27202
  margin: 0;
27180
27203
  padding: 0;
@@ -27194,12 +27217,16 @@ const css$g = /* css */`
27194
27217
  border: none;
27195
27218
  }
27196
27219
  }
27197
- .navi_option {
27198
- --x-color: var(--color);
27199
- --x-background-color: var(--background-color);
27200
- --x-font-weight: var(--font-weight);
27220
+ ::highlight(navi-suggestion-match) {
27221
+ color: var(--suggestion-color-highlight);
27222
+ background-color: var(--suggestion-background-color-highlight);
27223
+ }
27224
+ .navi_suggestion {
27225
+ --x-color: var(--suggestion-color);
27226
+ --x-background-color: var(--suggestion-background-color);
27227
+ --x-font-weight: var(--suggestion-font-weight);
27201
27228
 
27202
- padding: var(--padding);
27229
+ padding: var(--suggestion-padding);
27203
27230
  color: var(--x-color);
27204
27231
  font-weight: var(--x-font-weight);
27205
27232
  background-color: var(--x-background-color);
@@ -27207,49 +27234,134 @@ const css$g = /* css */`
27207
27234
  user-select: none;
27208
27235
 
27209
27236
  &:hover {
27210
- --x-color: var(--color-hover);
27211
- --x-background-color: var(--background-color-hover);
27237
+ --x-color: var(--suggestion-color-hover);
27238
+ --x-background-color: var(--suggestion-background-color-hover);
27212
27239
  }
27213
27240
 
27214
- &[data-highlighted] {
27215
- --x-color: var(--color-highlighted);
27216
- --x-background-color: var(--background-color-highlighted);
27241
+ &[data-pointed] {
27242
+ --x-color: var(--suggestion-color-pointed);
27243
+ --x-background-color: var(--suggestion-background-color-pointed);
27217
27244
  }
27218
27245
 
27219
27246
  &[data-selected] {
27220
- --x-color: var(--color-selected);
27221
- --x-background-color: var(--background-color-selected);
27222
- --x-font-weight: var(--font-weight-selected);
27247
+ --x-color: var(--suggestion-color-selected);
27248
+ --x-background-color: var(--suggestion-background-color-selected);
27249
+ --x-font-weight: var(--suggestion-font-weight-selected);
27223
27250
  }
27224
27251
 
27225
- &[data-highlighted][data-selected] {
27226
- --x-color: var(--color-highlighted-selected);
27227
- --x-background-color: var(--background-color-highlighted-selected);
27252
+ &[data-pointed][data-selected] {
27253
+ --x-color: var(--suggestion-color-pointed-selected);
27254
+ --x-background-color: var(--suggestion-background-color-pointed-selected);
27228
27255
  }
27229
27256
  }
27257
+ .navi_suggestion_group_label {
27258
+ position: sticky;
27259
+ top: 0;
27260
+ z-index: 1;
27261
+ display: block;
27262
+ padding: var(--suggestion-group-label-padding);
27263
+ color: var(--suggestion-group-label-color);
27264
+ font-weight: var(--suggestion-group-label-font-weight);
27265
+ font-size: var(--suggestion-group-label-font-size);
27266
+ text-transform: uppercase;
27267
+ letter-spacing: 0.05em;
27268
+ background-color: var(--suggestion-group-label-background-color);
27269
+ user-select: none;
27270
+ }
27230
27271
  `;
27272
+ const SuggestionListStyleCSSVars = {
27273
+ borderRadius: "--suggestion-list-border-radius",
27274
+ borderWidth: "--suggestion-list-border-width",
27275
+ borderColor: "--suggestion-list-border-color",
27276
+ backgroundColor: "--suggestion-list-background-color",
27277
+ maxHeight: "--suggestion-list-max-height"
27278
+ };
27279
+ const SuggestionStyleCSSVars = {
27280
+ "padding": "--suggestion-padding",
27281
+ "color": "--suggestion-color",
27282
+ "backgroundColor": "--suggestion-background-color",
27283
+ "fontWeight": "--suggestion-font-weight",
27284
+ ":-navi-pointed": {
27285
+ color: "--suggestion-color-pointed",
27286
+ backgroundColor: "--suggestion-background-color-pointed"
27287
+ },
27288
+ ":hover": {
27289
+ color: "--suggestion-color-hover",
27290
+ backgroundColor: "--suggestion-background-color-hover"
27291
+ },
27292
+ ":-navi-selected": {
27293
+ color: "--suggestion-color-selected",
27294
+ backgroundColor: "--suggestion-background-color-selected",
27295
+ fontWeight: "--suggestion-font-weight-selected"
27296
+ },
27297
+ "::highlight": {
27298
+ color: "--suggestion-color-highlight",
27299
+ backgroundColor: "--suggestion-background-color-highlight"
27300
+ }
27301
+ };
27231
27302
 
27232
27303
  /**
27233
27304
  * Context OptionList provides downward to its Option children.
27234
27305
  */
27235
- const OptionListContext = createContext(null);
27236
- const OptionList = ({
27306
+ const SuggestionListContext = createContext(null);
27307
+ const SuggestionList = ({
27237
27308
  popover,
27238
27309
  onChange: onChangeProp,
27310
+ highlight,
27239
27311
  children,
27240
27312
  ...rest
27241
27313
  }) => {
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;
27314
+ import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
27315
+ const ItemTrackerProvider = useSuggestionItemTrackerProvider();
27316
+ const [pointedValue, setPointedValue] = useState(null);
27317
+ const pointedValueRef = useRef(null);
27318
+ pointedValueRef.current = pointedValue;
27247
27319
  const ownId = useId();
27248
27320
  const id = rest.id ?? ownId;
27249
- const listRef = useRef(null);
27321
+ const defaultRef = useRef(null);
27322
+ const ref = rest.ref || defaultRef;
27323
+ useLayoutEffect(() => {
27324
+ if (!CSS.highlights) {
27325
+ return undefined;
27326
+ }
27327
+ if (!highlight) {
27328
+ CSS.highlights.delete("navi-suggestion-match");
27329
+ return undefined;
27330
+ }
27331
+ const listEl = ref.current;
27332
+ if (!listEl) {
27333
+ return undefined;
27334
+ }
27335
+ const ranges = [];
27336
+ const lowerHighlight = highlight.toLowerCase();
27337
+ for (const suggestionEl of listEl.querySelectorAll("[role='option']")) {
27338
+ const walker = document.createTreeWalker(suggestionEl, NodeFilter.SHOW_TEXT);
27339
+ let node;
27340
+ while (node = walker.nextNode()) {
27341
+ const text = node.textContent;
27342
+ const lowerText = text.toLowerCase();
27343
+ let index = lowerText.indexOf(lowerHighlight);
27344
+ while (index !== -1) {
27345
+ const range = new Range();
27346
+ range.setStart(node, index);
27347
+ range.setEnd(node, index + highlight.length);
27348
+ ranges.push(range);
27349
+ index = lowerText.indexOf(lowerHighlight, index + 1);
27350
+ }
27351
+ }
27352
+ }
27353
+ if (ranges.length === 0) {
27354
+ CSS.highlights.delete("navi-suggestion-match");
27355
+ } else {
27356
+ CSS.highlights.set("navi-suggestion-match", new Highlight(...ranges));
27357
+ }
27358
+ return () => {
27359
+ CSS.highlights.delete("navi-suggestion-match");
27360
+ };
27361
+ }, [highlight, children]);
27250
27362
  const effectiveOnChange = popover ? value => {
27251
27363
  onChangeProp?.(value);
27252
- listRef.current?.dispatchEvent(new CustomEvent("combobox-selected", {
27364
+ ref.current?.dispatchEvent(new CustomEvent("navi_suggestion_list_selected", {
27253
27365
  detail: {
27254
27366
  value
27255
27367
  },
@@ -27263,17 +27375,17 @@ const OptionList = ({
27263
27375
  if (values.length === 0) {
27264
27376
  return false;
27265
27377
  }
27266
- const current = highlightedValueRef.current;
27378
+ const current = pointedValueRef.current;
27267
27379
  if (direction === "down") {
27268
27380
  const idx = current === null ? -1 : values.indexOf(current);
27269
- setHighlightedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
27381
+ setPointedValue(values[idx < values.length - 1 ? idx + 1 : idx]);
27270
27382
  } else if (direction === "up") {
27271
27383
  const idx = current === null ? -1 : values.indexOf(current);
27272
- setHighlightedValue(values[idx > 0 ? idx - 1 : 0]);
27384
+ setPointedValue(values[idx > 0 ? idx - 1 : 0]);
27273
27385
  } else if (direction === "first") {
27274
- setHighlightedValue(values[0]);
27386
+ setPointedValue(values[0]);
27275
27387
  } else if (direction === "last") {
27276
- setHighlightedValue(values[values.length - 1]);
27388
+ setPointedValue(values[values.length - 1]);
27277
27389
  }
27278
27390
  return true;
27279
27391
  };
@@ -27281,53 +27393,53 @@ const OptionList = ({
27281
27393
  // Listen for commands dispatched by a linked Input (combobox mode)
27282
27394
  const noopRef = useRef(null);
27283
27395
  useEffect(() => {
27284
- if (!popover || !listRef.current) {
27396
+ if (!popover || !ref.current) {
27285
27397
  return undefined;
27286
27398
  }
27287
- const el = listRef.current;
27399
+ const el = ref.current;
27288
27400
  const onNavigate = e => {
27289
27401
  navigate(e.detail.direction);
27290
27402
  };
27291
27403
  const onConfirm = e => {
27292
- const current = highlightedValueRef.current;
27404
+ const current = pointedValueRef.current;
27293
27405
  if (current !== null) {
27294
27406
  onChangeRef.current?.(current);
27295
27407
  e.preventDefault();
27296
27408
  }
27297
27409
  };
27298
27410
  const onClear = () => {
27299
- setHighlightedValue(null);
27411
+ setPointedValue(null);
27300
27412
  };
27301
- el.addEventListener("combobox-navigate", onNavigate);
27302
- el.addEventListener("combobox-confirm", onConfirm);
27303
- el.addEventListener("combobox-clear", onClear);
27413
+ el.addEventListener("navi_suggestion_list_navigate", onNavigate);
27414
+ el.addEventListener("navi_suggestion_list_confirm", onConfirm);
27415
+ el.addEventListener("navi_suggestion_list_clear", onClear);
27304
27416
  return () => {
27305
- el.removeEventListener("combobox-navigate", onNavigate);
27306
- el.removeEventListener("combobox-confirm", onConfirm);
27307
- el.removeEventListener("combobox-clear", onClear);
27417
+ el.removeEventListener("navi_suggestion_list_navigate", onNavigate);
27418
+ el.removeEventListener("navi_suggestion_list_confirm", onConfirm);
27419
+ el.removeEventListener("navi_suggestion_list_clear", onClear);
27308
27420
  };
27309
27421
  }, [popover]);
27310
- useKeyboardShortcuts(popover ? noopRef : listRef, [{
27422
+ useKeyboardShortcuts(popover ? noopRef : ref, [{
27311
27423
  key: "arrowdown",
27312
- description: "Highlight next option",
27424
+ description: "Point to next suggestion",
27313
27425
  handler: () => navigate("down")
27314
27426
  }, {
27315
27427
  key: "arrowup",
27316
- description: "Highlight previous option",
27428
+ description: "Point to previous suggestion",
27317
27429
  handler: () => navigate("up")
27318
27430
  }, {
27319
27431
  key: "home",
27320
- description: "Highlight first option",
27432
+ description: "Point to first suggestion",
27321
27433
  handler: () => navigate("first")
27322
27434
  }, {
27323
27435
  key: "end",
27324
- description: "Highlight last option",
27436
+ description: "Point to last suggestion",
27325
27437
  handler: () => navigate("last")
27326
27438
  }, {
27327
27439
  key: "enter",
27328
- description: "Select highlighted option",
27440
+ description: "Confirm pointed suggestion",
27329
27441
  handler: () => {
27330
- const current = highlightedValueRef.current;
27442
+ const current = pointedValueRef.current;
27331
27443
  if (current === null) {
27332
27444
  return false;
27333
27445
  }
@@ -27336,87 +27448,91 @@ const OptionList = ({
27336
27448
  }
27337
27449
  }, {
27338
27450
  key: "escape",
27339
- description: "Clear highlighted option",
27451
+ description: "Clear pointed suggestion",
27340
27452
  handler: () => {
27341
- setHighlightedValue(null);
27453
+ setPointedValue(null);
27342
27454
  return true;
27343
27455
  }
27344
27456
  }]);
27345
- const optionListContext = {
27346
- highlightedValue,
27347
- setHighlightedValue,
27457
+ const suggestionListContext = {
27458
+ pointedValue,
27459
+ setPointedValue,
27348
27460
  onSelect: effectiveOnChange
27349
27461
  };
27350
27462
  return jsx(Box, {
27351
27463
  as: "ul",
27352
- ref: listRef,
27464
+ ref: ref,
27353
27465
  id: id,
27354
27466
  role: "listbox",
27355
27467
  tabIndex: popover ? -1 : 0,
27356
27468
  popover: popover ? "manual" : undefined,
27357
27469
  ...rest,
27358
- baseClassName: "navi_option_list",
27359
- children: jsx(OptionListContext.Provider, {
27360
- value: optionListContext,
27470
+ baseClassName: "navi_suggestion_list",
27471
+ styleCSSVars: SuggestionListStyleCSSVars,
27472
+ children: jsx(SuggestionListContext.Provider, {
27473
+ value: suggestionListContext,
27361
27474
  children: jsx(ItemTrackerProvider, {
27362
27475
  children: children
27363
27476
  })
27364
27477
  })
27365
27478
  });
27366
27479
  };
27367
- const OPTION_PSEUDO_CLASSES = [":-navi-highlighted", ":-navi-selected"];
27368
- const Option = ({
27480
+ const SUGGESTION_PSEUDO_CLASSES = [":-navi-pointed", ":-navi-selected"];
27481
+ const SUGGESTION_PSEUDO_ELEMENTS = ["::highlight"];
27482
+ const Suggestion = ({
27369
27483
  value,
27370
27484
  selected,
27371
27485
  hidden,
27372
27486
  children,
27373
27487
  ...rest
27374
27488
  }) => {
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({
27489
+ import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
27490
+ const suggestionId = useId();
27491
+ const id = rest.id || suggestionId;
27492
+ useTrackSuggestion({
27379
27493
  value,
27380
- optionId: id,
27494
+ suggestionId: id,
27381
27495
  hidden
27382
27496
  });
27383
27497
  const {
27384
- highlightedValue,
27385
- setHighlightedValue,
27498
+ pointedValue,
27499
+ setPointedValue,
27386
27500
  onSelect
27387
- } = useContext(OptionListContext);
27388
- const isHighlighted = highlightedValue === value;
27389
- const optionRef = useRef(null);
27501
+ } = useContext(SuggestionListContext);
27502
+ const isPointed = pointedValue === value;
27503
+ const suggestionRef = useRef(null);
27390
27504
  useEffect(() => {
27391
- const optionEl = optionRef.current;
27392
- if (isHighlighted && optionEl) {
27393
- optionEl.scrollIntoView({
27505
+ const suggestionEl = suggestionRef.current;
27506
+ if (isPointed && suggestionEl) {
27507
+ suggestionEl.scrollIntoView({
27394
27508
  block: "nearest"
27395
27509
  });
27396
27510
  }
27397
- }, [isHighlighted]);
27511
+ }, [isPointed]);
27398
27512
  return jsx(Box, {
27399
27513
  as: "li",
27400
- ref: optionRef,
27401
- baseClassName: "navi_option",
27402
- id: optionId,
27514
+ ref: suggestionRef,
27515
+ baseClassName: "navi_suggestion",
27516
+ id: suggestionId,
27403
27517
  role: "option",
27404
27518
  "aria-selected": selected,
27405
27519
  "aria-hidden": hidden ? true : undefined,
27406
27520
  hidden: hidden,
27407
27521
  basePseudoState: {
27408
- ":-navi-highlighted": isHighlighted,
27522
+ ":-navi-pointed": isPointed,
27409
27523
  ":-navi-selected": selected
27410
27524
  },
27411
- pseudoClasses: OPTION_PSEUDO_CLASSES,
27525
+ pseudoClasses: SUGGESTION_PSEUDO_CLASSES,
27526
+ pseudoElements: SUGGESTION_PSEUDO_ELEMENTS,
27527
+ styleCSSVars: SuggestionStyleCSSVars,
27412
27528
  onMouseEnter: () => {
27413
27529
  if (!hidden) {
27414
- setHighlightedValue(value);
27530
+ setPointedValue(value);
27415
27531
  }
27416
27532
  },
27417
27533
  onMouseLeave: () => {
27418
27534
  if (!hidden) {
27419
- setHighlightedValue(null);
27535
+ setPointedValue(null);
27420
27536
  }
27421
27537
  },
27422
27538
  onMouseDown: e => {
@@ -27429,6 +27545,39 @@ const Option = ({
27429
27545
  children: children
27430
27546
  });
27431
27547
  };
27548
+ const SuggestionGroup = ({
27549
+ label,
27550
+ children,
27551
+ ...rest
27552
+ }) => {
27553
+ import.meta.css = [css$g, "@jsenv/navi/src/field/suggestion_list.jsx"];
27554
+ const groupId = useId();
27555
+ return jsxs("li", {
27556
+ role: "presentation",
27557
+ ...rest,
27558
+ children: [jsx("span", {
27559
+ id: groupId,
27560
+ role: "presentation",
27561
+ "aria-hidden": "true",
27562
+ style: {
27563
+ display: "contents"
27564
+ },
27565
+ children: typeof label === "string" ? jsx("span", {
27566
+ className: "navi_suggestion_group_label",
27567
+ children: label
27568
+ }) : label
27569
+ }), jsx("ul", {
27570
+ role: "group",
27571
+ "aria-labelledby": groupId,
27572
+ style: {
27573
+ margin: 0,
27574
+ padding: 0,
27575
+ listStyle: "none"
27576
+ },
27577
+ children: children
27578
+ })]
27579
+ });
27580
+ };
27432
27581
 
27433
27582
  const RadioList = props => {
27434
27583
  const uiStateController = useUIGroupStateController(props, "radio_list", {
@@ -33399,5 +33548,5 @@ const UserSvg = () => jsx("svg", {
33399
33548
  })
33400
33549
  });
33401
33550
 
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 };
33551
+ 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
33552
  //# sourceMappingURL=jsenv_navi.js.map