@primer/react 38.29.1-rc.0a2dc6645 → 38.29.1-rc.4254fa28f
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 +0 -6
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +68 -27
- package/dist/Textarea/Textarea.d.ts.map +1 -1
- package/dist/Textarea/Textarea.js +186 -158
- package/dist/_VisuallyHidden.js +2 -2
- package/dist/_VisuallyHidden.module.css.js +2 -2
- package/dist/live-region/Announce.d.ts.map +1 -1
- package/dist/live-region/Announce.js +36 -51
- package/dist/utils/character-counter.d.ts +21 -16
- package/dist/utils/character-counter.d.ts.map +1 -1
- package/dist/utils/character-counter.js +59 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
- [#8021](https://github.com/primer/react/pull/8021) [`6131a94`](https://github.com/primer/react/commit/6131a9424b5bd5bb98b7ae05771d8187fe2f58cd) Thanks [@mattcosta7](https://github.com/mattcosta7)! - Announce, AriaStatus, AriaAlert: Avoid an extra React render on every content change (e.g. per keystroke when tied to an input)
|
|
8
|
-
|
|
9
7
|
- [#7935](https://github.com/primer/react/pull/7935) [`5b3c806`](https://github.com/primer/react/commit/5b3c806305cfa09d33d42fc2957d55105725ea3b) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - ConfirmationDialog: `useConfirm`/`confirm` now removes its host element from `document.body` after the dialog is closed, and uses a fresh host element per call, so the empty container no longer lingers or leaks into other components and tests
|
|
10
8
|
|
|
11
|
-
- [#8021](https://github.com/primer/react/pull/8021) [`6131a94`](https://github.com/primer/react/commit/6131a9424b5bd5bb98b7ae05771d8187fe2f58cd) Thanks [@mattcosta7](https://github.com/mattcosta7)! - Textarea: Improve typing performance by deriving the character counter in render instead of in effects, removing extra re-renders on each keystroke.
|
|
12
|
-
|
|
13
|
-
- [#8021](https://github.com/primer/react/pull/8021) [`6131a94`](https://github.com/primer/react/commit/6131a9424b5bd5bb98b7ae05771d8187fe2f58cd) Thanks [@mattcosta7](https://github.com/mattcosta7)! - TextInput: Improve typing performance by deriving the character counter in render instead of in effects, removing extra re-renders on each keystroke.
|
|
14
|
-
|
|
15
9
|
## 38.29.0
|
|
16
10
|
|
|
17
11
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextInput.d.ts","sourceRoot":"","sources":["../../src/TextInput/TextInput.tsx"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"TextInput.d.ts","sourceRoot":"","sources":["../../src/TextInput/TextInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAwD,MAAM,OAAO,CAAA;AAE5E,OAAO,KAAK,EAAC,mBAAmB,IAAI,8BAA8B,EAAC,MAAM,sBAAsB,CAAA;AAO/F,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,yCAAyC,CAAA;AAQ/E,MAAM,MAAM,4BAA4B,GAAG;IACzC,uEAAuE;IACvE,IAAI,CAAC,EAAE,KAAK,CAAC,WAAW,CAAA;IACxB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;QAKI;IACJ,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAA;IAChD,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,SAAS,CAAA;IACnD;;OAEG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,SAAS,CAAA;IACpD;;OAEG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAA;IACvE;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,GAAG,OAAO,CACT,IAAI,CACF,kBAAkB,EAChB,OAAO,GACP,UAAU,GACV,UAAU,GACV,WAAW,GACX,OAAO,GACP,UAAU,GACV,UAAU,GACV,SAAS,GACT,MAAM,GACN,kBAAkB,CACrB,CACF,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,4BAA4B,CAAC,CAAA;;;;;;;;;;;AAkQzG,wBAGE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useId, useCallback } from 'react';
|
|
1
|
+
import React, { useState, useRef, useId, useCallback, useEffect } from 'react';
|
|
2
2
|
import { isValidElementType } from 'react-is';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { AlertFillIcon } from '@primer/octicons-react';
|
|
@@ -8,11 +8,9 @@ import { TextInputWrapper } from '../internal/components/TextInputWrapper.js';
|
|
|
8
8
|
import TextInputAction from '../internal/components/TextInputInnerAction.js';
|
|
9
9
|
import UnstyledTextInput from '../internal/components/UnstyledTextInput.js';
|
|
10
10
|
import VisuallyHidden from '../_VisuallyHidden.js';
|
|
11
|
-
import
|
|
12
|
-
import { SCREEN_READER_DELAY, getCharacterCountState } from '../utils/character-counter.js';
|
|
11
|
+
import { CharacterCounter } from '../utils/character-counter.js';
|
|
13
12
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
14
13
|
import { useProvidedRefOrCreate } from '../hooks/useProvidedRefOrCreate.js';
|
|
15
|
-
import { AriaStatus } from '../live-region/AriaStatus.js';
|
|
16
14
|
import Text from '../Text/Text.js';
|
|
17
15
|
|
|
18
16
|
// using forwardRef is important so that other components can autofocus the input
|
|
@@ -48,22 +46,16 @@ const TextInput = /*#__PURE__*/React.forwardRef(({
|
|
|
48
46
|
'data-component': dataComponent,
|
|
49
47
|
...inputProps
|
|
50
48
|
}, ref) => {
|
|
51
|
-
var _counter$isOverLimit;
|
|
52
49
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
53
50
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
// The counter and validation state are derived directly from the current
|
|
64
|
-
// length, so they stay in sync with the input without an extra render.
|
|
65
|
-
const counter = characterLimit ? getCharacterCountState(currentLength, characterLimit) : undefined;
|
|
66
|
-
const isOverLimit = (_counter$isOverLimit = counter === null || counter === void 0 ? void 0 : counter.isOverLimit) !== null && _counter$isOverLimit !== void 0 ? _counter$isOverLimit : false;
|
|
51
|
+
const [characterCount, setCharacterCount] = useState('');
|
|
52
|
+
const [isOverLimit, setIsOverLimit] = useState(false);
|
|
53
|
+
const [screenReaderMessage, setScreenReaderMessage] = useState('');
|
|
54
|
+
const characterCounterRef = useRef(null);
|
|
55
|
+
const lastCountedLengthRef = useRef(null);
|
|
56
|
+
const lastCharacterCountRef = useRef('');
|
|
57
|
+
const lastIsOverLimitRef = useRef(false);
|
|
58
|
+
const lastScreenReaderMessageRef = useRef('');
|
|
67
59
|
|
|
68
60
|
// this class is necessary to style FilterSearch, plz no touchy!
|
|
69
61
|
const wrapperClasses = clsx(className, 'TextInput-wrapper');
|
|
@@ -92,13 +84,63 @@ const TextInput = /*#__PURE__*/React.forwardRef(({
|
|
|
92
84
|
onBlur && onBlur(e_1);
|
|
93
85
|
}, [onBlur]);
|
|
94
86
|
|
|
87
|
+
// Initialize character counter
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (characterLimit) {
|
|
90
|
+
characterCounterRef.current = new CharacterCounter({
|
|
91
|
+
onCountUpdate: (count, overLimit, message) => {
|
|
92
|
+
if (message !== lastCharacterCountRef.current) {
|
|
93
|
+
lastCharacterCountRef.current = message;
|
|
94
|
+
setCharacterCount(message);
|
|
95
|
+
}
|
|
96
|
+
if (overLimit !== lastIsOverLimitRef.current) {
|
|
97
|
+
lastIsOverLimitRef.current = overLimit;
|
|
98
|
+
setIsOverLimit(overLimit);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
onScreenReaderAnnounce: message_0 => {
|
|
102
|
+
if (message_0 !== lastScreenReaderMessageRef.current) {
|
|
103
|
+
lastScreenReaderMessageRef.current = message_0;
|
|
104
|
+
setScreenReaderMessage(message_0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
lastCountedLengthRef.current = null;
|
|
109
|
+
return () => {
|
|
110
|
+
var _characterCounterRef$;
|
|
111
|
+
(_characterCounterRef$ = characterCounterRef.current) === null || _characterCounterRef$ === void 0 ? void 0 : _characterCounterRef$.cleanup();
|
|
112
|
+
characterCounterRef.current = null;
|
|
113
|
+
lastCountedLengthRef.current = null;
|
|
114
|
+
lastCharacterCountRef.current = '';
|
|
115
|
+
lastIsOverLimitRef.current = false;
|
|
116
|
+
lastScreenReaderMessageRef.current = '';
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}, [characterLimit]);
|
|
120
|
+
|
|
121
|
+
// Update character count when value changes or on mount
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (characterLimit && characterCounterRef.current) {
|
|
124
|
+
const currentValue = value !== undefined ? String(value) : defaultValue !== undefined ? String(defaultValue) : '';
|
|
125
|
+
const currentLength = currentValue.length;
|
|
126
|
+
if (currentLength !== lastCountedLengthRef.current) {
|
|
127
|
+
lastCountedLengthRef.current = currentLength;
|
|
128
|
+
characterCounterRef.current.updateCharacterCount(currentLength, characterLimit);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, [value, defaultValue, characterLimit]);
|
|
132
|
+
|
|
95
133
|
// Handle input change with character counter
|
|
96
134
|
const handleInputChange = useCallback(e_2 => {
|
|
97
|
-
if (characterLimit &&
|
|
98
|
-
|
|
135
|
+
if (characterLimit && characterCounterRef.current) {
|
|
136
|
+
const currentLength_0 = e_2.target.value.length;
|
|
137
|
+
if (currentLength_0 !== lastCountedLengthRef.current) {
|
|
138
|
+
lastCountedLengthRef.current = currentLength_0;
|
|
139
|
+
characterCounterRef.current.updateCharacterCount(currentLength_0, characterLimit);
|
|
140
|
+
}
|
|
99
141
|
}
|
|
100
142
|
onChange === null || onChange === void 0 ? void 0 : onChange(e_2);
|
|
101
|
-
}, [onChange, characterLimit
|
|
143
|
+
}, [onChange, characterLimit]);
|
|
102
144
|
const characterCountId = useId();
|
|
103
145
|
const characterCountStaticMessageId = useId();
|
|
104
146
|
const isValid = isOverLimit ? 'error' : validationStatus;
|
|
@@ -160,11 +202,10 @@ const TextInput = /*#__PURE__*/React.forwardRef(({
|
|
|
160
202
|
children: typeof TrailingVisual !== 'string' && isValidElementType(TrailingVisual) ? /*#__PURE__*/jsx(TrailingVisual, {}) : TrailingVisual
|
|
161
203
|
}), trailingAction]
|
|
162
204
|
}), characterLimit && /*#__PURE__*/jsxs(Fragment, {
|
|
163
|
-
children: [/*#__PURE__*/jsx(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
children: counter === null || counter === void 0 ? void 0 : counter.message
|
|
205
|
+
children: [/*#__PURE__*/jsx(VisuallyHidden, {
|
|
206
|
+
"aria-live": "polite",
|
|
207
|
+
role: "status",
|
|
208
|
+
children: screenReaderMessage
|
|
168
209
|
}), /*#__PURE__*/jsxs(VisuallyHidden, {
|
|
169
210
|
id: characterCountStaticMessageId,
|
|
170
211
|
children: ["You can enter up to ", characterLimit, " ", characterLimit === 1 ? 'character' : 'characters']
|
|
@@ -176,7 +217,7 @@ const TextInput = /*#__PURE__*/React.forwardRef(({
|
|
|
176
217
|
"data-component": "TextInput.CharacterCounter",
|
|
177
218
|
children: [isOverLimit && /*#__PURE__*/jsx(AlertFillIcon, {
|
|
178
219
|
size: 16
|
|
179
|
-
}),
|
|
220
|
+
}), characterCount]
|
|
180
221
|
})]
|
|
181
222
|
})]
|
|
182
223
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../../src/Textarea/Textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAe,MAAM,OAAO,CAAA;AAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../../src/Textarea/Textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAe,MAAM,OAAO,CAAA;AAC/D,OAAO,KAA8C,MAAM,OAAO,CAAA;AAElE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,qCAAqC,CAAA;AAE7E,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAA;AAOlD,eAAO,MAAM,qBAAqB,IAAI,CAAA;AACtC,eAAO,MAAM,qBAAqB,KAAK,CAAA;AACvC,eAAO,MAAM,uBAAuB,SAAS,CAAA;AAE7C,MAAM,MAAM,aAAa,GAAG;IAC1B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;IACvC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAAA;IACpD;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,sBAAsB,CAAC,mBAAmB,CAAC,CAAA;AAE/C;;;GAGG;AACH,QAAA,MAAM,QAAQ;IA/CZ;;OAEG;eACQ,OAAO;IAClB;;OAEG;uBACgB,oBAAoB;IACvC;;OAEG;YACK,OAAO;IACf;;OAEG;aACM,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU;IACpD;;OAEG;eACQ,OAAO;IAClB;;OAEG;gBACS,MAAM;IAClB;;OAEG;gBACS,MAAM;IAClB;;OAEG;gBACS,MAAM;IAClB;;OAEG;YACK,KAAK,CAAC,aAAa;IAC3B;;;OAGG;qBACc,MAAM;2FA2IxB,CAAA;wBAK0B,cAAc,CAAC,OAAO,QAAQ,CAAC;AAA1D,wBAA0D"}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { c } from 'react-compiler-runtime';
|
|
2
|
-
import React, { useId,
|
|
2
|
+
import React, { useRef, useId, useEffect } from 'react';
|
|
3
3
|
import { TextInputBaseWrapper } from '../internal/components/TextInputWrapper.js';
|
|
4
4
|
import classes from './TextArea.module.css.js';
|
|
5
5
|
import { AlertFillIcon } from '@primer/octicons-react';
|
|
6
|
-
import {
|
|
6
|
+
import { CharacterCounter } from '../utils/character-counter.js';
|
|
7
7
|
import VisuallyHidden from '../_VisuallyHidden.js';
|
|
8
|
-
import visuallyHiddenClasses from '../_VisuallyHidden.module.css.js';
|
|
9
8
|
import { clsx } from 'clsx';
|
|
10
9
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
|
-
import { AriaStatus } from '../live-region/AriaStatus.js';
|
|
12
10
|
import Text from '../Text/Text.js';
|
|
13
11
|
|
|
14
12
|
const DEFAULT_TEXTAREA_ROWS = 7;
|
|
@@ -19,7 +17,7 @@ const DEFAULT_TEXTAREA_RESIZE = 'both';
|
|
|
19
17
|
* This component accepts all native HTML <textarea> attributes as props.
|
|
20
18
|
*/
|
|
21
19
|
const Textarea = /*#__PURE__*/React.forwardRef((t0, ref) => {
|
|
22
|
-
const $ = c(
|
|
20
|
+
const $ = c(68);
|
|
23
21
|
let block;
|
|
24
22
|
let characterLimit;
|
|
25
23
|
let className;
|
|
@@ -97,170 +95,200 @@ const Textarea = /*#__PURE__*/React.forwardRef((t0, ref) => {
|
|
|
97
95
|
const rows = t1 === undefined ? DEFAULT_TEXTAREA_ROWS : t1;
|
|
98
96
|
const cols = t2 === undefined ? DEFAULT_TEXTAREA_COLS : t2;
|
|
99
97
|
const resize = t3 === undefined ? DEFAULT_TEXTAREA_RESIZE : t3;
|
|
98
|
+
const [characterCount, setCharacterCount] = React.useState("");
|
|
99
|
+
const [isOverLimit, setIsOverLimit] = React.useState(false);
|
|
100
|
+
const [screenReaderMessage, setScreenReaderMessage] = React.useState("");
|
|
101
|
+
const characterCounterRef = useRef(null);
|
|
100
102
|
const characterCountId = useId();
|
|
101
103
|
const characterCountStaticMessageId = useId();
|
|
102
|
-
const isControlled = value !== undefined;
|
|
103
104
|
let t4;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
let t5;
|
|
106
|
+
if ($[18] !== characterLimit) {
|
|
107
|
+
t4 = () => {
|
|
108
|
+
if (characterLimit) {
|
|
109
|
+
characterCounterRef.current = new CharacterCounter({
|
|
110
|
+
onCountUpdate: (count, overLimit, message) => {
|
|
111
|
+
setCharacterCount(message);
|
|
112
|
+
setIsOverLimit(overLimit);
|
|
113
|
+
},
|
|
114
|
+
onScreenReaderAnnounce: message_0 => {
|
|
115
|
+
setScreenReaderMessage(message_0);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return () => {
|
|
119
|
+
var _characterCounterRef$;
|
|
120
|
+
(_characterCounterRef$ = characterCounterRef.current) === null || _characterCounterRef$ === void 0 ? void 0 : _characterCounterRef$.cleanup();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
t5 = [characterLimit];
|
|
125
|
+
$[18] = characterLimit;
|
|
107
126
|
$[19] = t4;
|
|
127
|
+
$[20] = t5;
|
|
108
128
|
} else {
|
|
109
129
|
t4 = $[19];
|
|
130
|
+
t5 = $[20];
|
|
110
131
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
let
|
|
114
|
-
if ($[
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
children:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
132
|
+
useEffect(t4, t5);
|
|
133
|
+
let t6;
|
|
134
|
+
let t7;
|
|
135
|
+
if ($[21] !== characterLimit || $[22] !== defaultValue || $[23] !== value) {
|
|
136
|
+
t6 = () => {
|
|
137
|
+
if (characterLimit && characterCounterRef.current) {
|
|
138
|
+
const currentValue = value !== undefined ? String(value) : defaultValue !== undefined ? String(defaultValue) : "";
|
|
139
|
+
characterCounterRef.current.updateCharacterCount(currentValue.length, characterLimit);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
t7 = [value, defaultValue, characterLimit];
|
|
143
|
+
$[21] = characterLimit;
|
|
144
|
+
$[22] = defaultValue;
|
|
145
|
+
$[23] = value;
|
|
146
|
+
$[24] = t6;
|
|
147
|
+
$[25] = t7;
|
|
148
|
+
} else {
|
|
149
|
+
t6 = $[24];
|
|
150
|
+
t7 = $[25];
|
|
151
|
+
}
|
|
152
|
+
useEffect(t6, t7);
|
|
153
|
+
let t8;
|
|
154
|
+
if ($[26] !== characterLimit || $[27] !== onChange) {
|
|
155
|
+
t8 = e => {
|
|
156
|
+
var _onChange;
|
|
157
|
+
if (characterLimit && characterCounterRef.current) {
|
|
158
|
+
characterCounterRef.current.updateCharacterCount(e.target.value.length, characterLimit);
|
|
159
|
+
}
|
|
160
|
+
(_onChange = onChange) === null || _onChange === void 0 ? void 0 : _onChange(e);
|
|
161
|
+
};
|
|
162
|
+
$[26] = characterLimit;
|
|
163
|
+
$[27] = onChange;
|
|
164
|
+
$[28] = t8;
|
|
165
|
+
} else {
|
|
166
|
+
t8 = $[28];
|
|
167
|
+
}
|
|
168
|
+
const handleTextareaChange = t8;
|
|
169
|
+
const isValid = isOverLimit ? "error" : validationStatus;
|
|
170
|
+
const t9 = isValid === "error" ? "true" : "false";
|
|
171
|
+
let t10;
|
|
172
|
+
if ($[29] !== maxHeight || $[30] !== minHeight || $[31] !== style) {
|
|
173
|
+
t10 = {
|
|
174
|
+
minHeight,
|
|
175
|
+
maxHeight,
|
|
176
|
+
...style
|
|
177
|
+
};
|
|
178
|
+
$[29] = maxHeight;
|
|
179
|
+
$[30] = minHeight;
|
|
180
|
+
$[31] = style;
|
|
181
|
+
$[32] = t10;
|
|
182
|
+
} else {
|
|
183
|
+
t10 = $[32];
|
|
184
|
+
}
|
|
185
|
+
let t11;
|
|
186
|
+
if ($[33] !== characterCountStaticMessageId || $[34] !== characterLimit || $[35] !== rest) {
|
|
187
|
+
t11 = characterLimit ? [characterCountStaticMessageId, rest["aria-describedby"]].filter(Boolean).join(" ") || undefined : rest["aria-describedby"];
|
|
188
|
+
$[33] = characterCountStaticMessageId;
|
|
189
|
+
$[34] = characterLimit;
|
|
190
|
+
$[35] = rest;
|
|
191
|
+
$[36] = t11;
|
|
192
|
+
} else {
|
|
193
|
+
t11 = $[36];
|
|
194
|
+
}
|
|
195
|
+
let t12;
|
|
196
|
+
if ($[37] !== cols || $[38] !== defaultValue || $[39] !== disabled || $[40] !== handleTextareaChange || $[41] !== ref || $[42] !== required || $[43] !== resize || $[44] !== rest || $[45] !== rows || $[46] !== t10 || $[47] !== t11 || $[48] !== t9 || $[49] !== value) {
|
|
197
|
+
t12 = /*#__PURE__*/jsx("textarea", {
|
|
198
|
+
value: value,
|
|
199
|
+
defaultValue: defaultValue,
|
|
200
|
+
"data-resize": resize,
|
|
201
|
+
"aria-required": required,
|
|
202
|
+
"aria-invalid": t9,
|
|
203
|
+
ref: ref,
|
|
204
|
+
disabled: disabled,
|
|
205
|
+
rows: rows,
|
|
206
|
+
cols: cols,
|
|
207
|
+
className: classes.TextArea,
|
|
208
|
+
onChange: handleTextareaChange,
|
|
209
|
+
style: t10,
|
|
210
|
+
...rest,
|
|
211
|
+
"aria-describedby": t11
|
|
212
|
+
});
|
|
213
|
+
$[37] = cols;
|
|
214
|
+
$[38] = defaultValue;
|
|
215
|
+
$[39] = disabled;
|
|
216
|
+
$[40] = handleTextareaChange;
|
|
217
|
+
$[41] = ref;
|
|
218
|
+
$[42] = required;
|
|
219
|
+
$[43] = resize;
|
|
220
|
+
$[44] = rest;
|
|
221
|
+
$[45] = rows;
|
|
222
|
+
$[46] = t10;
|
|
223
|
+
$[47] = t11;
|
|
224
|
+
$[48] = t9;
|
|
225
|
+
$[49] = value;
|
|
226
|
+
$[50] = t12;
|
|
227
|
+
} else {
|
|
228
|
+
t12 = $[50];
|
|
229
|
+
}
|
|
230
|
+
let t13;
|
|
231
|
+
if ($[51] !== block || $[52] !== className || $[53] !== contrast || $[54] !== disabled || $[55] !== isValid || $[56] !== t12) {
|
|
232
|
+
t13 = /*#__PURE__*/jsx(TextInputBaseWrapper, {
|
|
233
|
+
validationStatus: isValid,
|
|
234
|
+
disabled: disabled,
|
|
235
|
+
block: block,
|
|
236
|
+
contrast: contrast,
|
|
237
|
+
className: className,
|
|
238
|
+
children: t12
|
|
239
|
+
});
|
|
240
|
+
$[51] = block;
|
|
241
|
+
$[52] = className;
|
|
242
|
+
$[53] = contrast;
|
|
243
|
+
$[54] = disabled;
|
|
244
|
+
$[55] = isValid;
|
|
245
|
+
$[56] = t12;
|
|
246
|
+
$[57] = t13;
|
|
247
|
+
} else {
|
|
248
|
+
t13 = $[57];
|
|
249
|
+
}
|
|
250
|
+
let t14;
|
|
251
|
+
if ($[58] !== characterCount || $[59] !== characterCountId || $[60] !== characterCountStaticMessageId || $[61] !== characterLimit || $[62] !== isOverLimit || $[63] !== screenReaderMessage) {
|
|
252
|
+
t14 = characterLimit && /*#__PURE__*/jsxs(Fragment, {
|
|
253
|
+
children: [/*#__PURE__*/jsx(VisuallyHidden, {
|
|
254
|
+
"aria-live": "polite",
|
|
255
|
+
role: "status",
|
|
256
|
+
children: screenReaderMessage
|
|
257
|
+
}), /*#__PURE__*/jsxs(VisuallyHidden, {
|
|
258
|
+
id: characterCountStaticMessageId,
|
|
259
|
+
children: ["You can enter up to ", characterLimit, " ", characterLimit === 1 ? "character" : "characters"]
|
|
260
|
+
}), /*#__PURE__*/jsxs(Text, {
|
|
261
|
+
"aria-hidden": "true",
|
|
262
|
+
id: characterCountId,
|
|
263
|
+
size: "small",
|
|
264
|
+
className: clsx(classes.CharacterCounter, isOverLimit && classes["CharacterCounter--error"]),
|
|
265
|
+
children: [isOverLimit && /*#__PURE__*/jsx(AlertFillIcon, {
|
|
266
|
+
size: 16
|
|
267
|
+
}), characterCount]
|
|
235
268
|
})]
|
|
236
269
|
});
|
|
237
|
-
$[
|
|
238
|
-
$[
|
|
239
|
-
$[
|
|
240
|
-
$[
|
|
241
|
-
$[
|
|
242
|
-
$[
|
|
243
|
-
$[
|
|
244
|
-
|
|
245
|
-
$[
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
$[
|
|
253
|
-
$[
|
|
254
|
-
$[
|
|
255
|
-
$[38] = rows;
|
|
256
|
-
$[39] = style;
|
|
257
|
-
$[40] = validationStatus;
|
|
258
|
-
$[41] = value;
|
|
259
|
-
$[42] = t5;
|
|
270
|
+
$[58] = characterCount;
|
|
271
|
+
$[59] = characterCountId;
|
|
272
|
+
$[60] = characterCountStaticMessageId;
|
|
273
|
+
$[61] = characterLimit;
|
|
274
|
+
$[62] = isOverLimit;
|
|
275
|
+
$[63] = screenReaderMessage;
|
|
276
|
+
$[64] = t14;
|
|
277
|
+
} else {
|
|
278
|
+
t14 = $[64];
|
|
279
|
+
}
|
|
280
|
+
let t15;
|
|
281
|
+
if ($[65] !== t13 || $[66] !== t14) {
|
|
282
|
+
t15 = /*#__PURE__*/jsxs(Fragment, {
|
|
283
|
+
children: [t13, t14]
|
|
284
|
+
});
|
|
285
|
+
$[65] = t13;
|
|
286
|
+
$[66] = t14;
|
|
287
|
+
$[67] = t15;
|
|
260
288
|
} else {
|
|
261
|
-
|
|
289
|
+
t15 = $[67];
|
|
262
290
|
}
|
|
263
|
-
return
|
|
291
|
+
return t15;
|
|
264
292
|
});
|
|
265
293
|
Textarea.displayName = 'Textarea';
|
|
266
294
|
Textarea.__SLOT__ = Symbol('Textarea');
|
package/dist/_VisuallyHidden.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c } from 'react-compiler-runtime';
|
|
2
|
-
import
|
|
2
|
+
import classes from './_VisuallyHidden.module.css.js';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -36,7 +36,7 @@ function VisuallyHidden(t0) {
|
|
|
36
36
|
let t2;
|
|
37
37
|
if ($[6] !== className || $[7] !== t1) {
|
|
38
38
|
t2 = clsx(className, {
|
|
39
|
-
[
|
|
39
|
+
[classes.InternalVisuallyHidden]: t1
|
|
40
40
|
});
|
|
41
41
|
$[6] = className;
|
|
42
42
|
$[7] = t1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './_VisuallyHidden-1f156b61.css';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var classes = {"InternalVisuallyHidden":"prc-src-InternalVisuallyHidden-2YaI6"};
|
|
4
4
|
|
|
5
|
-
export {
|
|
5
|
+
export { classes as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Announce.d.ts","sourceRoot":"","sources":["../../src/live-region/Announce.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAA;AAEjE,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,KAAK,CAAC,WAAW,IAAI,gBAAgB,CACxE,EAAE,EACF,KAAK,EACL;IACE;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IAExB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAA;CACpC,CACF,CAAA;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,SAAS,KAAK,CAAC,WAAW,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"Announce.d.ts","sourceRoot":"","sources":["../../src/live-region/Announce.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAA;AAEjE,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,KAAK,CAAC,WAAW,IAAI,gBAAgB,CACxE,EAAE,EACF,KAAK,EACL;IACE;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IAExB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAA;CACpC,CACF,CAAA;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,SAAS,KAAK,CAAC,WAAW,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,EAAE,CAAC,qBAwGtF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c } from 'react-compiler-runtime';
|
|
2
2
|
import { announceFromElement } from '@primer/live-region-element';
|
|
3
|
-
import { useRef, useEffect } from 'react';
|
|
3
|
+
import { useRef, useState, useEffect } from 'react';
|
|
4
4
|
import { useEffectOnce } from '../internal/hooks/useEffectOnce.js';
|
|
5
5
|
import { useEffectCallback } from '../internal/hooks/useEffectCallback.js';
|
|
6
6
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -11,7 +11,7 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
11
11
|
* will also announce any changes to the text content of `children`
|
|
12
12
|
*/
|
|
13
13
|
function Announce(props) {
|
|
14
|
-
const $ = c(
|
|
14
|
+
const $ = c(25);
|
|
15
15
|
let children;
|
|
16
16
|
let delayMs;
|
|
17
17
|
let rest;
|
|
@@ -51,10 +51,10 @@ function Announce(props) {
|
|
|
51
51
|
const hidden = t2 === undefined ? false : t2;
|
|
52
52
|
const politeness = t3 === undefined ? "polite" : t3;
|
|
53
53
|
const ref = useRef(null);
|
|
54
|
-
const previousAnnouncementText =
|
|
54
|
+
const [previousAnnouncementText, setPreviousAnnouncementText] = useState(null);
|
|
55
55
|
const savedAnnouncement = useRef(null);
|
|
56
56
|
let t4;
|
|
57
|
-
if ($[8] !== delayMs || $[9] !== hidden || $[10] !== politeness) {
|
|
57
|
+
if ($[8] !== delayMs || $[9] !== hidden || $[10] !== politeness || $[11] !== previousAnnouncementText) {
|
|
58
58
|
t4 = () => {
|
|
59
59
|
var _savedAnnouncement$cu;
|
|
60
60
|
const {
|
|
@@ -70,10 +70,14 @@ function Announce(props) {
|
|
|
70
70
|
if (!textContent) {
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
|
-
if (textContent === previousAnnouncementText
|
|
73
|
+
if (textContent === previousAnnouncementText) {
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
const style = window.getComputedStyle(element);
|
|
77
|
+
if (style.display === "none") {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (style.visibility === "hidden") {
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
83
|
(_savedAnnouncement$cu = savedAnnouncement.current) === null || _savedAnnouncement$cu === void 0 ? void 0 : _savedAnnouncement$cu.cancel();
|
|
@@ -83,33 +87,34 @@ function Announce(props) {
|
|
|
83
87
|
politeness,
|
|
84
88
|
delayMs
|
|
85
89
|
});
|
|
86
|
-
|
|
90
|
+
setPreviousAnnouncementText(textContent);
|
|
87
91
|
};
|
|
88
92
|
$[8] = delayMs;
|
|
89
93
|
$[9] = hidden;
|
|
90
94
|
$[10] = politeness;
|
|
91
|
-
$[11] =
|
|
95
|
+
$[11] = previousAnnouncementText;
|
|
96
|
+
$[12] = t4;
|
|
92
97
|
} else {
|
|
93
|
-
t4 = $[
|
|
98
|
+
t4 = $[12];
|
|
94
99
|
}
|
|
95
100
|
const announce = useEffectCallback(t4);
|
|
96
101
|
let t5;
|
|
97
|
-
if ($[
|
|
102
|
+
if ($[13] !== announce || $[14] !== announceOnShow) {
|
|
98
103
|
t5 = () => {
|
|
99
104
|
if (announceOnShow) {
|
|
100
105
|
announce();
|
|
101
106
|
}
|
|
102
107
|
};
|
|
103
|
-
$[
|
|
104
|
-
$[
|
|
105
|
-
$[
|
|
108
|
+
$[13] = announce;
|
|
109
|
+
$[14] = announceOnShow;
|
|
110
|
+
$[15] = t5;
|
|
106
111
|
} else {
|
|
107
|
-
t5 = $[
|
|
112
|
+
t5 = $[15];
|
|
108
113
|
}
|
|
109
114
|
useEffectOnce(t5);
|
|
110
115
|
let t6;
|
|
111
116
|
let t7;
|
|
112
|
-
if ($[
|
|
117
|
+
if ($[16] !== announce) {
|
|
113
118
|
t6 = () => {
|
|
114
119
|
const {
|
|
115
120
|
current: container
|
|
@@ -130,17 +135,17 @@ function Announce(props) {
|
|
|
130
135
|
};
|
|
131
136
|
};
|
|
132
137
|
t7 = [announce];
|
|
133
|
-
$[
|
|
134
|
-
$[
|
|
135
|
-
$[
|
|
138
|
+
$[16] = announce;
|
|
139
|
+
$[17] = t6;
|
|
140
|
+
$[18] = t7;
|
|
136
141
|
} else {
|
|
137
|
-
t6 = $[
|
|
138
|
-
t7 = $[
|
|
142
|
+
t6 = $[17];
|
|
143
|
+
t7 = $[18];
|
|
139
144
|
}
|
|
140
145
|
useEffect(t6, t7);
|
|
141
146
|
let t8;
|
|
142
147
|
let t9;
|
|
143
|
-
if ($[
|
|
148
|
+
if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
|
|
144
149
|
t8 = () => () => {
|
|
145
150
|
if (savedAnnouncement.current !== null) {
|
|
146
151
|
savedAnnouncement.current.cancel();
|
|
@@ -148,26 +153,26 @@ function Announce(props) {
|
|
|
148
153
|
}
|
|
149
154
|
};
|
|
150
155
|
t9 = [];
|
|
151
|
-
$[
|
|
152
|
-
$[
|
|
156
|
+
$[19] = t8;
|
|
157
|
+
$[20] = t9;
|
|
153
158
|
} else {
|
|
154
|
-
t8 = $[
|
|
155
|
-
t9 = $[
|
|
159
|
+
t8 = $[19];
|
|
160
|
+
t9 = $[20];
|
|
156
161
|
}
|
|
157
162
|
useEffect(t8, t9);
|
|
158
163
|
let t10;
|
|
159
|
-
if ($[
|
|
164
|
+
if ($[21] !== BaseComponent || $[22] !== children || $[23] !== rest) {
|
|
160
165
|
t10 = /*#__PURE__*/jsx(BaseComponent, {
|
|
161
166
|
...rest,
|
|
162
167
|
ref: ref,
|
|
163
168
|
children: children
|
|
164
169
|
});
|
|
165
|
-
$[
|
|
166
|
-
$[
|
|
167
|
-
$[
|
|
168
|
-
$[
|
|
170
|
+
$[21] = BaseComponent;
|
|
171
|
+
$[22] = children;
|
|
172
|
+
$[23] = rest;
|
|
173
|
+
$[24] = t10;
|
|
169
174
|
} else {
|
|
170
|
-
t10 = $[
|
|
175
|
+
t10 = $[24];
|
|
171
176
|
}
|
|
172
177
|
return t10;
|
|
173
178
|
}
|
|
@@ -181,24 +186,4 @@ function getTextContent(element) {
|
|
|
181
186
|
return value ? value.trim() : '';
|
|
182
187
|
}
|
|
183
188
|
|
|
184
|
-
/**
|
|
185
|
-
* Determine if an element is visible (not hidden via `display: none` or
|
|
186
|
-
* `visibility: hidden`). Prefers the native `checkVisibility()` API when
|
|
187
|
-
* available and falls back to `getComputedStyle` for older browsers.
|
|
188
|
-
*/
|
|
189
|
-
function isVisible(element) {
|
|
190
|
-
if (typeof element.checkVisibility === 'function') {
|
|
191
|
-
return element.checkVisibility({
|
|
192
|
-
// `visibilityProperty` is the standard option name (Chrome 116+, Firefox
|
|
193
|
-
// 125+, Safari 17.4+); `checkVisibilityCSS` is the legacy Chromium name
|
|
194
|
-
// (Chrome 105+). Unknown options are ignored, so passing both ensures
|
|
195
|
-
// `visibility: hidden` is treated as not visible across all engines.
|
|
196
|
-
visibilityProperty: true,
|
|
197
|
-
checkVisibilityCSS: true
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
const style = window.getComputedStyle(element);
|
|
201
|
-
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
189
|
export { Announce };
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared character counting
|
|
3
|
-
*
|
|
2
|
+
* Shared character counting functionality for text inputs with character limits.
|
|
3
|
+
* Handles real-time character count updates, validation, and aria-live announcements.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
|
|
5
|
+
export interface CharacterCounterCallbacks {
|
|
6
|
+
onCountUpdate: (count: number, isOverLimit: boolean, message: string) => void;
|
|
7
|
+
onScreenReaderAnnounce: (message: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare class CharacterCounter {
|
|
10
|
+
private announceTimeout;
|
|
11
|
+
private callbacks;
|
|
12
|
+
private isInitialLoad;
|
|
13
|
+
constructor(callbacks: CharacterCounterCallbacks);
|
|
14
|
+
/**
|
|
15
|
+
* Update the character count based on current input value
|
|
16
|
+
*/
|
|
17
|
+
updateCharacterCount(currentLength: number, maxLength: number): void;
|
|
7
18
|
/**
|
|
8
|
-
*
|
|
9
|
-
* characters over the limit when it has been exceeded.
|
|
19
|
+
* Announce character count to screen readers with debouncing
|
|
10
20
|
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
private announceToScreenReader;
|
|
22
|
+
/**
|
|
23
|
+
* Clean up any pending timeouts
|
|
24
|
+
*/
|
|
25
|
+
cleanup(): void;
|
|
16
26
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Compute the character count state for a given length and limit. This is a pure
|
|
19
|
-
* function so the counter can be derived during render without component state.
|
|
20
|
-
*/
|
|
21
|
-
export declare function getCharacterCountState(currentLength: number, maxLength: number): CharacterCountState;
|
|
22
27
|
//# sourceMappingURL=character-counter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"character-counter.d.ts","sourceRoot":"","sources":["../../src/utils/character-counter.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"character-counter.d.ts","sourceRoot":"","sources":["../../src/utils/character-counter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7E,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClD;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,aAAa,CAAgB;gBAEzB,SAAS,EAAE,yBAAyB;IAIhD;;OAEG;IACH,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IA0BpE;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAc9B;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB"}
|
|
@@ -1,30 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared character counting
|
|
3
|
-
*
|
|
2
|
+
* Shared character counting functionality for text inputs with character limits.
|
|
3
|
+
* Handles real-time character count updates, validation, and aria-live announcements.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const SCREEN_READER_DELAY = 500;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
class CharacterCounter {
|
|
8
|
+
announceTimeout = null;
|
|
9
|
+
isInitialLoad = true;
|
|
10
|
+
constructor(callbacks) {
|
|
11
|
+
this.callbacks = callbacks;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Update the character count based on current input value
|
|
16
|
+
*/
|
|
17
|
+
updateCharacterCount(currentLength, maxLength) {
|
|
18
|
+
const charactersRemaining = maxLength - currentLength;
|
|
19
|
+
// eslint-disable-next-line no-useless-assignment
|
|
20
|
+
let message = '';
|
|
21
|
+
if (charactersRemaining >= 0) {
|
|
22
|
+
const characterText = charactersRemaining === 1 ? 'character' : 'characters';
|
|
23
|
+
message = `${charactersRemaining} ${characterText} remaining`;
|
|
24
|
+
this.callbacks.onCountUpdate(charactersRemaining, false, message);
|
|
25
|
+
} else {
|
|
26
|
+
const charactersOver = -charactersRemaining;
|
|
27
|
+
const characterText = charactersOver === 1 ? 'character' : 'characters';
|
|
28
|
+
message = `${charactersOver} ${characterText} over`;
|
|
29
|
+
this.callbacks.onCountUpdate(charactersOver, true, message);
|
|
30
|
+
}
|
|
31
|
+
if (!this.isInitialLoad) {
|
|
32
|
+
this.announceToScreenReader(message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// After first update, set isInitialLoad to false
|
|
36
|
+
if (this.isInitialLoad) {
|
|
37
|
+
this.isInitialLoad = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Announce character count to screen readers with debouncing
|
|
43
|
+
*/
|
|
44
|
+
announceToScreenReader(message) {
|
|
45
|
+
if (this.announceTimeout) {
|
|
46
|
+
clearTimeout(this.announceTimeout);
|
|
47
|
+
}
|
|
48
|
+
if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.announceTimeout = window.setTimeout(() => {
|
|
52
|
+
this.callbacks.onScreenReaderAnnounce(message);
|
|
53
|
+
}, SCREEN_READER_DELAY);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clean up any pending timeouts
|
|
58
|
+
*/
|
|
59
|
+
cleanup() {
|
|
60
|
+
if (this.announceTimeout) {
|
|
61
|
+
clearTimeout(this.announceTimeout);
|
|
62
|
+
}
|
|
20
63
|
}
|
|
21
|
-
const charactersOver = -charactersRemaining;
|
|
22
|
-
const characterText = charactersOver === 1 ? 'character' : 'characters';
|
|
23
|
-
return {
|
|
24
|
-
count: charactersOver,
|
|
25
|
-
isOverLimit: true,
|
|
26
|
-
message: `${charactersOver} ${characterText} over`
|
|
27
|
-
};
|
|
28
64
|
}
|
|
29
65
|
|
|
30
|
-
export {
|
|
66
|
+
export { CharacterCounter };
|
package/package.json
CHANGED