@wordpress/components 32.5.0 → 32.6.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.
Files changed (282) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +18 -4
  4. package/build/alignment-matrix-control/cell.cjs +3 -3
  5. package/build/alignment-matrix-control/cell.cjs.map +2 -2
  6. package/build/alignment-matrix-control/index.cjs +3 -3
  7. package/build/alignment-matrix-control/index.cjs.map +2 -2
  8. package/build/autocomplete/autocompleter-ui.cjs +75 -79
  9. package/build/autocomplete/autocompleter-ui.cjs.map +2 -2
  10. package/build/autocomplete/get-autocomplete-match.cjs +91 -0
  11. package/build/autocomplete/get-autocomplete-match.cjs.map +7 -0
  12. package/build/autocomplete/index.cjs +104 -107
  13. package/build/autocomplete/index.cjs.map +3 -3
  14. package/build/box-control/index.cjs +0 -8
  15. package/build/box-control/index.cjs.map +2 -2
  16. package/build/box-control/utils.cjs +1 -10
  17. package/build/box-control/utils.cjs.map +2 -2
  18. package/build/calendar/utils/use-localization-props.cjs +3 -2
  19. package/build/calendar/utils/use-localization-props.cjs.map +2 -2
  20. package/build/custom-gradient-picker/index.cjs.map +2 -2
  21. package/build/custom-select-control/index.cjs.map +3 -3
  22. package/build/custom-select-control-v2/custom-select.cjs +2 -2
  23. package/build/custom-select-control-v2/custom-select.cjs.map +2 -2
  24. package/build/custom-select-control-v2/index.cjs.map +3 -3
  25. package/build/date-time/{date → date-picker}/index.cjs +6 -6
  26. package/build/date-time/{date → date-picker}/index.cjs.map +2 -2
  27. package/build/date-time/{date → date-picker}/styles.cjs +17 -17
  28. package/build/date-time/{date → date-picker}/styles.cjs.map +2 -2
  29. package/build/date-time/{date → date-picker}/use-lilius/index.cjs +1 -1
  30. package/build/date-time/{date → date-picker}/use-lilius/index.cjs.map +1 -1
  31. package/build/date-time/date-time/index.cjs +6 -6
  32. package/build/date-time/date-time/index.cjs.map +2 -2
  33. package/build/date-time/index.cjs +4 -4
  34. package/build/date-time/index.cjs.map +2 -2
  35. package/build/date-time/{time → time-picker}/index.cjs +6 -6
  36. package/build/date-time/time-picker/index.cjs.map +7 -0
  37. package/build/date-time/{time → time-picker}/styles.cjs +21 -21
  38. package/build/date-time/{time → time-picker}/styles.cjs.map +2 -2
  39. package/build/date-time/{time → time-picker}/time-input/index.cjs +1 -1
  40. package/build/date-time/{time → time-picker}/time-input/index.cjs.map +1 -1
  41. package/build/date-time/{time → time-picker}/timezone.cjs +1 -1
  42. package/build/date-time/{time → time-picker}/timezone.cjs.map +1 -1
  43. package/build/modal/index.cjs.map +2 -2
  44. package/build/palette-edit/index.cjs.map +2 -2
  45. package/build/radio-control/index.cjs +2 -0
  46. package/build/radio-control/index.cjs.map +2 -2
  47. package/build/sandbox/index.cjs +127 -3
  48. package/build/sandbox/index.cjs.map +2 -2
  49. package/build/textarea-control/styles/textarea-control-styles.cjs +3 -3
  50. package/build/textarea-control/styles/textarea-control-styles.cjs.map +2 -2
  51. package/build/validated-form-controls/control-with-error.cjs +12 -8
  52. package/build/validated-form-controls/control-with-error.cjs.map +2 -2
  53. package/build-module/alignment-matrix-control/cell.mjs +3 -3
  54. package/build-module/alignment-matrix-control/cell.mjs.map +2 -2
  55. package/build-module/alignment-matrix-control/index.mjs +3 -3
  56. package/build-module/alignment-matrix-control/index.mjs.map +2 -2
  57. package/build-module/autocomplete/autocompleter-ui.mjs +74 -78
  58. package/build-module/autocomplete/autocompleter-ui.mjs.map +2 -2
  59. package/build-module/autocomplete/get-autocomplete-match.mjs +56 -0
  60. package/build-module/autocomplete/get-autocomplete-match.mjs.map +7 -0
  61. package/build-module/autocomplete/index.mjs +103 -107
  62. package/build-module/autocomplete/index.mjs.map +3 -3
  63. package/build-module/box-control/index.mjs +1 -9
  64. package/build-module/box-control/index.mjs.map +2 -2
  65. package/build-module/box-control/utils.mjs +1 -9
  66. package/build-module/box-control/utils.mjs.map +2 -2
  67. package/build-module/calendar/utils/use-localization-props.mjs +3 -2
  68. package/build-module/calendar/utils/use-localization-props.mjs.map +2 -2
  69. package/build-module/custom-gradient-picker/index.mjs.map +2 -2
  70. package/build-module/custom-select-control/index.mjs +2 -2
  71. package/build-module/custom-select-control/index.mjs.map +2 -2
  72. package/build-module/custom-select-control-v2/custom-select.mjs +2 -2
  73. package/build-module/custom-select-control-v2/custom-select.mjs.map +2 -2
  74. package/build-module/custom-select-control-v2/index.mjs +2 -2
  75. package/build-module/custom-select-control-v2/index.mjs.map +2 -2
  76. package/build-module/date-time/{date → date-picker}/index.mjs +3 -3
  77. package/build-module/date-time/{date → date-picker}/index.mjs.map +2 -2
  78. package/build-module/date-time/{date → date-picker}/styles.mjs +17 -17
  79. package/build-module/date-time/{date → date-picker}/styles.mjs.map +2 -2
  80. package/build-module/date-time/{date → date-picker}/use-lilius/index.mjs +1 -1
  81. package/build-module/date-time/{date → date-picker}/use-lilius/index.mjs.map +1 -1
  82. package/build-module/date-time/date-time/index.mjs +2 -2
  83. package/build-module/date-time/date-time/index.mjs.map +1 -1
  84. package/build-module/date-time/index.mjs +2 -2
  85. package/build-module/date-time/index.mjs.map +1 -1
  86. package/build-module/date-time/{time → time-picker}/index.mjs +3 -3
  87. package/build-module/date-time/time-picker/index.mjs.map +7 -0
  88. package/build-module/date-time/{time → time-picker}/styles.mjs +21 -21
  89. package/build-module/date-time/{time → time-picker}/styles.mjs.map +2 -2
  90. package/build-module/date-time/{time → time-picker}/time-input/index.mjs +1 -1
  91. package/build-module/date-time/{time → time-picker}/time-input/index.mjs.map +1 -1
  92. package/build-module/date-time/{time → time-picker}/timezone.mjs +1 -1
  93. package/build-module/date-time/{time → time-picker}/timezone.mjs.map +1 -1
  94. package/build-module/modal/index.mjs.map +2 -2
  95. package/build-module/palette-edit/index.mjs.map +2 -2
  96. package/build-module/radio-control/index.mjs +2 -0
  97. package/build-module/radio-control/index.mjs.map +2 -2
  98. package/build-module/sandbox/index.mjs +128 -4
  99. package/build-module/sandbox/index.mjs.map +2 -2
  100. package/build-module/textarea-control/styles/textarea-control-styles.mjs +3 -3
  101. package/build-module/textarea-control/styles/textarea-control-styles.mjs.map +2 -2
  102. package/build-module/validated-form-controls/control-with-error.mjs +12 -8
  103. package/build-module/validated-form-controls/control-with-error.mjs.map +2 -2
  104. package/build-style/style-rtl.css +83 -26
  105. package/build-style/style.css +83 -26
  106. package/build-types/autocomplete/autocompleter-ui.d.ts +2 -2
  107. package/build-types/autocomplete/autocompleter-ui.d.ts.map +1 -1
  108. package/build-types/autocomplete/get-autocomplete-match.d.ts +11 -0
  109. package/build-types/autocomplete/get-autocomplete-match.d.ts.map +1 -0
  110. package/build-types/autocomplete/index.d.ts +8 -0
  111. package/build-types/autocomplete/index.d.ts.map +1 -1
  112. package/build-types/autocomplete/test/get-autocomplete-match.d.ts +2 -0
  113. package/build-types/autocomplete/test/get-autocomplete-match.d.ts.map +1 -0
  114. package/build-types/autocomplete/types.d.ts +23 -9
  115. package/build-types/autocomplete/types.d.ts.map +1 -1
  116. package/build-types/box-control/index.d.ts.map +1 -1
  117. package/build-types/box-control/utils.d.ts +7 -16
  118. package/build-types/box-control/utils.d.ts.map +1 -1
  119. package/build-types/button/stories/index.story.d.ts +0 -1
  120. package/build-types/button/stories/index.story.d.ts.map +1 -1
  121. package/build-types/calendar/utils/use-localization-props.d.ts +3 -3
  122. package/build-types/calendar/utils/use-localization-props.d.ts.map +1 -1
  123. package/build-types/checkbox-control/types.d.ts +4 -0
  124. package/build-types/checkbox-control/types.d.ts.map +1 -1
  125. package/build-types/custom-gradient-picker/constants.d.ts +2 -2
  126. package/build-types/custom-gradient-picker/index.d.ts.map +1 -1
  127. package/build-types/custom-select-control-v2/custom-select.d.ts +3 -3
  128. package/build-types/custom-select-control-v2/custom-select.d.ts.map +1 -1
  129. package/build-types/custom-select-control-v2/types.d.ts +1 -1
  130. package/build-types/custom-select-control-v2/types.d.ts.map +1 -1
  131. package/build-types/date-time/date-picker/index.d.ts.map +1 -0
  132. package/build-types/date-time/date-picker/styles.d.ts.map +1 -0
  133. package/build-types/date-time/date-picker/test/index.d.ts.map +1 -0
  134. package/build-types/date-time/date-picker/test/use-lilius.d.ts.map +1 -0
  135. package/build-types/date-time/date-picker/use-lilius/index.d.ts.map +1 -0
  136. package/build-types/date-time/date-time/index.d.ts +2 -2
  137. package/build-types/date-time/date-time/index.d.ts.map +1 -1
  138. package/build-types/date-time/index.d.ts +2 -2
  139. package/build-types/date-time/index.d.ts.map +1 -1
  140. package/build-types/date-time/stories/date.story.d.ts +1 -1
  141. package/build-types/date-time/stories/date.story.d.ts.map +1 -1
  142. package/build-types/date-time/stories/time.story.d.ts +1 -1
  143. package/build-types/date-time/stories/time.story.d.ts.map +1 -1
  144. package/build-types/date-time/{time → time-picker}/index.d.ts +1 -1
  145. package/build-types/date-time/time-picker/index.d.ts.map +1 -0
  146. package/build-types/date-time/time-picker/styles.d.ts.map +1 -0
  147. package/build-types/date-time/time-picker/test/index.d.ts.map +1 -0
  148. package/build-types/date-time/time-picker/time-input/index.d.ts.map +1 -0
  149. package/build-types/date-time/time-picker/time-input/test/index.d.ts.map +1 -0
  150. package/build-types/date-time/time-picker/timezone.d.ts.map +1 -0
  151. package/build-types/date-time/types.d.ts +1 -1
  152. package/build-types/date-time/types.d.ts.map +1 -1
  153. package/build-types/font-size-picker/constants.d.ts +2 -2
  154. package/build-types/font-size-picker/constants.d.ts.map +1 -1
  155. package/build-types/modal/index.d.ts.map +1 -1
  156. package/build-types/palette-edit/index.d.ts +1 -1
  157. package/build-types/palette-edit/index.d.ts.map +1 -1
  158. package/build-types/radio-control/index.d.ts.map +1 -1
  159. package/build-types/radio-control/types.d.ts +6 -0
  160. package/build-types/radio-control/types.d.ts.map +1 -1
  161. package/build-types/sandbox/index.d.ts +1 -1
  162. package/build-types/sandbox/index.d.ts.map +1 -1
  163. package/build-types/sandbox/types.d.ts +11 -0
  164. package/build-types/sandbox/types.d.ts.map +1 -1
  165. package/build-types/textarea-control/stories/index.story.d.ts.map +1 -1
  166. package/build-types/textarea-control/styles/textarea-control-styles.d.ts.map +1 -1
  167. package/build-types/validated-form-controls/components/checkbox-control.d.ts +2 -1
  168. package/build-types/validated-form-controls/components/checkbox-control.d.ts.map +1 -1
  169. package/build-types/validated-form-controls/components/radio-control.d.ts +2 -1
  170. package/build-types/validated-form-controls/components/radio-control.d.ts.map +1 -1
  171. package/build-types/validated-form-controls/control-with-error.d.ts.map +1 -1
  172. package/package.json +21 -21
  173. package/src/alignment-matrix-control/README.md +1 -1
  174. package/src/alignment-matrix-control/style.module.scss +1 -1
  175. package/src/angle-picker-control/style.module.scss +1 -0
  176. package/src/autocomplete/README.md +2 -2
  177. package/src/autocomplete/autocompleter-ui.native.js +166 -173
  178. package/src/autocomplete/autocompleter-ui.tsx +114 -116
  179. package/src/autocomplete/get-autocomplete-match.ts +115 -0
  180. package/src/autocomplete/index.tsx +129 -208
  181. package/src/autocomplete/test/get-autocomplete-match.ts +338 -0
  182. package/src/autocomplete/test/index.tsx +112 -4
  183. package/src/autocomplete/types.ts +17 -10
  184. package/src/box-control/index.tsx +1 -19
  185. package/src/box-control/utils.ts +1 -19
  186. package/src/button/README.md +1 -1
  187. package/src/button/stories/index.story.tsx +0 -1
  188. package/src/button/style.scss +1 -7
  189. package/src/calendar/style.scss +3 -3
  190. package/src/calendar/utils/use-localization-props.ts +3 -4
  191. package/src/checkbox-control/style.scss +17 -5
  192. package/src/checkbox-control/types.ts +4 -0
  193. package/src/circular-option-picker/style.scss +1 -1
  194. package/src/color-palette/style.scss +1 -1
  195. package/src/css.d.ts +1 -0
  196. package/src/custom-gradient-picker/index.tsx +1 -0
  197. package/src/custom-select-control/index.tsx +3 -3
  198. package/src/custom-select-control-v2/custom-select.tsx +4 -4
  199. package/src/custom-select-control-v2/index.tsx +2 -2
  200. package/src/custom-select-control-v2/types.ts +1 -1
  201. package/src/date-time/README.md +3 -3
  202. package/src/date-time/date-picker/README.md +65 -0
  203. package/src/date-time/date-time/index.tsx +2 -2
  204. package/src/date-time/index.ts +2 -2
  205. package/src/date-time/stories/date.story.tsx +1 -1
  206. package/src/date-time/stories/time.story.tsx +1 -1
  207. package/src/date-time/time-picker/README.md +119 -0
  208. package/src/date-time/{time → time-picker}/index.tsx +1 -1
  209. package/src/date-time/types.ts +1 -1
  210. package/src/divider/README.md +5 -6
  211. package/src/dropdown-menu/style.scss +1 -1
  212. package/src/flex/stories/index.story.tsx +1 -1
  213. package/src/form-file-upload/README.md +3 -3
  214. package/src/form-toggle/style.scss +35 -2
  215. package/src/form-token-field/style.scss +12 -3
  216. package/src/gradient-picker/README.md +2 -2
  217. package/src/h-stack/README.md +10 -15
  218. package/src/h-stack/stories/index.story.tsx +2 -2
  219. package/src/heading/stories/index.story.tsx +1 -1
  220. package/src/higher-order/with-focus-outside/index.native.js +21 -20
  221. package/src/icon/README.md +1 -1
  222. package/src/menu/README.md +2 -2
  223. package/src/mobile/utils/get-px-from-css-unit.native.js +1 -1
  224. package/src/modal/index.tsx +1 -0
  225. package/src/palette-edit/index.tsx +1 -0
  226. package/src/radio-control/index.tsx +2 -0
  227. package/src/radio-control/style.scss +21 -2
  228. package/src/radio-control/test/index.tsx +10 -0
  229. package/src/radio-control/types.ts +6 -0
  230. package/src/sandbox/index.native.js +2 -2
  231. package/src/sandbox/index.tsx +191 -11
  232. package/src/sandbox/test/index.tsx +65 -24
  233. package/src/sandbox/types.ts +11 -0
  234. package/src/snackbar/style.scss +2 -2
  235. package/src/tab-panel/style.scss +1 -1
  236. package/src/tabs/README.md +6 -6
  237. package/src/text/stories/index.story.tsx +1 -1
  238. package/src/textarea-control/stories/index.story.tsx +3 -0
  239. package/src/textarea-control/styles/textarea-control-styles.ts +6 -0
  240. package/src/toggle-control/style.scss +1 -1
  241. package/src/toggle-control/test/index.tsx +8 -2
  242. package/src/toolbar/toolbar-button/toolbar-button-container.native.js +3 -1
  243. package/src/tree-select/README.md +1 -1
  244. package/src/v-stack/README.md +10 -15
  245. package/src/v-stack/stories/index.story.tsx +2 -2
  246. package/src/validated-form-controls/control-with-error.tsx +17 -12
  247. package/src/validated-form-controls/test/control-with-error.tsx +28 -1
  248. package/src/view/README.md +2 -5
  249. package/build/date-time/time/index.cjs.map +0 -7
  250. package/build-module/date-time/time/index.mjs.map +0 -7
  251. package/build-types/date-time/date/index.d.ts.map +0 -1
  252. package/build-types/date-time/date/styles.d.ts.map +0 -1
  253. package/build-types/date-time/date/test/index.d.ts.map +0 -1
  254. package/build-types/date-time/date/test/use-lilius.d.ts.map +0 -1
  255. package/build-types/date-time/date/use-lilius/index.d.ts.map +0 -1
  256. package/build-types/date-time/time/index.d.ts.map +0 -1
  257. package/build-types/date-time/time/styles.d.ts.map +0 -1
  258. package/build-types/date-time/time/test/index.d.ts.map +0 -1
  259. package/build-types/date-time/time/time-input/index.d.ts.map +0 -1
  260. package/build-types/date-time/time/time-input/test/index.d.ts.map +0 -1
  261. package/build-types/date-time/time/timezone.d.ts.map +0 -1
  262. package/src/button/stories/style.css +0 -8
  263. /package/build-types/date-time/{date → date-picker}/index.d.ts +0 -0
  264. /package/build-types/date-time/{date → date-picker}/styles.d.ts +0 -0
  265. /package/build-types/date-time/{date → date-picker}/test/index.d.ts +0 -0
  266. /package/build-types/date-time/{date → date-picker}/test/use-lilius.d.ts +0 -0
  267. /package/build-types/date-time/{date → date-picker}/use-lilius/index.d.ts +0 -0
  268. /package/build-types/date-time/{time → time-picker}/styles.d.ts +0 -0
  269. /package/build-types/date-time/{time → time-picker}/test/index.d.ts +0 -0
  270. /package/build-types/date-time/{time → time-picker}/time-input/index.d.ts +0 -0
  271. /package/build-types/date-time/{time → time-picker}/time-input/test/index.d.ts +0 -0
  272. /package/build-types/date-time/{time → time-picker}/timezone.d.ts +0 -0
  273. /package/src/date-time/{date → date-picker}/index.tsx +0 -0
  274. /package/src/date-time/{date → date-picker}/styles.ts +0 -0
  275. /package/src/date-time/{date → date-picker}/test/index.tsx +0 -0
  276. /package/src/date-time/{date → date-picker}/test/use-lilius.ts +0 -0
  277. /package/src/date-time/{date → date-picker}/use-lilius/index.ts +0 -0
  278. /package/src/date-time/{time → time-picker}/styles.ts +0 -0
  279. /package/src/date-time/{time → time-picker}/test/index.tsx +0 -0
  280. /package/src/date-time/{time → time-picker}/time-input/index.tsx +0 -0
  281. /package/src/date-time/{time → time-picker}/time-input/test/index.tsx +0 -0
  282. /package/src/date-time/{time → time-picker}/timezone.tsx +0 -0
