@primer/react 38.40.0 → 38.40.1-rc.65ba5ef2b

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @primer/react
2
2
 
3
+ ## 38.40.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#8413](https://github.com/primer/react/pull/8413) [`d4e30ed`](https://github.com/primer/react/commit/d4e30ed186af6ae781b3adcf83278e0c2001a834) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - SelectPanel: Announce the filter input label when focus moves into the panel
8
+
3
9
  ## 38.40.0
4
10
 
5
11
  ### Minor Changes
@@ -96,6 +96,7 @@ function FilteredActionList({ loading = false, placeholderText, filterValue: ext
96
96
  }
97
97
  }, [activeDescendantRef]);
98
98
  const listContainerRefCallback = useCallback((node) => {
99
+ listRef.current = node;
99
100
  setListContainerElement(node);
100
101
  onListContainerRefChanged === null || onListContainerRefChanged === void 0 || onListContainerRefChanged(node);
101
102
  }, [onListContainerRefChanged]);
@@ -201,7 +202,7 @@ function FilteredActionList({ loading = false, placeholderText, filterValue: ext
201
202
  readInputRef,
202
203
  usingRovingTabindex
203
204
  ]);
204
- useAnnouncements(items, usingRovingTabindex ? listRef : { current: listContainerElement }, readInputRef, announcementsEnabled, loading, messageText, _PrivateFocusManagement);
205
+ const onInputFocus = useAnnouncements(items, listRef, readInputRef, announcementsEnabled, loading, messageText, _PrivateFocusManagement, filterValue);
205
206
  useScrollFlash(readScrollContainerRef);
