carbon-react 120.6.1 → 121.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) 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/breadcrumbs/crumb/crumb.component.js +1 -0
  6. package/esm/components/date/date.component.d.ts +1 -1
  7. package/esm/components/grouped-character/grouped-character.component.js +0 -1
  8. package/esm/components/link/link.component.js +9 -4
  9. package/esm/components/link/link.style.d.ts +1 -0
  10. package/esm/components/link/link.style.js +15 -16
  11. package/esm/components/menu/menu-item/menu-item.style.js +1 -15
  12. package/esm/components/number/number.component.js +0 -1
  13. package/esm/components/textarea/textarea.component.d.ts +0 -2
  14. package/esm/components/textarea/textarea.component.js +6 -11
  15. package/esm/components/textbox/textbox.component.d.ts +0 -2
  16. package/esm/components/textbox/textbox.component.js +7 -12
  17. package/esm/components/textbox/textbox.style.js +4 -0
  18. package/esm/hooks/__internal__/useCharacterCount/useCharacterCount.d.ts +1 -6
  19. package/esm/hooks/__internal__/useCharacterCount/useCharacterCount.js +24 -8
  20. package/esm/hooks/__internal__/useDebounce/index.d.ts +1 -0
  21. package/esm/hooks/__internal__/useDebounce/index.js +1 -0
  22. package/esm/hooks/__internal__/useDebounce/useDebounce.d.ts +4 -0
  23. package/esm/hooks/__internal__/useDebounce/useDebounce.js +19 -0
  24. package/esm/locales/en-gb.js +3 -3
  25. package/esm/locales/locale.d.ts +1 -1
  26. package/esm/locales/pl-pl.js +2 -2
  27. package/lib/__internal__/character-count/character-count.component.d.ts +4 -2
  28. package/lib/__internal__/character-count/character-count.component.js +16 -6
  29. package/lib/__internal__/character-count/character-count.style.d.ts +4 -1
  30. package/lib/__internal__/character-count/character-count.style.js +31 -5
  31. package/lib/components/breadcrumbs/crumb/crumb.component.js +1 -0
  32. package/lib/components/date/date.component.d.ts +1 -1
  33. package/lib/components/grouped-character/grouped-character.component.js +0 -1
  34. package/lib/components/link/link.component.js +8 -3
  35. package/lib/components/link/link.style.d.ts +1 -0
  36. package/lib/components/link/link.style.js +15 -16
  37. package/lib/components/menu/menu-item/menu-item.style.js +1 -15
  38. package/lib/components/number/number.component.js +0 -1
  39. package/lib/components/textarea/textarea.component.d.ts +0 -2
  40. package/lib/components/textarea/textarea.component.js +6 -11
  41. package/lib/components/textbox/textbox.component.d.ts +0 -2
  42. package/lib/components/textbox/textbox.component.js +7 -12
  43. package/lib/components/textbox/textbox.style.js +4 -0
  44. package/lib/hooks/__internal__/useCharacterCount/useCharacterCount.d.ts +1 -6
  45. package/lib/hooks/__internal__/useCharacterCount/useCharacterCount.js +23 -7
  46. package/lib/hooks/__internal__/useDebounce/index.d.ts +1 -0
  47. package/lib/hooks/__internal__/useDebounce/index.js +13 -0
  48. package/lib/hooks/__internal__/useDebounce/package.json +6 -0
  49. package/lib/hooks/__internal__/useDebounce/useDebounce.d.ts +4 -0
  50. package/lib/hooks/__internal__/useDebounce/useDebounce.js +26 -0
  51. package/lib/locales/en-gb.js +3 -3
  52. package/lib/locales/locale.d.ts +1 -1
  53. package/lib/locales/pl-pl.js +2 -2
  54. 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 };