@@ -25,7 +25,7 @@ import getDefaultUseItems from './get-default-use-items';
25
25
  import Button from '../button';
26
26
  import Popover from '../popover';
27
27
  import { VisuallyHidden } from '../visually-hidden';
28
- import type { AutocompleterUIProps, KeyedOption, WPCompleter } from './types';
28
+ import type { AutocompleterUIProps, KeyedOption } from './types';
29
29
 
30
30
  type ListBoxProps = {
31
31
  items: KeyedOption[];
@@ -79,112 +79,125 @@ function ListBox( {
79
79
  );
80
80
  }
81
81
 
82
- export function getAutoCompleterUI( autocompleter: WPCompleter ) {
82
+ export function AutocompleterUI( {
83
+ autocompleter,
84
+ filterValue,
85
+ instanceId,
86
+ listBoxId,
87
+ className,
88
+ selectedIndex,
89
+ onChangeOptions,
90
+ onSelect,
91
+ reset,
92
+ contentRef,
93
+ }: AutocompleterUIProps ) {
94
+ // The useItems hook is derived from the autocompleter prop. This is safe
95
+ // because the parent renders this component with key={autocompleter.name},
96
+ // ensuring a fresh mount (and stable hook identity) when the completer changes.
83
97
  const useItems =
84
98
  autocompleter.useItems ?? getDefaultUseItems( autocompleter );
99
+ // eslint-disable-next-line react-compiler/react-compiler
100
+ const [ items ] = useItems( filterValue );
101
+ const popoverAnchor = useAnchor( {
102
+ editableContentElement: contentRef.current,
103
+ } );
104
+
105
+ const [ needsA11yCompat, setNeedsA11yCompat ] = useState( false );
106
+ const popoverRef = useRef< HTMLElement >( null );
107
+ const popoverRefs = useMergeRefs( [
108
+ popoverRef,
109
+ useRefEffect(
110
+ ( node ) => {
111
+ if ( ! contentRef.current ) {
112
+ return;
113
+ }
85
114
 
86
- function AutocompleterUI( {
87
- filterValue,
88
- instanceId,
89
- listBoxId,
90
- className,
91
- selectedIndex,
92
- onChangeOptions,
93
- onSelect,
94
- onReset,
95
- reset,
96
- contentRef,
97
- }: AutocompleterUIProps ) {
98
- const [ items ] = useItems( filterValue );
99
- const popoverAnchor = useAnchor( {
100
- editableContentElement: contentRef.current,
101
- } );
102
-
103
- const [ needsA11yCompat, setNeedsA11yCompat ] = useState( false );
104
- const popoverRef = useRef< HTMLElement >( null );
105
- const popoverRefs = useMergeRefs( [
106
- popoverRef,
107
- useRefEffect(
108
- ( node ) => {
109
- if ( ! contentRef.current ) {
110
- return;
111
- }
112
-
113
- // If the popover is rendered in a different document than
114
- // the content, we need to duplicate the options list in the
115
- // content document so that it's available to the screen
116
- // readers, which check the DOM ID based aria-* attributes.
117
- setNeedsA11yCompat(
118
- node.ownerDocument !== contentRef.current.ownerDocument
119
- );
120
- },
121
- [ contentRef ]
122
- ),
123
- ] );
124
-
125
- useOnClickOutside( popoverRef, reset );
126
-
127
- const debouncedSpeak = useDebounce( speak, 500 );
128
-
129
- function announce( options: Array< KeyedOption > ) {
130
- if ( ! debouncedSpeak ) {
131
- return;
132
- }
133
- if ( !! options.length ) {
134
- if ( filterValue ) {
135
- debouncedSpeak(
136
- sprintf(
137
- /* translators: %d: number of results. */
138
- _n(
139
- '%d result found, use up and down arrow keys to navigate.',
140
- '%d results found, use up and down arrow keys to navigate.',
141
- options.length
142
- ),
115
+ // If the popover is rendered in a different document than
116
+ // the content, we need to duplicate the options list in the
117
+ // content document so that it's available to the screen
118
+ // readers, which check the DOM ID based aria-* attributes.
119
+ setNeedsA11yCompat(
120
+ node.ownerDocument !== contentRef.current.ownerDocument
121
+ );
122
+ },
123
+ [ contentRef ]
124
+ ),
125
+ ] );
126
+
127
+ useOnClickOutside( popoverRef, reset );
128
+
129
+ const debouncedSpeak = useDebounce( speak, 500 );
130
+
131
+ function announce( options: Array< KeyedOption > ) {
132
+ if ( ! debouncedSpeak ) {
133
+ return;
134
+ }
135
+ if ( !! options.length ) {
136
+ if ( filterValue ) {
137
+ debouncedSpeak(
138
+ sprintf(
139
+ /* translators: %d: number of results. */
140
+ _n(
141
+ '%d result found, use up and down arrow keys to navigate.',
142
+ '%d results found, use up and down arrow keys to navigate.',
143
143
  options.length
144
144
  ),
145
- 'assertive'
146
- );
147
- } else {
148
- debouncedSpeak(
149
- sprintf(
150
- /* translators: %d: number of results. */
151
- _n(
152
- 'Initial %d result loaded. Type to filter all available results. Use up and down arrow keys to navigate.',
153
- 'Initial %d results loaded. Type to filter all available results. Use up and down arrow keys to navigate.',
154
- options.length
155
- ),
145
+ options.length
146
+ ),
147
+ 'assertive'
148
+ );
149
+ } else {
150
+ debouncedSpeak(
151
+ sprintf(
152
+ /* translators: %d: number of results. */
153
+ _n(
154
+ 'Initial %d result loaded. Type to filter all available results. Use up and down arrow keys to navigate.',
155
+ 'Initial %d results loaded. Type to filter all available results. Use up and down arrow keys to navigate.',
156
156
  options.length
157
157
  ),
158
- 'assertive'
159
- );
160
- }
161
- } else {
162
- debouncedSpeak( __( 'No results.' ), 'assertive' );
158
+ options.length
159
+ ),
160
+ 'assertive'
161
+ );
163
162
  }
163
+ } else {
164
+ debouncedSpeak( __( 'No results.' ), 'assertive' );
164
165
  }
166
+ }
165
167
 
166
- useLayoutEffect( () => {
167
- onChangeOptions( items );
168
- announce( items );
169
- // We want to avoid introducing unexpected side effects.
170
- // See https://github.com/WordPress/gutenberg/pull/41820
171
- }, [ items ] );
168
+ useLayoutEffect( () => {
169
+ onChangeOptions( items );
170
+ announce( items );
171
+ // We want to avoid introducing unexpected side effects.
172
+ // See https://github.com/WordPress/gutenberg/pull/41820
173
+ }, [ items ] );
172
174
 
173
- if ( items.length === 0 ) {
174
- return null;
175
- }
175
+ if ( items.length === 0 ) {
176
+ return null;
177
+ }
176
178
 
177
- return (
178
- <>
179
- <Popover
180
- offset={ 8 }
181
- focusOnMount={ false }
182
- onClose={ onReset }
183
- placement="top-start"
184
- className="components-autocomplete__popover"
185
- anchor={ popoverAnchor }
186
- ref={ popoverRefs }
187
- >
179
+ return (
180
+ <>
181
+ <Popover
182
+ offset={ 8 }
183
+ focusOnMount={ false }
184
+ placement="top-start"
185
+ className="components-autocomplete__popover"
186
+ anchor={ popoverAnchor }
187
+ ref={ popoverRefs }
188
+ >
189
+ <ListBox
190
+ items={ items }
191
+ onSelect={ onSelect }
192
+ selectedIndex={ selectedIndex }
193
+ instanceId={ instanceId }
194
+ listBoxId={ listBoxId }
195
+ className={ className }
196
+ />
197
+ </Popover>
198
+ { contentRef.current &&
199
+ needsA11yCompat &&
200
+ createPortal(
188
201
  <ListBox
189
202
  items={ items }
190
203
  onSelect={ onSelect }
@@ -192,27 +205,12 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) {
192
205
  instanceId={ instanceId }
193
206
  listBoxId={ listBoxId }
194
207
  className={ className }
195
- />
196
- </Popover>
197
- { contentRef.current &&
198
- needsA11yCompat &&
199
- createPortal(
200
- <ListBox
201
- items={ items }
202
- onSelect={ onSelect }
203
- selectedIndex={ selectedIndex }
204
- instanceId={ instanceId }
205
- listBoxId={ listBoxId }
206
- className={ className }
207
- Component={ VisuallyHidden }
208
- />,
209
- contentRef.current.ownerDocument.body
210
- ) }
211
- </>
212
- );
213
- }
214
-
215
- return AutocompleterUI;
208
+ Component={ VisuallyHidden }
209
+ />,
210
+ contentRef.current.ownerDocument.body
211
+ ) }
212
+ </>
213
+ );
216
214
  }
217
215
 
218
216
  function useOnClickOutside(
@@ -0,0 +1,115 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import removeAccents from 'remove-accents';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import type { WPCompleter } from './types';
10
+
11
+ type AutocompleteMatch = {
12
+ completer: WPCompleter;
13
+ filterValue: string;
14
+ };
15
+
16
+ export function getAutocompleteMatch(
17
+ textContent: string,
18
+ completers: WPCompleter[],
19
+ filteredOptionsLength: number,
20
+ isBackspacing: boolean,
21
+ getTextAfterSelection: () => string
22
+ ): AutocompleteMatch | null {
23
+ if ( ! textContent ) {
24
+ return null;
25
+ }
26
+
27
+ // Find the completer whose trigger prefix ends closest to the cursor
28
+ // (rightmost end position). Comparing end positions instead of start
29
+ // positions correctly resolves overlapping prefixes like "@" and "@@".
30
+ let completer: WPCompleter | null = null;
31
+ let triggerIndex = -1;
32
+ let matchedEndIndex = -1;
33
+ let matchedPrefixLength = 0;
34
+
35
+ for ( const currentCompleter of completers ) {
36
+ const currentIndex = textContent.lastIndexOf(
37
+ currentCompleter.triggerPrefix
38
+ );
39
+ if ( currentIndex < 0 ) {
40
+ continue;
41
+ }
42
+ const currentEndIndex =
43
+ currentIndex + currentCompleter.triggerPrefix.length;
44
+ if (
45
+ currentEndIndex > matchedEndIndex ||
46
+ ( currentEndIndex === matchedEndIndex &&
47
+ currentCompleter.triggerPrefix.length > matchedPrefixLength )
48
+ ) {
49
+ completer = currentCompleter;
50
+ triggerIndex = currentIndex;
51
+ matchedEndIndex = currentEndIndex;
52
+ matchedPrefixLength = currentCompleter.triggerPrefix.length;
53
+ }
54
+ }
55
+
56
+ if ( ! completer ) {
57
+ return null;
58
+ }
59
+
60
+ const { allowContext, triggerPrefix } = completer;
61
+ const textWithoutTrigger = textContent.slice(
62
+ triggerIndex + triggerPrefix.length
63
+ );
64
+
65
+ // Prevent matching with an extremely long string, which causes
66
+ // the editor to slow-down significantly. This could happen, for
67
+ // example, if `matchingWhileBackspacing` is true and one of the
68
+ // "words" ends up being too long. Returning null here intentionally
69
+ // resets the autocompleter state in the caller.
70
+ if ( textWithoutTrigger.length > 50 ) {
71
+ return null;
72
+ }
73
+
74
+ const mismatch = filteredOptionsLength === 0;
75
+ const wordsFromTrigger = textWithoutTrigger.split( /\s/ );
76
+
77
+ // Allow matching when typing a trigger + the match string or when
78
+ // clicking in an existing trigger word on the page.
79
+ // E.g. "Some text @a" — "@a" is detected as a trigger word.
80
+ const hasOneTriggerWord = wordsFromTrigger.length === 1;
81
+
82
+ // Allow matching when backspacing near a trigger word (up to 3
83
+ // words from the trigger character). This lets us recover from a
84
+ // mismatch when backspacing while still imposing sane limits.
85
+ // E.g. "Some text @marcelo sekkkk" — backspacing "kkkk" re-shows
86
+ // the popup once the text matches again.
87
+ const matchingWhileBackspacing =
88
+ isBackspacing && wordsFromTrigger.length <= 3;
89
+
90
+ if ( mismatch && ! ( matchingWhileBackspacing || hasOneTriggerWord ) ) {
91
+ return null;
92
+ }
93
+
94
+ if (
95
+ allowContext &&
96
+ ! allowContext(
97
+ textContent.slice( 0, triggerIndex ),
98
+ getTextAfterSelection()
99
+ )
100
+ ) {
101
+ return null;
102
+ }
103
+
104
+ if (
105
+ /^\s/.test( textWithoutTrigger ) ||
106
+ /\s\s+$/.test( textWithoutTrigger )
107
+ ) {
108
+ return null;
109
+ }
110
+
111
+ return {
112
+ completer,
113
+ filterValue: removeAccents( textWithoutTrigger ),
114
+ };
115
+ }