206
207
  const handleSelectAllChange = useCallback((e) => {
207
208
  if (onSelectAllChange) onSelectAllChange(e.target.checked);
@@ -302,6 +303,7 @@ function FilteredActionList({ loading = false, placeholderText, filterValue: ext
302
303
  children: [
303
304
  /*#__PURE__*/ jsx(FilteredActionListInput, {
304
305
  inputRef: appliedInputRef,
306
+ onInputFocus,
305
307
  value: filterValue,
306
308
  onInputChange,
307
309
  onInputKeyPress,
@@ -5,6 +5,7 @@ import React from "react";
5
5
  interface FilteredActionListInputProps extends Partial<Omit<TextInputProps, 'onChange' | 'onKeyDown'>> {
6
6
  inputRef: React.Ref<HTMLInputElement | null>;
7
7
  onInputChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
8
+ onInputFocus?: React.FocusEventHandler<HTMLInputElement>;
8
9
  onInputKeyPress?: React.KeyboardEventHandler<HTMLInputElement>;
9
10
  onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
10
11
  placeholderText?: string;
@@ -17,6 +18,7 @@ declare function FilteredActionListInput({
17
18
  inputRef,
18
19
  value,
19
20
  onInputChange,
21
+ onInputFocus,
20
22
  onInputKeyPress,
21
23
  onInputKeyDown,
22
24
  placeholderText,
@@ -25,6 +27,7 @@ declare function FilteredActionListInput({
25
27
  loading,
26
28
  fullScreenOnNarrow,
27
29
  className,
30
+ onFocus,
28
31
  ...restTextInputProps
29
32
  }: FilteredActionListInputProps): React.JSX.Element;
30
33
  //#endregion
@@ -5,21 +5,23 @@ import { jsx } from "react/jsx-runtime";
5
5
  import { clsx } from "clsx";
6
6
  //#region src/FilteredActionList/FilteredActionListInput.tsx
7
7
  function FilteredActionListInput(t0) {
8
- const $ = c(28);
8
+ const $ = c(34);
9
9
  let className;
10
10
  let fullScreenOnNarrow;
11
11
  let inputDescriptionTextId;
12
12
  let inputRef;
13
13
  let listId;
14
14
  let loading;
15
+ let onFocus;
15
16
  let onInputChange;
17
+ let onInputFocus;
16
18
  let onInputKeyDown;
17
19
  let onInputKeyPress;
18
20
  let placeholderText;
19
21
  let restTextInputProps;
20
22
  let value;
21
23
  if ($[0] !== t0) {
22
- ({inputRef, value, onInputChange, onInputKeyPress, onInputKeyDown, placeholderText, listId, inputDescriptionTextId, loading, fullScreenOnNarrow, className, ...restTextInputProps} = t0);
24
+ ({inputRef, value, onInputChange, onInputFocus, onInputKeyPress, onInputKeyDown, placeholderText, listId, inputDescriptionTextId, loading, fullScreenOnNarrow, className, onFocus, ...restTextInputProps} = t0);
23
25
  $[0] = t0;
24
26
  $[1] = className;
25
27
  $[2] = fullScreenOnNarrow;
@@ -27,12 +29,14 @@ function FilteredActionListInput(t0) {
27
29
  $[4] = inputRef;
28
30
  $[5] = listId;
29
31
  $[6] = loading;
30
- $[7] = onInputChange;
31
- $[8] = onInputKeyDown;
32
- $[9] = onInputKeyPress;
33
- $[10] = placeholderText;
34
- $[11] = restTextInputProps;
35
- $[12] = value;
32
+ $[7] = onFocus;
33
+ $[8] = onInputChange;
34
+ $[9] = onInputFocus;
35
+ $[10] = onInputKeyDown;
36
+ $[11] = onInputKeyPress;
37
+ $[12] = placeholderText;
38
+ $[13] = restTextInputProps;
39
+ $[14] = value;
36
40
  } else {
37
41
  className = $[1];
38
42
  fullScreenOnNarrow = $[2];
@@ -40,23 +44,36 @@ function FilteredActionListInput(t0) {
40
44
  inputRef = $[4];
41
45
  listId = $[5];
42
46
  loading = $[6];
43
- onInputChange = $[7];
44
- onInputKeyDown = $[8];
45
- onInputKeyPress = $[9];
46
- placeholderText = $[10];
47
- restTextInputProps = $[11];
48
- value = $[12];
47
+ onFocus = $[7];
48
+ onInputChange = $[8];
49
+ onInputFocus = $[9];
50
+ onInputKeyDown = $[10];
51
+ onInputKeyPress = $[11];
52
+ placeholderText = $[12];
53
+ restTextInputProps = $[13];
54
+ value = $[14];
49
55
  }
50
56
  let t1;
51
- if ($[13] !== className || $[14] !== fullScreenOnNarrow) {
52
- t1 = clsx(className, { [FilteredActionList_module_css_default.FullScreenTextInput]: fullScreenOnNarrow });
53
- $[13] = className;
54
- $[14] = fullScreenOnNarrow;
55
- $[15] = t1;
56
- } else t1 = $[15];
57
+ if ($[15] !== onFocus || $[16] !== onInputFocus) {
58
+ t1 = (event) => {
59
+ var _onInputFocus, _onFocus;
60
+ (_onInputFocus = onInputFocus) === null || _onInputFocus === void 0 || _onInputFocus(event);
61
+ (_onFocus = onFocus) === null || _onFocus === void 0 || _onFocus(event);
62
+ };
63
+ $[15] = onFocus;
64
+ $[16] = onInputFocus;
65
+ $[17] = t1;
66
+ } else t1 = $[17];
57
67
  let t2;
58
- if ($[16] !== inputDescriptionTextId || $[17] !== inputRef || $[18] !== listId || $[19] !== loading || $[20] !== onInputChange || $[21] !== onInputKeyDown || $[22] !== onInputKeyPress || $[23] !== placeholderText || $[24] !== restTextInputProps || $[25] !== t1 || $[26] !== value) {
59
- t2 = /*#__PURE__*/ jsx("div", {
68
+ if ($[18] !== className || $[19] !== fullScreenOnNarrow) {
69
+ t2 = clsx(className, { [FilteredActionList_module_css_default.FullScreenTextInput]: fullScreenOnNarrow });
70
+ $[18] = className;
71
+ $[19] = fullScreenOnNarrow;
72
+ $[20] = t2;
73
+ } else t2 = $[20];
74
+ let t3;
75
+ if ($[21] !== inputDescriptionTextId || $[22] !== inputRef || $[23] !== listId || $[24] !== loading || $[25] !== onInputChange || $[26] !== onInputKeyDown || $[27] !== onInputKeyPress || $[28] !== placeholderText || $[29] !== restTextInputProps || $[30] !== t1 || $[31] !== t2 || $[32] !== value) {
76
+ t3 = /*#__PURE__*/ jsx("div", {
60
77
  className: FilteredActionList_module_css_default.Header,
61
78
  "data-component": "FilteredActionList.Header",
62
79
  children: /*#__PURE__*/ jsx(TextInput_default, {
@@ -66,6 +83,7 @@ function FilteredActionListInput(t0) {
66
83
  color: "fg.default",
67
84
  value,
68
85
  onChange: onInputChange,
86
+ onFocus: t1,
69
87
  onKeyPress: onInputKeyPress,
70
88
  onKeyDown: onInputKeyDown,
71
89
  placeholder: placeholderText,
@@ -77,24 +95,25 @@ function FilteredActionListInput(t0) {
77
95
  "aria-describedby": inputDescriptionTextId,
78
96
  loaderPosition: "leading",
79
97
  loading,
80
- className: t1,
98
+ className: t2,
81
99
  ...restTextInputProps
82
100
  })
83
101
  });
84
- $[16] = inputDescriptionTextId;
85
- $[17] = inputRef;
86
- $[18] = listId;
87
- $[19] = loading;
88
- $[20] = onInputChange;
89
- $[21] = onInputKeyDown;
90
- $[22] = onInputKeyPress;
91
- $[23] = placeholderText;
92
- $[24] = restTextInputProps;
93
- $[25] = t1;
94
- $[26] = value;
95
- $[27] = t2;
96
- } else t2 = $[27];
97
- return t2;
102
+ $[21] = inputDescriptionTextId;
103
+ $[22] = inputRef;
104
+ $[23] = listId;
105
+ $[24] = loading;
106
+ $[25] = onInputChange;
107
+ $[26] = onInputKeyDown;
108
+ $[27] = onInputKeyPress;
109
+ $[28] = placeholderText;
110
+ $[29] = restTextInputProps;
111
+ $[30] = t1;
112
+ $[31] = t2;
113
+ $[32] = value;
114
+ $[33] = t3;
115
+ } else t3 = $[33];
116
+ return t3;
98
117
  }
99
118
  //#endregion
100
119
  export { FilteredActionListInput };
@@ -2,12 +2,20 @@ import { useCallback, useEffect, useRef } from "react";
2
2
  import { announce } from "@primer/live-region-element";
3
3
  //#region src/FilteredActionList/useAnnouncements.tsx
4
4
  const delayMs = 500;
5
- const useFirstRender = () => {
6
- const firstRender = useRef(true);
7
- useEffect(() => {
8
- firstRender.current = false;
9
- }, []);
10
- return firstRender.current;
5
+ const getInputLabel = (input) => {
6
+ var _input$labels;
7
+ if (!input) return;
8
+ const labelledBy = input.getAttribute("aria-labelledby");
9
+ if (labelledBy) {
10
+ const label = labelledBy.split(/\s+/).map((id) => {
11
+ var _input$ownerDocument$;
12
+ return (_input$ownerDocument$ = input.ownerDocument.getElementById(id)) === null || _input$ownerDocument$ === void 0 ? void 0 : _input$ownerDocument$.textContent.trim();
13
+ }).filter(Boolean).join(" ");
14
+ if (label) return label;
15
+ }
16
+ const ariaLabel = input.getAttribute("aria-label");
17
+ if (ariaLabel) return ariaLabel;
18
+ return Array.from((_input$labels = input.labels) !== null && _input$labels !== void 0 ? _input$labels : []).map((label) => label.textContent.trim()).filter(Boolean).join(" ") || void 0;
11
19
  };
12
20
  const getItemWithActiveDescendant = (listRef, items) => {
13
21
  const listElement = listRef.current;
@@ -22,52 +30,65 @@ const getItemWithActiveDescendant = (listRef, items) => {
22
30
  selected: activeItem === null || activeItem === void 0 ? void 0 : activeItem.selected
23
31
  };
24
32
  };
25
- const useAnnouncements = (items, listContainerRef, inputRef, enabled = true, loading = false, message, focusManagement) => {
33
+ const getAnnouncementState = (items, loading, message, filterValue) => JSON.stringify({
34
+ items: items.map((item, index) => {
35
+ var _ref, _item$id;
36
+ return [
37
+ (_ref = (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : item.text) !== null && _ref !== void 0 ? _ref : index,
38
+ item.text,
39
+ item.selected
40
+ ];
41
+ }),
42
+ loading,
43
+ message,
44
+ filterValue
45
+ });
46
+ const useAnnouncements = (items, listContainerRef, inputRef, enabled = true, loading = false, message, focusManagement, filterValue) => {
26
47
  const usingRovingTabindex = focusManagement === "roving-tabindex";
27
48
  const liveRegion = document.querySelector("live-region");
28
49
  const selectedItems = items.filter((item) => item.selected).length;
29
50
  const announce$1 = useCallback((...args) => {
30
51
  if (enabled) return announce(...args);
31
52
  }, [enabled]);
32
- useEffect(function announceInitialFocus() {
33
- const focusHandler = () => {
34
- if (usingRovingTabindex) {
35
- const announcementText = `${items.length} item${items.length > 1 ? "s" : ""} available, ${selectedItems} selected.`;
36
- announce$1(announcementText, {
37
- delayMs,
38
- from: liveRegion ? liveRegion : void 0
39
- });
40
- } else window.requestAnimationFrame(() => {
41
- const activeItem = getItemWithActiveDescendant(listContainerRef, items);
42
- if (!activeItem) return;
43
- const { index, text, selected } = activeItem;
44
- const announcementText = [
45
- `Focus on filter text box and list of items`,
46
- `Focused item: ${text}`,
47
- `${selected ? "selected" : "not selected"}`,
48
- `${index + 1} of ${items.length}`
49
- ].join(", ");
50
- announce$1(announcementText, {
51
- delayMs,
52
- from: liveRegion ? liveRegion : void 0
53
- });
53
+ const onInputFocus = useCallback(() => {
54
+ if (usingRovingTabindex) {
55
+ const announcementText = `${items.length} item${items.length > 1 ? "s" : ""} available, ${selectedItems} selected.`;
56
+ announce$1(announcementText, {
57
+ delayMs,
58
+ from: liveRegion ? liveRegion : void 0
59
+ });
60
+ return;
61
+ }
62
+ window.setTimeout(() => {
63
+ const activeItem = getItemWithActiveDescendant(listContainerRef, items);
64
+ if (!activeItem) return;
65
+ const { index, text, selected } = activeItem;
66
+ const inputLabel = getInputLabel(inputRef.current);
67
+ const announcementText = [
68
+ inputLabel ? `${inputLabel}, filter text box and list of items` : "Focus on filter text box and list of items",
69
+ `Focused item: ${text}`,
70
+ `${selected ? "selected" : "not selected"}`,
71
+ `${index + 1} of ${items.length}`
72
+ ].join(", ");
73
+ announce$1(announcementText, {
74
+ delayMs,
75
+ from: liveRegion ? liveRegion : void 0
54
76
  });
55
- };
56
- const inputElement = inputRef.current;
57
- inputElement === null || inputElement === void 0 || inputElement.addEventListener("focus", focusHandler);
58
- return () => inputElement === null || inputElement === void 0 ? void 0 : inputElement.removeEventListener("focus", focusHandler);
77
+ });
59
78
  }, [
60
- listContainerRef,
79
+ announce$1,
61
80
  inputRef,
62
81
  items,
82
+ listContainerRef,
63
83
  liveRegion,
64
- announce$1,
65
- usingRovingTabindex,
66
- selectedItems
84
+ selectedItems,
85
+ usingRovingTabindex
67
86
  ]);
68
- const isFirstRender = useFirstRender();
87
+ const announcementState = getAnnouncementState(items, loading, message, filterValue);
88
+ const previousAnnouncementState = useRef(announcementState);
69
89
  useEffect(function announceListUpdates() {
70
- if (isFirstRender) return;
90
+ if (previousAnnouncementState.current === announcementState) return;
91
+ previousAnnouncementState.current = announcementState;
71
92
  liveRegion === null || liveRegion === void 0 || liveRegion.clear();
72
93
  if (items.length === 0 && !loading) {
73
94
  announce$1(`${message === null || message === void 0 ? void 0 : message.title}. ${message === null || message === void 0 ? void 0 : message.description}`, { delayMs });
@@ -96,7 +117,7 @@ const useAnnouncements = (items, listContainerRef, inputRef, enabled = true, loa
96
117
  });
97
118
  }, [
98
119
  announce$1,
99
- isFirstRender,
120
+ announcementState,
100
121
  items,
101
122
  listContainerRef,
102
123
  liveRegion,
@@ -106,6 +127,7 @@ const useAnnouncements = (items, listContainerRef, inputRef, enabled = true, loa
106
127
  loading,
107
128
  selectedItems
108
129
  ]);
130
+ return onInputFocus;
109
131
  };
110
132
  //#endregion
111
133
  export { useAnnouncements };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@primer/react",
3
3
  "type": "module",
4
- "version": "38.40.0",
4
+ "version": "38.40.1-rc.65ba5ef2b",
5
5
  "description": "An implementation of GitHub's Primer Design System using React",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",