@@ -70,6 +70,7 @@ Crumb.propTypes = {
70
70
  "data-component": PropTypes.string,
71
71
  "data-element": PropTypes.string,
72
72
  "data-role": PropTypes.string,
73
+ "hasFocus": PropTypes.bool,
73
74
  "href": PropTypes.string,
74
75
  "isCurrent": PropTypes.bool,
75
76
  "onClick": PropTypes.func,
@@ -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,
@@ -1,5 +1,5 @@
1
1
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
- import React, { useContext, useMemo } from "react";
2
+ import React, { useContext, useMemo, useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import Icon from "../icon";
5
5
  import MenuContext from "../menu/menu.context";
@@ -29,6 +29,7 @@ const Link = /*#__PURE__*/React.forwardRef(({
29
29
  removeAriaLabelOnIcon,
30
30
  ...rest
31
31
  }, ref) => {
32
+ const [hasFocus, setHasFocus] = useState(false);
32
33
  const l = useLocale();
33
34
  const {
34
35
  inMenu
@@ -75,7 +76,9 @@ const Link = /*#__PURE__*/React.forwardRef(({
75
76
  href,
76
77
  rel,
77
78
  "aria-label": ariaLabel,
78
- ...ariaProps
79
+ ...ariaProps,
80
+ onFocus: () => setHasFocus(true),
81
+ onBlur: () => setHasFocus(false)
79
82
  };
80
83
  const buttonProps = {
81
84
  type: "button"
@@ -87,8 +90,7 @@ const Link = /*#__PURE__*/React.forwardRef(({
87
90
  }
88
91
  return /*#__PURE__*/React.createElement(type, type === "button" ? {
89
92
  ...componentProps,
90
- ...buttonProps,
91
- placeholderTabIndex
93
+ ...buttonProps
92
94
  } : {
93
95
  ...componentProps,
94
96
  ...(placeholderTabIndex && href === undefined && !onClick && {
@@ -107,6 +109,8 @@ const Link = /*#__PURE__*/React.forwardRef(({
107
109
  isMenuItem: inMenu
108
110
  }, tagComponent("link", rest), isSkipLink && {
109
111
  "data-element": "skip-link"
112
+ }, {
113
+ hasFocus: hasFocus
110
114
  }), createLinkBasedOnType());
111
115
  });
112
116
  Link.propTypes = {
@@ -162,6 +166,7 @@ Link.propTypes = {
162
166
  "children": PropTypes.node,
163
167
  "className": PropTypes.string,
164
168
  "disabled": PropTypes.bool,
169
+ "hasFocus": PropTypes.bool,
165
170
  "href": PropTypes.string,
166
171
  "icon": PropTypes.oneOf(["accessibility_web", "add", "admin", "alert_on", "alert", "analysis", "app_facebook", "app_instagram", "app_tiktok", "app_twitter", "app_youtube", "apps", "arrow_bottom_right_circle", "arrow_down", "arrow_left_boxed", "arrow_left_right_small", "arrow_left_small", "arrow_left", "arrow_right_small", "arrow_right", "arrow_top_left_circle", "arrow_up", "arrow", "arrows_left_right", "attach", "bank_with_card", "bank", "basket_with_squares", "basket", "bed", "bill_paid", "bin", "block_arrow_right", "blocked_square", "blocked", "bold", "box_arrow_left", "box_arrow_right", "boxed_shapes", "bulk_destroy", "bullet_list_dotted", "bullet_list_numbers", "bullet_list", "business", "calendar_pay_date", "calendar_today", "calendar", "call", "camera", "car_lock", "car_money", "car_repair", "card_view", "card_wallet", "caret_down", "caret_large_down", "caret_large_left", "caret_large_right", "caret_large_up", "caret_left", "caret_right", "caret_up", "cart", "cash", "chart_bar", "chart_line", "chart_pie", "chat_notes", "chat", "check_all", "check_none", "chevron_down_thick", "chevron_down", "chevron_left_thick", "chevron_left", "chevron_right_thick", "chevron_right", "chevron_up_thick", "chevron_up", "circle_with_dots", "circles_connection", "clock", "close", "coins", "collaborate", "computer_clock", "connect_off", "connect", "construction", "contacts", "copy", "create", "credit_card_slash", "credit_card", "cross_circle", "cross", "csv", "dashboard", "delete", "delivery", "disconnect", "disputed", "document_right_align", "document_tick", "document_vertical_lines", "download", "draft", "drag_vertical", "drag", "drill", "dropdown", "duplicate", "edit", "edited", "ellipsis_horizontal", "ellipsis_vertical", "email_switch", "email", "entry", "envelope_dollar", "envelope_euro", "error_square", "error", "euro", "expand", "factory", "favourite_lined", "favourite", "fax", "feedback", "file_excel", "file_generic", "file_image", "file_pdf", "file_word", "files_leaning", "filter_new", "filter", "fit_height", "fit_width", "flag", "folder", "form_refresh", "gift", "go", "graduation_hat", "graph", "grid", "hand_cash_coins", "hand_cash_note", "heart_pulse", "help", "hide", "home", "image", "in_progress", "in_transit", "individual", "info", "intranet", "italic", "job_seeked", "key", "laptop", "ledger_arrow_left", "ledger_arrow_right", "ledger", "lightbulb_off", "lightbulb_on", "like", "link_cloud", "link_on", "link", "list_view", "location", "locked", "logout", "lookup", "marker", "message", "microphone", "minus_large", "minus", "mobile", "money_bag", "none", "old_warning", "palm_tree", "pause_circle", "pause", "pdf", "people_switch", "people", "percentage_boxed", "person_info", "person_tick", "person", "petrol_pump", "phone", "piggy_bank", "plane", "play_circle", "play", "plus_large", "plus", "pound", "print", "progress", "progressed", "protect", "question_hollow", "question_mark", "question", "recruiting", "refresh_clock", "refresh", "remove", "sage_coin", "save", "scan", "search", "send", "services", "settings_old", "settings", "share", "shop", "sort_down", "sort_up", "spanner", "split_container", "split", "square_dot", "squares_nine", "stacked_boxes", "stacked_squares", "submitted", "support_online", "sync", "tag", "talk", "target_man", "target", "theatre_masks", "three_boxes", "tick_circle", "tick_thick", "tick", "true_tick", "u_turn_left", "u_turn_right", "undo", "unlocked", "upload", "uploaded", "video", "view", "volunteering", "warning", "website", "welfare"]),
167
172
  "iconAlign": PropTypes.oneOf(["left", "right"]),
@@ -10,6 +10,7 @@ export interface StyledLinkProps {
10
10
  isDarkBackground?: boolean;
11
11
  /** Allows link styling to be updated for light or dark backgrounds */
12
12
  variant?: Variants;
13
+ hasFocus?: boolean;
13
14
  }
14
15
  interface PrivateStyledLinkProps {
15
16
  hasContent: boolean;
@@ -42,7 +42,8 @@ const StyledLink = styled.span`
42
42
  disabled,
43
43
  variant,
44
44
  isDarkBackground,
45
- isMenuItem
45
+ isMenuItem,
46
+ hasFocus
46
47
  }) => {
47
48
  const colorMapKey = isDarkBackground ? "dark" : "light";
48
49
  const {
@@ -117,21 +118,6 @@ const StyledLink = styled.span`
117
118
  }
118
119
  `}
119
120
  }
120
-
121
- ${!disabled && !theme.focusRedesignOptOut && css`
122
- a,
123
- button {
124
- outline: none;
125
- text-decoration: none;
126
- border-bottom-left-radius: var(--borderRadius000);
127
- border-bottom-right-radius: var(--borderRadius000);
128
- }
129
- &:focus-within {
130
- box-shadow: 0 4px 0 0 var(--colorsUtilityYin090);
131
- border-bottom-left-radius: var(--borderRadius025);
132
- border-bottom-right-radius: var(--borderRadius025);
133
- }
134
- `}
135
121
  `}
136
122
 
137
123
  a,
@@ -173,6 +159,19 @@ const StyledLink = styled.span`
173
159
  `}
174
160
  }
175
161
 
162
+ ${!isSkipLink && !disabled && !theme.focusRedesignOptOut && hasFocus && css`
163
+ a,
164
+ button {
165
+ outline: none;
166
+ text-decoration: none;
167
+ border-bottom-left-radius: var(--borderRadius000);
168
+ border-bottom-right-radius: var(--borderRadius000);
169
+ }
170
+ box-shadow: 0 4px 0 0 var(--colorsUtilityYin090);
171
+ border-bottom-left-radius: var(--borderRadius025);
172
+ border-bottom-right-radius: var(--borderRadius025);
173
+ `}
174
+
176
175
  button {
177
176
  background-color: transparent;
178
177
  border: none;
@@ -7,14 +7,6 @@ import menuConfigVariants from "../menu.config";
7
7
  import Link from "../../link";
8
8
  import addFocusStyling from "../../../style/utils/add-focus-styling";
9
9
  import { baseTheme } from "../../../style/themes";
10
- const overrideLinkFocusStyling = fullScreenView => `
11
- &:focus-within {
12
- box-shadow: none;
13
- a {
14
- background-color: ${fullScreenView ? "var(--colorsComponentsMenuAutumnStandard600)" : "transparent"};
15
- }
16
- }
17
- `;
18
10
  const oldFocusStyling = `
19
11
  box-shadow: inset 0 0 0 var(--borderWidth300) var(--colorsSemanticFocus500);
20
12
  `;
@@ -45,19 +37,13 @@ const StyledMenuItemWrapper = styled.a.attrs({
45
37
  font-weight: 700;
46
38
  height: 40px;
47
39
  position: relative;
48
-
49
- && {
50
- :focus-within > a, > button {
51
- background-color: transparent;
52
- }
53
- }
40
+ box-shadow: none;
54
41
 
55
42
  a,
56
43
  button {
57
44
  cursor: pointer;
58
45
  }
59
46
 
60
- ${overrideLinkFocusStyling(inFullscreenView)}
61
47
  a:focus,
62
48
  button:focus {
63
49
  ${({
@@ -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;