carbon-react 120.6.0 → 121.0.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 (46) hide show
  1. package/esm/__internal__/character-count/character-count.component.d.ts +4 -2
  2. package/esm/__internal__/character-count/character-count.component.js +16 -6
  3. package/esm/__internal__/character-count/character-count.style.d.ts +4 -1
  4. package/esm/__internal__/character-count/character-count.style.js +26 -2
  5. package/esm/components/date/date.component.d.ts +1 -1
  6. package/esm/components/grouped-character/grouped-character.component.js +0 -1
  7. package/esm/components/multi-action-button/multi-action-button.style.js +0 -5
  8. package/esm/components/number/number.component.js +0 -1
  9. package/esm/components/textarea/textarea.component.d.ts +0 -2
  10. package/esm/components/textarea/textarea.component.js +6 -11
  11. package/esm/components/textbox/textbox.component.d.ts +0 -2
  12. package/esm/components/textbox/textbox.component.js +7 -12
  13. package/esm/components/textbox/textbox.style.js +4 -0
  14. package/esm/hooks/__internal__/useCharacterCount/useCharacterCount.d.ts +1 -6
  15. package/esm/hooks/__internal__/useCharacterCount/useCharacterCount.js +24 -8
  16. package/esm/hooks/__internal__/useDebounce/index.d.ts +1 -0
  17. package/esm/hooks/__internal__/useDebounce/index.js +1 -0
  18. package/esm/hooks/__internal__/useDebounce/useDebounce.d.ts +4 -0
  19. package/esm/hooks/__internal__/useDebounce/useDebounce.js +19 -0
  20. package/esm/locales/en-gb.js +3 -3
  21. package/esm/locales/locale.d.ts +1 -1
  22. package/esm/locales/pl-pl.js +2 -2
  23. package/lib/__internal__/character-count/character-count.component.d.ts +4 -2
  24. package/lib/__internal__/character-count/character-count.component.js +16 -6
  25. package/lib/__internal__/character-count/character-count.style.d.ts +4 -1
  26. package/lib/__internal__/character-count/character-count.style.js +31 -5
  27. package/lib/components/date/date.component.d.ts +1 -1
  28. package/lib/components/grouped-character/grouped-character.component.js +0 -1
  29. package/lib/components/multi-action-button/multi-action-button.style.js +0 -5
  30. package/lib/components/number/number.component.js +0 -1
  31. package/lib/components/textarea/textarea.component.d.ts +0 -2
  32. package/lib/components/textarea/textarea.component.js +6 -11
  33. package/lib/components/textbox/textbox.component.d.ts +0 -2
  34. package/lib/components/textbox/textbox.component.js +7 -12
  35. package/lib/components/textbox/textbox.style.js +4 -0
  36. package/lib/hooks/__internal__/useCharacterCount/useCharacterCount.d.ts +1 -6
  37. package/lib/hooks/__internal__/useCharacterCount/useCharacterCount.js +23 -7
  38. package/lib/hooks/__internal__/useDebounce/index.d.ts +1 -0
  39. package/lib/hooks/__internal__/useDebounce/index.js +13 -0
  40. package/lib/hooks/__internal__/useDebounce/package.json +6 -0
  41. package/lib/hooks/__internal__/useDebounce/useDebounce.d.ts +4 -0
  42. package/lib/hooks/__internal__/useDebounce/useDebounce.js +26 -0
  43. package/lib/locales/en-gb.js +3 -3
  44. package/lib/locales/locale.d.ts +1 -1
  45. package/lib/locales/pl-pl.js +2 -2
  46. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  import React from "react";
2
2
  interface CharacterCountProps {
3
3
  value: number;
4
+ debouncedValue?: number;
4
5
  limit: number;
6
+ isDebouncedOverLimit?: boolean;
5
7
  isOverLimit: boolean;
6
- "data-element"?: string;
8
+ visuallyHiddenHintId?: string;
7
9
  }
8
- declare const CharacterCount: ({ value, limit, isOverLimit, "data-element": dataElement, }: CharacterCountProps) => React.JSX.Element;
10
+ declare const CharacterCount: ({ value, debouncedValue, limit, isDebouncedOverLimit, isOverLimit, visuallyHiddenHintId, }: CharacterCountProps) => React.JSX.Element;
9
11
  export default CharacterCount;
@@ -1,21 +1,31 @@
1
1
  import React from "react";
2
2
  import PropTypes from "prop-types";
3
- import StyledCharacterCount from "./character-count.style";
3
+ import { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint } from "./character-count.style";
4
4
  import useLocale from "../../hooks/__internal__/useLocale";
5
5
  const CharacterCount = ({
6
6
  value,
7
+ debouncedValue = value,
7
8
  limit,
9
+ isDebouncedOverLimit,
8
10
  isOverLimit,
9
- "data-element": dataElement
11
+ visuallyHiddenHintId
10
12
  }) => {
11
13
  const limitMinusValue = +limit - +value;
12
14
  const valueMinusLimit = +value - +limit;
15
+ const debouncedLimitMinusValue = +limit - +debouncedValue;
16
+ const debouncedValueMinusLimit = debouncedValue - +limit;
13
17
  const l = useLocale();
14
18
  const getFormatNumber = (rawValue, locale) => new Intl.NumberFormat(locale).format(rawValue);
15
- return /*#__PURE__*/React.createElement(StyledCharacterCount, {
16
- "aria-live": "polite",
19
+ return /*#__PURE__*/React.createElement(StyledCharacterCountWrapper, null, /*#__PURE__*/React.createElement(VisuallyHiddenHint, {
20
+ "data-element": "visually-hidden-hint",
21
+ id: visuallyHiddenHintId
22
+ }, l.characterCount.visuallyHiddenHint(getFormatNumber(limit, l.locale()))), /*#__PURE__*/React.createElement(StyledCharacterCount, {
23
+ "aria-hidden": "true",
17
24
  isOverLimit: isOverLimit,
18
- "data-element": dataElement
19
- }, !isOverLimit ? l.characterCount.charactersLeft(limitMinusValue, getFormatNumber(limitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(valueMinusLimit, getFormatNumber(valueMinusLimit, l.locale())));
25
+ "data-element": "character-count"
26
+ }, !isOverLimit ? l.characterCount.charactersLeft(limitMinusValue, getFormatNumber(limitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(valueMinusLimit, getFormatNumber(valueMinusLimit, l.locale()))), /*#__PURE__*/React.createElement(VisuallyHiddenCharacterCount, {
27
+ "data-element": "visually-hidden-character-count",
28
+ "aria-live": "polite"
29
+ }, !isDebouncedOverLimit ? l.characterCount.charactersLeft(debouncedLimitMinusValue, getFormatNumber(debouncedLimitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(debouncedValueMinusLimit, getFormatNumber(debouncedValueMinusLimit, l.locale()))));
20
30
  };
21
31
  export default CharacterCount;
@@ -1,4 +1,7 @@
1
+ declare const StyledCharacterCountWrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
1
2
  declare const StyledCharacterCount: import("styled-components").StyledComponent<"div", any, {
2
3
  isOverLimit: boolean;
3
4
  }, never>;
4
- export default StyledCharacterCount;
5
+ declare const VisuallyHiddenCharacterCount: import("styled-components").StyledComponent<"div", any, {}, never>;
6
+ declare const VisuallyHiddenHint: import("styled-components").StyledComponent<"div", any, {}, never>;
7
+ export { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint, };
@@ -1,7 +1,13 @@
1
1
  import styled, { css } from "styled-components";
2
2
  import baseTheme from "../../style/themes/base";
3
+ import visuallyHidden from "../../style/utils/visually-hidden";
4
+ const StyledCharacterCountWrapper = styled.div``;
3
5
  const StyledCharacterCount = styled.div`
4
- text-align: right;
6
+ ::after {
7
+ content: " ";
8
+ }
9
+
10
+ text-align: left;
5
11
  font-size: 12px;
6
12
  margin-top: 4px;
7
13
  margin-bottom: 4px;
@@ -15,7 +21,25 @@ const StyledCharacterCount = styled.div`
15
21
  font-weight: 700;
16
22
  `}
17
23
  `;
24
+ const VisuallyHiddenCharacterCount = styled.div`
25
+ ::after {
26
+ content: " ";
27
+ }
28
+
29
+ ${visuallyHidden}
30
+ `;
31
+ const VisuallyHiddenHint = styled.div`
32
+ ::before {
33
+ content: " ";
34
+ }
35
+
36
+ ::after {
37
+ content: " ";
38
+ }
39
+
40
+ ${visuallyHidden}
41
+ `;
18
42
  StyledCharacterCount.defaultProps = {
19
43
  theme: baseTheme
20
44
  };
21
- export default StyledCharacterCount;
45
+ export { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint };
@@ -12,7 +12,7 @@ export interface DateChangeEvent {
12
12
  };
13
13
  };
14
14
  }
15
- export interface DateInputProps extends Omit<TextboxProps, "value" | "formattedValue" | "rawValue" | "onChange" | "onBlur" | "onMouseDown" | "onChangeDeferred" | "deferTimeout" | "children" | "leftChildren" | "placeholder" | "iconOnClick" | "iconOnMouseDown" | "enforceCharacterLimit" | "characterLimit" | "warnOverLimit" | "iconTabIndex" | "inputIcon"> {
15
+ export interface DateInputProps extends Omit<TextboxProps, "value" | "formattedValue" | "rawValue" | "onChange" | "onBlur" | "onMouseDown" | "onChangeDeferred" | "deferTimeout" | "children" | "leftChildren" | "placeholder" | "iconOnClick" | "iconOnMouseDown" | "characterLimit" | "warnOverLimit" | "iconTabIndex" | "inputIcon"> {
16
16
  /** Boolean to allow the input to have an empty value */
17
17
  allowEmptyValue?: boolean;
18
18
  /** Boolean to toggle where DatePicker is rendered in relation to the Date Input */
@@ -203,7 +203,6 @@ GroupedCharacter.propTypes = {
203
203
  "dir": PropTypes.string,
204
204
  "disabled": PropTypes.bool,
205
205
  "draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
206
- "enforceCharacterLimit": PropTypes.bool,
207
206
  "enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
208
207
  "error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
209
208
  "fieldHelp": PropTypes.node,
@@ -82,11 +82,6 @@ const StyledButtonChildrenContainer = styled.div`
82
82
  box-shadow: var(--boxShadow100);
83
83
  border-radius: var(--borderRadius100);
84
84
 
85
- ${StyledIcon} {
86
- margin-left: 0;
87
- left: 8px;
88
- }
89
-
90
85
  ${borderRadiusStyling}
91
86
 
92
87
  ${StyledButton} {
@@ -135,7 +135,6 @@ Number.propTypes = {
135
135
  "dir": PropTypes.string,
136
136
  "disabled": PropTypes.bool,
137
137
  "draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
138
- "enforceCharacterLimit": PropTypes.bool,
139
138
  "enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
140
139
  "error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
141
140
  "fieldHelp": PropTypes.node,
@@ -26,8 +26,6 @@ export interface TextareaProps extends ValidationProps, MarginProps, Omit<Common
26
26
  cols?: number;
27
27
  /** If true, the component will be disabled */
28
28
  disabled?: boolean;
29
- /** Stop the user typing over the characterLimit */
30
- enforceCharacterLimit?: boolean;
31
29
  /** Indicate that error has occurred
32
30
  Pass string to display icon, tooltip and red border
33
31
  Pass true boolean to only display red border */
@@ -28,7 +28,6 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
28
28
  size,
29
29
  children,
30
30
  characterLimit,
31
- enforceCharacterLimit = true,
32
31
  onChange,
33
32
  disabled = false,
34
33
  labelInline,
@@ -119,7 +118,7 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
119
118
  label,
120
119
  fieldHelp
121
120
  });
122
- const [maxLength, characterCount, characterCountHintId, characterCountHint] = useCharacterCount(value, characterLimit, enforceCharacterLimit);
121
+ const [characterCount, visuallyHiddenHintId] = useCharacterCount(value, characterLimit);
123
122
  useEffect(() => {
124
123
  if (rows) {
125
124
  minHeight.current = internalRef?.current?.scrollHeight || 0;
@@ -147,10 +146,8 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
147
146
  }, [expandable]);
148
147
  const hasIconInside = !!(inputIcon || validationId && !validationOnLabel);
149
148
  const hintId = useRef(guid());
150
- const characterCountHintIdValue = characterCount ? characterCountHintId : undefined;
151
- const inputHintIdValue = inputHint ? hintId.current : undefined;
152
- const hintIdValue = characterLimit ? characterCountHintIdValue : inputHintIdValue;
153
- const combinedAriaDescribedBy = [ariaDescribedBy, hintIdValue].filter(Boolean).join(" ");
149
+ const inputHintId = inputHint ? hintId.current : undefined;
150
+ const combinedAriaDescribedBy = [ariaDescribedBy, inputHintId, visuallyHiddenHintId].filter(Boolean).join(" ");
154
151
  const input = /*#__PURE__*/React.createElement(InputPresentation, {
155
152
  size: size,
156
153
  disabled: disabled,
@@ -168,7 +165,6 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
168
165
  name: name,
169
166
  value: value,
170
167
  ref: callbackRef,
171
- maxLength: maxLength,
172
168
  onChange: onChange,
173
169
  disabled: disabled,
174
170
  readOnly: readOnly,
@@ -219,10 +215,10 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
219
215
  useValidationIcon: computeLabelPropValues(validationOnLabel),
220
216
  adaptiveLabelBreakpoint: adaptiveLabelBreakpoint,
221
217
  validationRedesignOptIn: validationRedesignOptIn
222
- }, characterLimit || inputHint ? /*#__PURE__*/React.createElement(StyledInputHint, {
223
- id: hintIdValue,
218
+ }, inputHint ? /*#__PURE__*/React.createElement(StyledInputHint, {
219
+ id: inputHintId,
224
220
  "data-element": "input-hint"
225
- }, characterCountHint || inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/React.createElement(StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/React.createElement(Box, {
221
+ }, inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/React.createElement(StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/React.createElement(Box, {
226
222
  position: "relative"
227
223
  }, /*#__PURE__*/React.createElement(ValidationMessage, {
228
224
  error: error,
@@ -317,7 +313,6 @@ Textarea.propTypes = {
317
313
  "dir": PropTypes.string,
318
314
  "disabled": PropTypes.bool,
319
315
  "draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
320
- "enforceCharacterLimit": PropTypes.bool,
321
316
  "enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
322
317
  "error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
323
318
  "expandable": PropTypes.bool,
@@ -94,8 +94,6 @@ export interface TextboxProps extends CommonTextboxProps {
94
94
  positionedChildren?: React.ReactNode;
95
95
  /** Character limit of the textarea */
96
96
  characterLimit?: number;
97
- /** Stop the user typing over the characterLimit */
98
- enforceCharacterLimit?: boolean;
99
97
  }
100
98
  export declare const Textbox: React.ForwardRefExoticComponent<TextboxProps & React.RefAttributes<HTMLInputElement>>;
101
99
  export default Textbox;
@@ -70,13 +70,13 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
70
70
  "data-component": dataComponent,
71
71
  "data-element": dataElement,
72
72
  "data-role": dataRole,
73
- enforceCharacterLimit = true,
74
73
  characterLimit,
75
74
  helpAriaLabel,
76
75
  ...props
77
76
  }, ref) => {
78
77
  const characterCountValue = typeof value === "string" ? value : "";
79
- const [maxLength, characterCount, characterCountHintId, characterCountHint] = useCharacterCount(characterCountValue, characterLimit, enforceCharacterLimit);
78
+ const [uniqueId, uniqueName] = useUniqueId(id, name);
79
+ const [characterCount, visuallyHiddenHintId] = useCharacterCount(characterCountValue, characterLimit);
80
80
  const {
81
81
  validationRedesignOptIn
82
82
  } = useContext(NewValidationContext);
@@ -84,7 +84,6 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
84
84
  disableErrorBorder
85
85
  } = useContext(NumeralDateContext);
86
86
  const computeLabelPropValues = prop => validationRedesignOptIn ? undefined : prop;
87
- const [uniqueId, uniqueName] = useUniqueId(id, name);
88
87
  if (!deprecateInputRefWarnTriggered && inputRef) {
89
88
  deprecateInputRefWarnTriggered = true;
90
89
  Logger.deprecate("The `inputRef` prop in `Textbox` component is deprecated and will soon be removed. Please use `ref` instead.");
@@ -108,10 +107,8 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
108
107
  fieldHelp
109
108
  });
110
109
  const hintId = useRef(guid());
111
- const characterCountHintIdValue = characterCount ? characterCountHintId : undefined;
112
- const inputHintIdValue = inputHint ? hintId.current : undefined;
113
- const hintIdValue = characterLimit ? characterCountHintIdValue : inputHintIdValue;
114
- const combinedAriaDescribedBy = [ariaDescribedBy, hintIdValue].filter(Boolean).join(" ");
110
+ const inputHintId = inputHint ? hintId.current : undefined;
111
+ const combinedAriaDescribedBy = [ariaDescribedBy, inputHintId, visuallyHiddenHintId].filter(Boolean).join(" ");
115
112
  const hasIconInside = !!(inputIcon || validationId && !validationOnLabel);
116
113
  const input = /*#__PURE__*/React.createElement(InputPresentation, {
117
114
  align: align,
@@ -152,7 +149,6 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
152
149
  placeholder: disabled || readOnly ? "" : placeholder,
153
150
  readOnly: readOnly,
154
151
  value: typeof formattedValue === "string" ? formattedValue : value,
155
- maxLength: maxLength,
156
152
  validationIconId: validationRedesignOptIn ? undefined : validationId
157
153
  }, props)), children, /*#__PURE__*/React.createElement(InputIconToggle, {
158
154
  align: align,
@@ -197,10 +193,10 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
197
193
  "data-element": dataElement,
198
194
  validationIconId: validationRedesignOptIn ? undefined : validationId,
199
195
  validationRedesignOptIn: validationRedesignOptIn
200
- }, filterStyledSystemMarginProps(props)), characterLimit || inputHint ? /*#__PURE__*/React.createElement(StyledInputHint, {
201
- id: hintIdValue,
196
+ }, filterStyledSystemMarginProps(props)), inputHint ? /*#__PURE__*/React.createElement(StyledInputHint, {
197
+ id: inputHintId,
202
198
  "data-element": "input-hint"
203
- }, characterCountHint || inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/React.createElement(StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/React.createElement(Box, {
199
+ }, inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/React.createElement(StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/React.createElement(Box, {
204
200
  position: "relative"
205
201
  }, /*#__PURE__*/React.createElement(ValidationMessage, {
206
202
  error: error,
@@ -295,7 +291,6 @@ Textbox.propTypes = {
295
291
  "dir": PropTypes.string,
296
292
  "disabled": PropTypes.bool,
297
293
  "draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
298
- "enforceCharacterLimit": PropTypes.bool,
299
294
  "enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
300
295
  "error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
301
296
  "fieldHelp": PropTypes.node,
@@ -14,6 +14,10 @@ const ErrorBorder = styled.span`
14
14
  `}
15
15
  `;
16
16
  const StyledInputHint = styled.p`
17
+ ::after {
18
+ content: " ";
19
+ }
20
+
17
21
  display: block;
18
22
  flex: 1;
19
23
  margin-top: -3px;
@@ -1,8 +1,3 @@
1
1
  /// <reference types="react" />
2
- declare const useCharacterCount: (value?: string, characterLimit?: number, enforceCharacterLimit?: boolean) => [
3
- number | undefined,
4
- JSX.Element | null,
5
- string | undefined,
6
- string | null
7
- ];
2
+ declare const useCharacterCount: (value?: string, characterLimit?: number) => [JSX.Element | null, string | undefined];
8
3
  export default useCharacterCount;
@@ -1,11 +1,19 @@
1
- import React, { useMemo, useRef } from "react";
1
+ import React, { useMemo, useRef, useEffect, useState } from "react";
2
2
  import CharacterCount from "../../../__internal__/character-count";
3
- import useLocale from "../useLocale";
4
3
  import guid from "../../../__internal__/utils/helpers/guid";
5
- const useCharacterCount = (value = "", characterLimit, enforceCharacterLimit = true) => {
4
+ import useDebounce from "../useDebounce";
5
+ const useCharacterCount = (value = "", characterLimit) => {
6
6
  const isCharacterLimitValid = typeof characterLimit === "number" && !Number.isNaN(characterLimit);
7
- const l = useLocale();
8
- const hintString = l.characterCount.hintString();
7
+ const [debouncedValue, setDebouncedValue] = useState(value);
8
+ const debounceWaitTime = 2000;
9
+ const updateDebouncedValue = useDebounce(newValue => {
10
+ setDebouncedValue(newValue);
11
+ }, debounceWaitTime);
12
+ useEffect(() => {
13
+ if (characterLimit) {
14
+ updateDebouncedValue(value);
15
+ }
16
+ }, [value, characterLimit, updateDebouncedValue]);
9
17
  const hintId = useRef(guid());
10
18
  const isOverLimit = useMemo(() => {
11
19
  if (value && isCharacterLimitValid) {
@@ -13,11 +21,19 @@ const useCharacterCount = (value = "", characterLimit, enforceCharacterLimit = t
13
21
  }
14
22
  return false;
15
23
  }, [value, characterLimit, isCharacterLimitValid]);
16
- return [enforceCharacterLimit && isCharacterLimitValid ? characterLimit : undefined, isCharacterLimitValid ? /*#__PURE__*/React.createElement(CharacterCount, {
24
+ const isDebouncedOverLimit = useMemo(() => {
25
+ if (debouncedValue && isCharacterLimitValid) {
26
+ return debouncedValue.length > characterLimit;
27
+ }
28
+ return false;
29
+ }, [debouncedValue, characterLimit, isCharacterLimitValid]);
30
+ return [isCharacterLimitValid ? /*#__PURE__*/React.createElement(CharacterCount, {
17
31
  isOverLimit: isOverLimit,
32
+ isDebouncedOverLimit: isDebouncedOverLimit,
18
33
  value: value.length,
34
+ debouncedValue: debouncedValue.length,
19
35
  limit: characterLimit,
20
- "data-element": "character-limit"
21
- }) : null, hintId.current, isCharacterLimitValid ? hintString : null];
36
+ visuallyHiddenHintId: hintId.current
37
+ }) : null, isCharacterLimitValid ? hintId.current : undefined];
22
38
  };
23
39
  export default useCharacterCount;
@@ -0,0 +1 @@
1
+ export { default } from "./useDebounce";
@@ -0,0 +1 @@
1
+ export { default } from "./useDebounce";
@@ -0,0 +1,4 @@
1
+ import { DebouncedFunc } from "lodash";
2
+ declare type Callback = (...args: any[]) => void;
3
+ declare const useDebounce: <T extends Callback>(callback: T, delay: number) => DebouncedFunc<T>;
4
+ export default useDebounce;
@@ -0,0 +1,19 @@
1
+ import { useMemo, useEffect, useRef } from "react";
2
+ import debounce from "lodash/debounce";
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+
6
+ const useDebounce = (callback, delay) => {
7
+ const callbackRef = useRef(callback);
8
+ useEffect(() => {
9
+ callbackRef.current = callback;
10
+ });
11
+ const debouncedCallback = useMemo(() => debounce(callbackRef.current, delay), [delay]);
12
+ useEffect(() => {
13
+ return () => {
14
+ debouncedCallback.cancel();
15
+ };
16
+ });
17
+ return debouncedCallback;
18
+ };
19
+ export default useDebounce;
@@ -25,9 +25,9 @@ const enGB = {
25
25
  yes: () => "Yes"
26
26
  },
27
27
  characterCount: {
28
- hintString: () => "Input contains a character counter",
29
- tooManyCharacters: (count, formattedCount) => count === 1 ? `You have ${formattedCount} character too many` : `You have ${formattedCount} characters too many`,
30
- charactersLeft: (count, formattedCount) => count === 1 ? `You have ${formattedCount} character remaining` : `You have ${formattedCount} characters remaining`
28
+ tooManyCharacters: (count, formattedCount) => count === 1 ? `${formattedCount} character too many` : `${formattedCount} characters too many`,
29
+ charactersLeft: (count, formattedCount) => count === 1 ? `${formattedCount} character left` : `${formattedCount} characters left`,
30
+ visuallyHiddenHint: formattedCount => `You can enter up to ${formattedCount} characters`
31
31
  },
32
32
  date: {
33
33
  dateFnsLocale: () => enGBDateLocale
@@ -20,9 +20,9 @@ interface Locale {
20
20
  ariaLabel: () => string;
21
21
  };
22
22
  characterCount: {
23
- hintString: () => string;
24
23
  tooManyCharacters: (count: number, formattedCount: string) => string;
25
24
  charactersLeft: (count: number, formattedCount: string) => string;
25
+ visuallyHiddenHint: (formattedCount: string) => string;
26
26
  };
27
27
  confirm: {
28
28
  no: () => string;
@@ -62,9 +62,9 @@ const plPL = {
62
62
  yes: () => "Tak"
63
63
  },
64
64
  characterCount: {
65
- hintString: () => "Pole zawiera licznik znaków",
66
65
  tooManyCharacters: (count, formattedCount) => `Masz o ${formattedCount} ${PolishPlural("znak", "znaki", "znaków", count)} za dużo`,
67
- charactersLeft: (count, formattedCount) => `Masz ${formattedCount} ${PolishPlural("pozostały", "pozostałe", "pozostałych", count)} ${PolishPlural("znak", "znaki", "znaków", count)}`
66
+ charactersLeft: (count, formattedCount) => `Masz ${formattedCount} ${PolishPlural("pozostały", "pozostałe", "pozostałych", count)} ${PolishPlural("znak", "znaki", "znaków", count)}`,
67
+ visuallyHiddenHint: formattedCount => `Można wprowadzić do ${formattedCount} znaków`
68
68
  },
69
69
  date: {
70
70
  dateFnsLocale: () => plDateLocale
@@ -1,9 +1,11 @@
1
1
  import React from "react";
2
2
  interface CharacterCountProps {
3
3
  value: number;
4
+ debouncedValue?: number;
4
5
  limit: number;
6
+ isDebouncedOverLimit?: boolean;
5
7
  isOverLimit: boolean;
6
- "data-element"?: string;
8
+ visuallyHiddenHintId?: string;
7
9
  }
8
- declare const CharacterCount: ({ value, limit, isOverLimit, "data-element": dataElement, }: CharacterCountProps) => React.JSX.Element;
10
+ declare const CharacterCount: ({ value, debouncedValue, limit, isDebouncedOverLimit, isOverLimit, visuallyHiddenHintId, }: CharacterCountProps) => React.JSX.Element;
9
11
  export default CharacterCount;
@@ -6,24 +6,34 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireDefault(require("react"));
8
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
- var _characterCount = _interopRequireDefault(require("./character-count.style"));
9
+ var _characterCount = require("./character-count.style");
10
10
  var _useLocale = _interopRequireDefault(require("../../hooks/__internal__/useLocale"));
11
11
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
12
  const CharacterCount = ({
13
13
  value,
14
+ debouncedValue = value,
14
15
  limit,
16
+ isDebouncedOverLimit,
15
17
  isOverLimit,
16
- "data-element": dataElement
18
+ visuallyHiddenHintId
17
19
  }) => {
18
20
  const limitMinusValue = +limit - +value;
19
21
  const valueMinusLimit = +value - +limit;
22
+ const debouncedLimitMinusValue = +limit - +debouncedValue;
23
+ const debouncedValueMinusLimit = debouncedValue - +limit;
20
24
  const l = (0, _useLocale.default)();
21
25
  const getFormatNumber = (rawValue, locale) => new Intl.NumberFormat(locale).format(rawValue);
22
- return /*#__PURE__*/_react.default.createElement(_characterCount.default, {
23
- "aria-live": "polite",
26
+ return /*#__PURE__*/_react.default.createElement(_characterCount.StyledCharacterCountWrapper, null, /*#__PURE__*/_react.default.createElement(_characterCount.VisuallyHiddenHint, {
27
+ "data-element": "visually-hidden-hint",
28
+ id: visuallyHiddenHintId
29
+ }, l.characterCount.visuallyHiddenHint(getFormatNumber(limit, l.locale()))), /*#__PURE__*/_react.default.createElement(_characterCount.StyledCharacterCount, {
30
+ "aria-hidden": "true",
24
31
  isOverLimit: isOverLimit,
25
- "data-element": dataElement
26
- }, !isOverLimit ? l.characterCount.charactersLeft(limitMinusValue, getFormatNumber(limitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(valueMinusLimit, getFormatNumber(valueMinusLimit, l.locale())));
32
+ "data-element": "character-count"
33
+ }, !isOverLimit ? l.characterCount.charactersLeft(limitMinusValue, getFormatNumber(limitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(valueMinusLimit, getFormatNumber(valueMinusLimit, l.locale()))), /*#__PURE__*/_react.default.createElement(_characterCount.VisuallyHiddenCharacterCount, {
34
+ "data-element": "visually-hidden-character-count",
35
+ "aria-live": "polite"
36
+ }, !isDebouncedOverLimit ? l.characterCount.charactersLeft(debouncedLimitMinusValue, getFormatNumber(debouncedLimitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(debouncedValueMinusLimit, getFormatNumber(debouncedValueMinusLimit, l.locale()))));
27
37
  };
28
38
  var _default = CharacterCount;
29
39
  exports.default = _default;
@@ -1,4 +1,7 @@
1
+ declare const StyledCharacterCountWrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
1
2
  declare const StyledCharacterCount: import("styled-components").StyledComponent<"div", any, {
2
3
  isOverLimit: boolean;
3
4
  }, never>;
4
- export default StyledCharacterCount;
5
+ declare const VisuallyHiddenCharacterCount: import("styled-components").StyledComponent<"div", any, {}, never>;
6
+ declare const VisuallyHiddenHint: import("styled-components").StyledComponent<"div", any, {}, never>;
7
+ export { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint, };
@@ -3,14 +3,21 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = void 0;
6
+ exports.VisuallyHiddenHint = exports.VisuallyHiddenCharacterCount = exports.StyledCharacterCountWrapper = exports.StyledCharacterCount = void 0;
7
7
  var _styledComponents = _interopRequireWildcard(require("styled-components"));
8
8
  var _base = _interopRequireDefault(require("../../style/themes/base"));
9
+ var _visuallyHidden = _interopRequireDefault(require("../../style/utils/visually-hidden"));
9
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
11
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
11
12
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13
+ const StyledCharacterCountWrapper = _styledComponents.default.div``;
14
+ exports.StyledCharacterCountWrapper = StyledCharacterCountWrapper;
12
15
  const StyledCharacterCount = _styledComponents.default.div`
13
- text-align: right;
16
+ ::after {
17
+ content: " ";
18
+ }
19
+
20
+ text-align: left;
14
21
  font-size: 12px;
15
22
  margin-top: 4px;
16
23
  margin-bottom: 4px;
@@ -24,8 +31,27 @@ const StyledCharacterCount = _styledComponents.default.div`
24
31
  font-weight: 700;
25
32
  `}
26
33
  `;
34
+ exports.StyledCharacterCount = StyledCharacterCount;
35
+ const VisuallyHiddenCharacterCount = _styledComponents.default.div`
36
+ ::after {
37
+ content: " ";
38
+ }
39
+
40
+ ${_visuallyHidden.default}
41
+ `;
42
+ exports.VisuallyHiddenCharacterCount = VisuallyHiddenCharacterCount;
43
+ const VisuallyHiddenHint = _styledComponents.default.div`
44
+ ::before {
45
+ content: " ";
46
+ }
47
+
48
+ ::after {
49
+ content: " ";
50
+ }
51
+
52
+ ${_visuallyHidden.default}
53
+ `;
54
+ exports.VisuallyHiddenHint = VisuallyHiddenHint;
27
55
  StyledCharacterCount.defaultProps = {
28
56
  theme: _base.default
29
- };
30
- var _default = StyledCharacterCount;
31
- exports.default = _default;
57
+ };
@@ -12,7 +12,7 @@ export interface DateChangeEvent {
12
12
  };
13
13
  };
14
14
  }
15
- export interface DateInputProps extends Omit<TextboxProps, "value" | "formattedValue" | "rawValue" | "onChange" | "onBlur" | "onMouseDown" | "onChangeDeferred" | "deferTimeout" | "children" | "leftChildren" | "placeholder" | "iconOnClick" | "iconOnMouseDown" | "enforceCharacterLimit" | "characterLimit" | "warnOverLimit" | "iconTabIndex" | "inputIcon"> {
15
+ export interface DateInputProps extends Omit<TextboxProps, "value" | "formattedValue" | "rawValue" | "onChange" | "onBlur" | "onMouseDown" | "onChangeDeferred" | "deferTimeout" | "children" | "leftChildren" | "placeholder" | "iconOnClick" | "iconOnMouseDown" | "characterLimit" | "warnOverLimit" | "iconTabIndex" | "inputIcon"> {
16
16
  /** Boolean to allow the input to have an empty value */
17
17
  allowEmptyValue?: boolean;
18
18
  /** Boolean to toggle where DatePicker is rendered in relation to the Date Input */
@@ -213,7 +213,6 @@ GroupedCharacter.propTypes = {
213
213
  "dir": _propTypes.default.string,
214
214
  "disabled": _propTypes.default.bool,
215
215
  "draggable": _propTypes.default.oneOfType([_propTypes.default.oneOf(["false", "true"]), _propTypes.default.bool]),
216
- "enforceCharacterLimit": _propTypes.default.bool,
217
216
  "enterKeyHint": _propTypes.default.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
218
217
  "error": _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),
219
218
  "fieldHelp": _propTypes.default.node,
@@ -92,11 +92,6 @@ const StyledButtonChildrenContainer = _styledComponents.default.div`
92
92
  box-shadow: var(--boxShadow100);
93
93
  border-radius: var(--borderRadius100);
94
94
 
95
- ${_icon.default} {
96
- margin-left: 0;
97
- left: 8px;
98
- }
99
-
100
95
  ${_splitButtonChildren.borderRadiusStyling}
101
96
 
102
97
  ${_button.default} {
@@ -145,7 +145,6 @@ Number.propTypes = {
145
145
  "dir": _propTypes.default.string,
146
146
  "disabled": _propTypes.default.bool,
147
147
  "draggable": _propTypes.default.oneOfType([_propTypes.default.oneOf(["false", "true"]), _propTypes.default.bool]),
148
- "enforceCharacterLimit": _propTypes.default.bool,
149
148
  "enterKeyHint": _propTypes.default.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
150
149
  "error": _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),
151
150
  "fieldHelp": _propTypes.default.node,
@@ -26,8 +26,6 @@ export interface TextareaProps extends ValidationProps, MarginProps, Omit<Common
26
26
  cols?: number;
27
27
  /** If true, the component will be disabled */
28
28
  disabled?: boolean;
29
- /** Stop the user typing over the characterLimit */
30
- enforceCharacterLimit?: boolean;
31
29
  /** Indicate that error has occurred
32
30
  Pass string to display icon, tooltip and red border
33
31
  Pass true boolean to only display red border */
@@ -37,7 +37,6 @@ const Textarea = /*#__PURE__*/_react.default.forwardRef(({
37
37
  size,
38
38
  children,
39
39
  characterLimit,
40
- enforceCharacterLimit = true,
41
40
  onChange,
42
41
  disabled = false,
43
42
  labelInline,
@@ -128,7 +127,7 @@ const Textarea = /*#__PURE__*/_react.default.forwardRef(({
128
127
  label,
129
128
  fieldHelp
130
129
  });
131
- const [maxLength, characterCount, characterCountHintId, characterCountHint] = (0, _useCharacterCount.default)(value, characterLimit, enforceCharacterLimit);
130
+ const [characterCount, visuallyHiddenHintId] = (0, _useCharacterCount.default)(value, characterLimit);
132
131
  (0, _react.useEffect)(() => {
133
132
  if (rows) {
134
133
  minHeight.current = internalRef?.current?.scrollHeight || 0;
@@ -156,10 +155,8 @@ const Textarea = /*#__PURE__*/_react.default.forwardRef(({
156
155
  }, [expandable]);
157
156
  const hasIconInside = !!(inputIcon || validationId && !validationOnLabel);
158
157
  const hintId = (0, _react.useRef)((0, _guid.default)());
159
- const characterCountHintIdValue = characterCount ? characterCountHintId : undefined;
160
- const inputHintIdValue = inputHint ? hintId.current : undefined;
161
- const hintIdValue = characterLimit ? characterCountHintIdValue : inputHintIdValue;
162
- const combinedAriaDescribedBy = [ariaDescribedBy, hintIdValue].filter(Boolean).join(" ");
158
+ const inputHintId = inputHint ? hintId.current : undefined;
159
+ const combinedAriaDescribedBy = [ariaDescribedBy, inputHintId, visuallyHiddenHintId].filter(Boolean).join(" ");
163
160
  const input = /*#__PURE__*/_react.default.createElement(_input.InputPresentation, {
164
161
  size: size,
165
162
  disabled: disabled,
@@ -177,7 +174,6 @@ const Textarea = /*#__PURE__*/_react.default.forwardRef(({
177
174
  name: name,
178
175
  value: value,
179
176
  ref: callbackRef,
180
- maxLength: maxLength,
181
177
  onChange: onChange,
182
178
  disabled: disabled,
183
179
  readOnly: readOnly,
@@ -228,10 +224,10 @@ const Textarea = /*#__PURE__*/_react.default.forwardRef(({
228
224
  useValidationIcon: computeLabelPropValues(validationOnLabel),
229
225
  adaptiveLabelBreakpoint: adaptiveLabelBreakpoint,
230
226
  validationRedesignOptIn: validationRedesignOptIn
231
- }, characterLimit || inputHint ? /*#__PURE__*/_react.default.createElement(_textbox.StyledInputHint, {
232
- id: hintIdValue,
227
+ }, inputHint ? /*#__PURE__*/_react.default.createElement(_textbox.StyledInputHint, {
228
+ id: inputHintId,
233
229
  "data-element": "input-hint"
234
- }, characterCountHint || inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/_react.default.createElement(_textbox.StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/_react.default.createElement(_box.default, {
230
+ }, inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/_react.default.createElement(_textbox.StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/_react.default.createElement(_box.default, {
235
231
  position: "relative"
236
232
  }, /*#__PURE__*/_react.default.createElement(_validationMessage.default, {
237
233
  error: error,
@@ -327,7 +323,6 @@ Textarea.propTypes = {
327
323
  "dir": _propTypes.default.string,
328
324
  "disabled": _propTypes.default.bool,
329
325
  "draggable": _propTypes.default.oneOfType([_propTypes.default.oneOf(["false", "true"]), _propTypes.default.bool]),
330
- "enforceCharacterLimit": _propTypes.default.bool,
331
326
  "enterKeyHint": _propTypes.default.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
332
327
  "error": _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),
333
328
  "expandable": _propTypes.default.bool,
@@ -94,8 +94,6 @@ export interface TextboxProps extends CommonTextboxProps {
94
94
  positionedChildren?: React.ReactNode;
95
95
  /** Character limit of the textarea */
96
96
  characterLimit?: number;
97
- /** Stop the user typing over the characterLimit */
98
- enforceCharacterLimit?: boolean;
99
97
  }
100
98
  export declare const Textbox: React.ForwardRefExoticComponent<TextboxProps & React.RefAttributes<HTMLInputElement>>;
101
99
  export default Textbox;
@@ -79,13 +79,13 @@ const Textbox = /*#__PURE__*/_react.default.forwardRef(({
79
79
  "data-component": dataComponent,
80
80
  "data-element": dataElement,
81
81
  "data-role": dataRole,
82
- enforceCharacterLimit = true,
83
82
  characterLimit,
84
83
  helpAriaLabel,
85
84
  ...props
86
85
  }, ref) => {
87
86
  const characterCountValue = typeof value === "string" ? value : "";
88
- const [maxLength, characterCount, characterCountHintId, characterCountHint] = (0, _useCharacterCount.default)(characterCountValue, characterLimit, enforceCharacterLimit);
87
+ const [uniqueId, uniqueName] = (0, _useUniqueId.default)(id, name);
88
+ const [characterCount, visuallyHiddenHintId] = (0, _useCharacterCount.default)(characterCountValue, characterLimit);
89
89
  const {
90
90
  validationRedesignOptIn
91
91
  } = (0, _react.useContext)(_carbonProvider.NewValidationContext);
@@ -93,7 +93,6 @@ const Textbox = /*#__PURE__*/_react.default.forwardRef(({
93
93
  disableErrorBorder
94
94
  } = (0, _react.useContext)(_numeralDateContext.default);
95
95
  const computeLabelPropValues = prop => validationRedesignOptIn ? undefined : prop;
96
- const [uniqueId, uniqueName] = (0, _useUniqueId.default)(id, name);
97
96
  if (!deprecateInputRefWarnTriggered && inputRef) {
98
97
  deprecateInputRefWarnTriggered = true;
99
98
  _logger.default.deprecate("The `inputRef` prop in `Textbox` component is deprecated and will soon be removed. Please use `ref` instead.");
@@ -117,10 +116,8 @@ const Textbox = /*#__PURE__*/_react.default.forwardRef(({
117
116
  fieldHelp
118
117
  });
119
118
  const hintId = (0, _react.useRef)((0, _guid.default)());
120
- const characterCountHintIdValue = characterCount ? characterCountHintId : undefined;
121
- const inputHintIdValue = inputHint ? hintId.current : undefined;
122
- const hintIdValue = characterLimit ? characterCountHintIdValue : inputHintIdValue;
123
- const combinedAriaDescribedBy = [ariaDescribedBy, hintIdValue].filter(Boolean).join(" ");
119
+ const inputHintId = inputHint ? hintId.current : undefined;
120
+ const combinedAriaDescribedBy = [ariaDescribedBy, inputHintId, visuallyHiddenHintId].filter(Boolean).join(" ");
124
121
  const hasIconInside = !!(inputIcon || validationId && !validationOnLabel);
125
122
  const input = /*#__PURE__*/_react.default.createElement(_input.InputPresentation, {
126
123
  align: align,
@@ -161,7 +158,6 @@ const Textbox = /*#__PURE__*/_react.default.forwardRef(({
161
158
  placeholder: disabled || readOnly ? "" : placeholder,
162
159
  readOnly: readOnly,
163
160
  value: typeof formattedValue === "string" ? formattedValue : value,
164
- maxLength: maxLength,
165
161
  validationIconId: validationRedesignOptIn ? undefined : validationId
166
162
  }, props)), children, /*#__PURE__*/_react.default.createElement(_inputIconToggle.default, {
167
163
  align: align,
@@ -206,10 +202,10 @@ const Textbox = /*#__PURE__*/_react.default.forwardRef(({
206
202
  "data-element": dataElement,
207
203
  validationIconId: validationRedesignOptIn ? undefined : validationId,
208
204
  validationRedesignOptIn: validationRedesignOptIn
209
- }, (0, _utils.filterStyledSystemMarginProps)(props)), characterLimit || inputHint ? /*#__PURE__*/_react.default.createElement(_textbox.StyledInputHint, {
210
- id: hintIdValue,
205
+ }, (0, _utils.filterStyledSystemMarginProps)(props)), inputHint ? /*#__PURE__*/_react.default.createElement(_textbox.StyledInputHint, {
206
+ id: inputHintId,
211
207
  "data-element": "input-hint"
212
- }, characterCountHint || inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/_react.default.createElement(_textbox.StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/_react.default.createElement(_box.default, {
208
+ }, inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/_react.default.createElement(_textbox.StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/_react.default.createElement(_box.default, {
213
209
  position: "relative"
214
210
  }, /*#__PURE__*/_react.default.createElement(_validationMessage.default, {
215
211
  error: error,
@@ -305,7 +301,6 @@ Textbox.propTypes = {
305
301
  "dir": _propTypes.default.string,
306
302
  "disabled": _propTypes.default.bool,
307
303
  "draggable": _propTypes.default.oneOfType([_propTypes.default.oneOf(["false", "true"]), _propTypes.default.bool]),
308
- "enforceCharacterLimit": _propTypes.default.bool,
309
304
  "enterKeyHint": _propTypes.default.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
310
305
  "error": _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.bool]),
311
306
  "fieldHelp": _propTypes.default.node,
@@ -23,6 +23,10 @@ const ErrorBorder = _styledComponents.default.span`
23
23
  `;
24
24
  exports.ErrorBorder = ErrorBorder;
25
25
  const StyledInputHint = _styledComponents.default.p`
26
+ ::after {
27
+ content: " ";
28
+ }
29
+
26
30
  display: block;
27
31
  flex: 1;
28
32
  margin-top: -3px;
@@ -1,8 +1,3 @@
1
1
  /// <reference types="react" />
2
- declare const useCharacterCount: (value?: string, characterLimit?: number, enforceCharacterLimit?: boolean) => [
3
- number | undefined,
4
- JSX.Element | null,
5
- string | undefined,
6
- string | null
7
- ];
2
+ declare const useCharacterCount: (value?: string, characterLimit?: number) => [JSX.Element | null, string | undefined];
8
3
  export default useCharacterCount;
@@ -6,15 +6,23 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _characterCount = _interopRequireDefault(require("../../../__internal__/character-count"));
9
- var _useLocale = _interopRequireDefault(require("../useLocale"));
10
9
  var _guid = _interopRequireDefault(require("../../../__internal__/utils/helpers/guid"));
10
+ var _useDebounce = _interopRequireDefault(require("../useDebounce"));
11
11
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
12
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
13
13
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
14
- const useCharacterCount = (value = "", characterLimit, enforceCharacterLimit = true) => {
14
+ const useCharacterCount = (value = "", characterLimit) => {
15
15
  const isCharacterLimitValid = typeof characterLimit === "number" && !Number.isNaN(characterLimit);
16
- const l = (0, _useLocale.default)();
17
- const hintString = l.characterCount.hintString();
16
+ const [debouncedValue, setDebouncedValue] = (0, _react.useState)(value);
17
+ const debounceWaitTime = 2000;
18
+ const updateDebouncedValue = (0, _useDebounce.default)(newValue => {
19
+ setDebouncedValue(newValue);
20
+ }, debounceWaitTime);
21
+ (0, _react.useEffect)(() => {
22
+ if (characterLimit) {
23
+ updateDebouncedValue(value);
24
+ }
25
+ }, [value, characterLimit, updateDebouncedValue]);
18
26
  const hintId = (0, _react.useRef)((0, _guid.default)());
19
27
  const isOverLimit = (0, _react.useMemo)(() => {
20
28
  if (value && isCharacterLimitValid) {
@@ -22,12 +30,20 @@ const useCharacterCount = (value = "", characterLimit, enforceCharacterLimit = t
22
30
  }
23
31
  return false;
24
32
  }, [value, characterLimit, isCharacterLimitValid]);
25
- return [enforceCharacterLimit && isCharacterLimitValid ? characterLimit : undefined, isCharacterLimitValid ? /*#__PURE__*/_react.default.createElement(_characterCount.default, {
33
+ const isDebouncedOverLimit = (0, _react.useMemo)(() => {
34
+ if (debouncedValue && isCharacterLimitValid) {
35
+ return debouncedValue.length > characterLimit;
36
+ }
37
+ return false;
38
+ }, [debouncedValue, characterLimit, isCharacterLimitValid]);
39
+ return [isCharacterLimitValid ? /*#__PURE__*/_react.default.createElement(_characterCount.default, {
26
40
  isOverLimit: isOverLimit,
41
+ isDebouncedOverLimit: isDebouncedOverLimit,
27
42
  value: value.length,
43
+ debouncedValue: debouncedValue.length,
28
44
  limit: characterLimit,
29
- "data-element": "character-limit"
30
- }) : null, hintId.current, isCharacterLimitValid ? hintString : null];
45
+ visuallyHiddenHintId: hintId.current
46
+ }) : null, isCharacterLimitValid ? hintId.current : undefined];
31
47
  };
32
48
  var _default = useCharacterCount;
33
49
  exports.default = _default;
@@ -0,0 +1 @@
1
+ export { default } from "./useDebounce";
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "default", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _useDebounce.default;
10
+ }
11
+ });
12
+ var _useDebounce = _interopRequireDefault(require("./useDebounce"));
13
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sideEffects": false,
3
+ "module": "../../../../esm/hooks/__internal__/useDebounce/index.js",
4
+ "main": "./index.js",
5
+ "types": "./index.d.ts"
6
+ }
@@ -0,0 +1,4 @@
1
+ import { DebouncedFunc } from "lodash";
2
+ declare type Callback = (...args: any[]) => void;
3
+ declare const useDebounce: <T extends Callback>(callback: T, delay: number) => DebouncedFunc<T>;
4
+ export default useDebounce;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _debounce = _interopRequireDefault(require("lodash/debounce"));
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+
12
+ const useDebounce = (callback, delay) => {
13
+ const callbackRef = (0, _react.useRef)(callback);
14
+ (0, _react.useEffect)(() => {
15
+ callbackRef.current = callback;
16
+ });
17
+ const debouncedCallback = (0, _react.useMemo)(() => (0, _debounce.default)(callbackRef.current, delay), [delay]);
18
+ (0, _react.useEffect)(() => {
19
+ return () => {
20
+ debouncedCallback.cancel();
21
+ };
22
+ });
23
+ return debouncedCallback;
24
+ };
25
+ var _default = useDebounce;
26
+ exports.default = _default;
@@ -31,9 +31,9 @@ const enGB = {
31
31
  yes: () => "Yes"
32
32
  },
33
33
  characterCount: {
34
- hintString: () => "Input contains a character counter",
35
- tooManyCharacters: (count, formattedCount) => count === 1 ? `You have ${formattedCount} character too many` : `You have ${formattedCount} characters too many`,
36
- charactersLeft: (count, formattedCount) => count === 1 ? `You have ${formattedCount} character remaining` : `You have ${formattedCount} characters remaining`
34
+ tooManyCharacters: (count, formattedCount) => count === 1 ? `${formattedCount} character too many` : `${formattedCount} characters too many`,
35
+ charactersLeft: (count, formattedCount) => count === 1 ? `${formattedCount} character left` : `${formattedCount} characters left`,
36
+ visuallyHiddenHint: formattedCount => `You can enter up to ${formattedCount} characters`
37
37
  },
38
38
  date: {
39
39
  dateFnsLocale: () => _dateFnsLocales.enGB
@@ -20,9 +20,9 @@ interface Locale {
20
20
  ariaLabel: () => string;
21
21
  };
22
22
  characterCount: {
23
- hintString: () => string;
24
23
  tooManyCharacters: (count: number, formattedCount: string) => string;
25
24
  charactersLeft: (count: number, formattedCount: string) => string;
25
+ visuallyHiddenHint: (formattedCount: string) => string;
26
26
  };
27
27
  confirm: {
28
28
  no: () => string;
@@ -69,9 +69,9 @@ const plPL = {
69
69
  yes: () => "Tak"
70
70
  },
71
71
  characterCount: {
72
- hintString: () => "Pole zawiera licznik znaków",
73
72
  tooManyCharacters: (count, formattedCount) => `Masz o ${formattedCount} ${PolishPlural("znak", "znaki", "znaków", count)} za dużo`,
74
- charactersLeft: (count, formattedCount) => `Masz ${formattedCount} ${PolishPlural("pozostały", "pozostałe", "pozostałych", count)} ${PolishPlural("znak", "znaki", "znaków", count)}`
73
+ charactersLeft: (count, formattedCount) => `Masz ${formattedCount} ${PolishPlural("pozostały", "pozostałe", "pozostałych", count)} ${PolishPlural("znak", "znaki", "znaków", count)}`,
74
+ visuallyHiddenHint: formattedCount => `Można wprowadzić do ${formattedCount} znaków`
75
75
  },
76
76
  date: {
77
77
  dateFnsLocale: () => _dateFnsLocales.pl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carbon-react",
3
- "version": "120.6.0",
3
+ "version": "121.0.0",
4
4
  "description": "A library of reusable React components for easily building user interfaces.",
5
5
  "files": [
6
6
  "lib",