carbon-react 114.12.2 → 114.13.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.
@@ -1,8 +1,9 @@
1
- import React, { useState, useRef, useEffect, useMemo, useCallback, createRef } from "react";
1
+ import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { isFragment } from "react-is";
4
4
  import invariant from "invariant";
5
5
  import throttle from "lodash/throttle";
6
+ import { defaultFocusableSelectors } from "../../__internal__/focus-trap/focus-trap-utils";
6
7
  import Event from "../../__internal__/utils/helpers/events";
7
8
  import { StyledAnchorNavigation, StyledNavigation, StyledContent } from "./anchor-navigation.style";
8
9
  import AnchorNavigationItem from "./anchor-navigation-item/anchor-navigation-item.component";
@@ -23,9 +24,6 @@ const AnchorNavigation = ({
23
24
  !hasCorrectItemStructure ? process.env.NODE_ENV !== "production" ? invariant(false, `\`stickyNavigation\` prop in \`AnchorNavigation\` should be a React Fragment that only contains children of type \`${AnchorNavigationItem.displayName}\``) : invariant(false) : void 0;
24
25
  const [selectedIndex, setSelectedIndex] = useState(0);
25
26
  const sectionRefs = useRef(React.Children.map(stickyNavigation.props.children, child => child.props.target));
26
- const anchorRefs = useRef(Array.from({
27
- length: React.Children.count(stickyNavigation.props.children)
28
- }, () => /*#__PURE__*/createRef()));
29
27
  const contentRef = useRef(null);
30
28
  const navigationRef = useRef(null);
31
29
  const isUserScroll = useRef(true);
@@ -51,8 +49,13 @@ const AnchorNavigation = ({
51
49
  if (isUserScroll.current) {
52
50
  setSelectedAnchorBasedOnScroll();
53
51
  } else {
54
- if (isUserScrollTimer.current !== undefined) window.clearTimeout(isUserScrollTimer.current);
55
- isUserScrollTimer.current = setTimeout(() => {
52
+ if (isUserScrollTimer.current !== undefined) {
53
+ window.clearTimeout(isUserScrollTimer.current);
54
+ }
55
+
56
+ isUserScrollTimer.current = setTimeout(
57
+ /* istanbul ignore next */
58
+ () => {
56
59
  isUserScroll.current = true;
57
60
  }, SCROLL_THROTTLE + 50);
58
61
  }
@@ -62,23 +65,30 @@ const AnchorNavigation = ({
62
65
  return () => window.removeEventListener("scroll", scrollHandler, true);
63
66
  }, [scrollHandler]);
64
67
 
65
- const focusFirstFocusableChild = section => {
66
- const defaultFocusableSelectors = 'button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])';
67
- const firstFocusableElement = section.querySelector(defaultFocusableSelectors);
68
-
69
- if (firstFocusableElement) {
70
- firstFocusableElement.focus({
71
- preventScroll: true
72
- });
68
+ const focusSection = section => {
69
+ if (!section.matches(defaultFocusableSelectors)) {
70
+ section.setAttribute("tabindex", "-1");
73
71
  }
72
+
73
+ section.focus({
74
+ preventScroll: true
75
+ });
74
76
  };
75
77
 
76
78
  const scrollToSection = index => {
77
79
  const sectionToScroll = sectionRefs.current[index].current; // istanbul ignore if
78
80
  // function is called only after component is rendered, so ref cannot hold a null value
79
81
 
80
- if (sectionToScroll === null) return;
81
- focusFirstFocusableChild(sectionToScroll); // workaround due to preventScroll focus method option on firefox not working consistently
82
+ if (sectionToScroll === null) return; // ensure section has the appropriate element to remove the default focus styles.
83
+ // Can ignore else branch because there's no harm in setting this to "true" twice (it can't hold any other value),
84
+ // but it's probably more efficient not to.
85
+ // istanbul ignore else
86
+
87
+ if (!sectionToScroll.dataset.carbonAnchornavRef) {
88
+ sectionToScroll.dataset.carbonAnchornavRef = "true";
89
+ }
90
+
91
+ focusSection(sectionToScroll); // workaround due to preventScroll focus method option on firefox not working consistently
82
92
 
83
93
  window.setTimeout(() => {
84
94
  isUserScroll.current = false;
@@ -91,26 +101,13 @@ const AnchorNavigation = ({
91
101
  }, 10);
92
102
  };
93
103
 
94
- const focusNavItem = index => {
95
- var _anchorRefs$current$c;
96
-
97
- const noOfRefs = anchorRefs.current.length;
98
- (_anchorRefs$current$c = anchorRefs.current[(index % noOfRefs + noOfRefs) % noOfRefs].current) === null || _anchorRefs$current$c === void 0 ? void 0 : _anchorRefs$current$c.focus();
99
- };
100
-
101
104
  const handleClick = (event, index) => {
102
105
  event.preventDefault();
103
106
  scrollToSection(index);
104
107
  };
105
108
 
106
109
  const handleKeyDown = (event, index) => {
107
- event.preventDefault();
108
-
109
- if (Event.isUpKey(event)) {
110
- focusNavItem(index - 1);
111
- } else if (Event.isDownKey(event)) {
112
- focusNavItem(index + 1);
113
- } else if (Event.isEnterKey(event) || Event.isSpaceKey(event)) {
110
+ if (Event.isEnterKey(event)) {
114
111
  scrollToSection(index);
115
112
  }
116
113
  };
@@ -122,11 +119,11 @@ const AnchorNavigation = ({
122
119
  ref: navigationRef,
123
120
  "data-element": "anchor-sticky-navigation"
124
121
  }, React.Children.map(stickyNavigation.props.children, (child, index) => /*#__PURE__*/React.cloneElement(child, {
122
+ href: child.props.href || "#",
123
+ // need to pass an href to ensure the link is tabbable by default
125
124
  isSelected: index === selectedIndex,
126
- tabIndex: index === selectedIndex ? 0 : -1,
127
125
  onClick: event => handleClick(event, index),
128
- onKeyDown: event => handleKeyDown(event, index),
129
- ref: anchorRefs.current[index]
126
+ onKeyDown: event => handleKeyDown(event, index)
130
127
  }))), /*#__PURE__*/React.createElement(StyledContent, null, children));
131
128
  };
132
129
 
@@ -15,5 +15,9 @@ const StyledNavigation = styled.ul`
15
15
  const StyledContent = styled.div`
16
16
  flex: 1;
17
17
  margin-left: 32px;
18
+
19
+ [data-carbon-anchornav-ref="true"]:focus {
20
+ outline: none;
21
+ }
18
22
  `;
19
23
  export { StyledAnchorNavigation, StyledNavigation, StyledContent };
@@ -46,13 +46,17 @@ const DatePicker = /*#__PURE__*/React.forwardRef(({
46
46
  const weekdaysLong = useMemo(() => Array.from({
47
47
  length: 7
48
48
  }).map((_, i) => localize.day(i)), [localize]);
49
- const weekdaysShort = useMemo(() => Array.from({
50
- length: 7
51
- }).map((_, i) => localize.day(i, ["de", "pl"].filter(str => l.locale().includes(str)).length ? {
52
- width: "wide"
53
- } : {
54
- width: "abbreviated"
55
- }).substring(0, 3)), [l, localize]);
49
+ const weekdaysShort = useMemo(() => {
50
+ const isGivenLocale = str => l.locale().includes(str);
51
+
52
+ return Array.from({
53
+ length: 7
54
+ }).map((_, i) => localize.day(i, ["de", "pl"].some(isGivenLocale) ? {
55
+ width: "wide"
56
+ } : {
57
+ width: "abbreviated"
58
+ }).substring(0, isGivenLocale("de") ? 2 : 3));
59
+ }, [l, localize]);
56
60
 
57
61
  const handleDayClick = (date, {
58
62
  disabled
@@ -353,11 +353,6 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
353
353
 
354
354
  setTextValue("");
355
355
  const isAlreadySelected = actualValue.findIndex(val => isExpectedValue(val, newValue)) !== -1;
356
-
357
- if (!isAlreadySelected && isControlled.current && onChange) {
358
- onChange(createCustomEvent([...actualValue, newValue]));
359
- }
360
-
361
356
  textboxRef.focus();
362
357
  isMouseDownReported.current = false;
363
358
  updateValue(previousValue => {
@@ -367,7 +362,7 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
367
362
 
368
363
  return [...previousValue, newValue];
369
364
  });
370
- }, [createCustomEvent, onChange, textboxRef, actualValue, updateValue]);
365
+ }, [textboxRef, actualValue, updateValue]);
371
366
 
372
367
  function onSelectListClose() {
373
368
  setOpenState(false);
@@ -15,6 +15,8 @@ var _invariant = _interopRequireDefault(require("invariant"));
15
15
 
16
16
  var _throttle = _interopRequireDefault(require("lodash/throttle"));
17
17
 
18
+ var _focusTrapUtils = require("../../__internal__/focus-trap/focus-trap-utils");
19
+
18
20
  var _events = _interopRequireDefault(require("../../__internal__/utils/helpers/events"));
19
21
 
20
22
  var _anchorNavigation = require("./anchor-navigation.style");
@@ -45,9 +47,6 @@ const AnchorNavigation = ({
45
47
  !hasCorrectItemStructure ? process.env.NODE_ENV !== "production" ? (0, _invariant.default)(false, `\`stickyNavigation\` prop in \`AnchorNavigation\` should be a React Fragment that only contains children of type \`${_anchorNavigationItem.default.displayName}\``) : (0, _invariant.default)(false) : void 0;
46
48
  const [selectedIndex, setSelectedIndex] = (0, _react.useState)(0);
47
49
  const sectionRefs = (0, _react.useRef)(_react.default.Children.map(stickyNavigation.props.children, child => child.props.target));
48
- const anchorRefs = (0, _react.useRef)(Array.from({
49
- length: _react.default.Children.count(stickyNavigation.props.children)
50
- }, () => /*#__PURE__*/(0, _react.createRef)()));
51
50
  const contentRef = (0, _react.useRef)(null);
52
51
  const navigationRef = (0, _react.useRef)(null);
53
52
  const isUserScroll = (0, _react.useRef)(true);
@@ -73,8 +72,13 @@ const AnchorNavigation = ({
73
72
  if (isUserScroll.current) {
74
73
  setSelectedAnchorBasedOnScroll();
75
74
  } else {
76
- if (isUserScrollTimer.current !== undefined) window.clearTimeout(isUserScrollTimer.current);
77
- isUserScrollTimer.current = setTimeout(() => {
75
+ if (isUserScrollTimer.current !== undefined) {
76
+ window.clearTimeout(isUserScrollTimer.current);
77
+ }
78
+
79
+ isUserScrollTimer.current = setTimeout(
80
+ /* istanbul ignore next */
81
+ () => {
78
82
  isUserScroll.current = true;
79
83
  }, SCROLL_THROTTLE + 50);
80
84
  }
@@ -84,23 +88,30 @@ const AnchorNavigation = ({
84
88
  return () => window.removeEventListener("scroll", scrollHandler, true);
85
89
  }, [scrollHandler]);
86
90
 
87
- const focusFirstFocusableChild = section => {
88
- const defaultFocusableSelectors = 'button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])';
89
- const firstFocusableElement = section.querySelector(defaultFocusableSelectors);
90
-
91
- if (firstFocusableElement) {
92
- firstFocusableElement.focus({
93
- preventScroll: true
94
- });
91
+ const focusSection = section => {
92
+ if (!section.matches(_focusTrapUtils.defaultFocusableSelectors)) {
93
+ section.setAttribute("tabindex", "-1");
95
94
  }
95
+
96
+ section.focus({
97
+ preventScroll: true
98
+ });
96
99
  };
97
100
 
98
101
  const scrollToSection = index => {
99
102
  const sectionToScroll = sectionRefs.current[index].current; // istanbul ignore if
100
103
  // function is called only after component is rendered, so ref cannot hold a null value
101
104
 
102
- if (sectionToScroll === null) return;
103
- focusFirstFocusableChild(sectionToScroll); // workaround due to preventScroll focus method option on firefox not working consistently
105
+ if (sectionToScroll === null) return; // ensure section has the appropriate element to remove the default focus styles.
106
+ // Can ignore else branch because there's no harm in setting this to "true" twice (it can't hold any other value),
107
+ // but it's probably more efficient not to.
108
+ // istanbul ignore else
109
+
110
+ if (!sectionToScroll.dataset.carbonAnchornavRef) {
111
+ sectionToScroll.dataset.carbonAnchornavRef = "true";
112
+ }
113
+
114
+ focusSection(sectionToScroll); // workaround due to preventScroll focus method option on firefox not working consistently
104
115
 
105
116
  window.setTimeout(() => {
106
117
  isUserScroll.current = false;
@@ -113,26 +124,13 @@ const AnchorNavigation = ({
113
124
  }, 10);
114
125
  };
115
126
 
116
- const focusNavItem = index => {
117
- var _anchorRefs$current$c;
118
-
119
- const noOfRefs = anchorRefs.current.length;
120
- (_anchorRefs$current$c = anchorRefs.current[(index % noOfRefs + noOfRefs) % noOfRefs].current) === null || _anchorRefs$current$c === void 0 ? void 0 : _anchorRefs$current$c.focus();
121
- };
122
-
123
127
  const handleClick = (event, index) => {
124
128
  event.preventDefault();
125
129
  scrollToSection(index);
126
130
  };
127
131
 
128
132
  const handleKeyDown = (event, index) => {
129
- event.preventDefault();
130
-
131
- if (_events.default.isUpKey(event)) {
132
- focusNavItem(index - 1);
133
- } else if (_events.default.isDownKey(event)) {
134
- focusNavItem(index + 1);
135
- } else if (_events.default.isEnterKey(event) || _events.default.isSpaceKey(event)) {
133
+ if (_events.default.isEnterKey(event)) {
136
134
  scrollToSection(index);
137
135
  }
138
136
  };
@@ -144,11 +142,11 @@ const AnchorNavigation = ({
144
142
  ref: navigationRef,
145
143
  "data-element": "anchor-sticky-navigation"
146
144
  }, _react.default.Children.map(stickyNavigation.props.children, (child, index) => /*#__PURE__*/_react.default.cloneElement(child, {
145
+ href: child.props.href || "#",
146
+ // need to pass an href to ensure the link is tabbable by default
147
147
  isSelected: index === selectedIndex,
148
- tabIndex: index === selectedIndex ? 0 : -1,
149
148
  onClick: event => handleClick(event, index),
150
- onKeyDown: event => handleKeyDown(event, index),
151
- ref: anchorRefs.current[index]
149
+ onKeyDown: event => handleKeyDown(event, index)
152
150
  }))), /*#__PURE__*/_react.default.createElement(_anchorNavigation.StyledContent, null, children));
153
151
  };
154
152
 
@@ -27,5 +27,9 @@ exports.StyledNavigation = StyledNavigation;
27
27
  const StyledContent = _styledComponents.default.div`
28
28
  flex: 1;
29
29
  margin-left: 32px;
30
+
31
+ [data-carbon-anchornav-ref="true"]:focus {
32
+ outline: none;
33
+ }
30
34
  `;
31
35
  exports.StyledContent = StyledContent;
@@ -70,13 +70,17 @@ const DatePicker = /*#__PURE__*/_react.default.forwardRef(({
70
70
  const weekdaysLong = (0, _react.useMemo)(() => Array.from({
71
71
  length: 7
72
72
  }).map((_, i) => localize.day(i)), [localize]);
73
- const weekdaysShort = (0, _react.useMemo)(() => Array.from({
74
- length: 7
75
- }).map((_, i) => localize.day(i, ["de", "pl"].filter(str => l.locale().includes(str)).length ? {
76
- width: "wide"
77
- } : {
78
- width: "abbreviated"
79
- }).substring(0, 3)), [l, localize]);
73
+ const weekdaysShort = (0, _react.useMemo)(() => {
74
+ const isGivenLocale = str => l.locale().includes(str);
75
+
76
+ return Array.from({
77
+ length: 7
78
+ }).map((_, i) => localize.day(i, ["de", "pl"].some(isGivenLocale) ? {
79
+ width: "wide"
80
+ } : {
81
+ width: "abbreviated"
82
+ }).substring(0, isGivenLocale("de") ? 2 : 3));
83
+ }, [l, localize]);
80
84
 
81
85
  const handleDayClick = (date, {
82
86
  disabled
@@ -383,11 +383,6 @@ const MultiSelect = /*#__PURE__*/_react.default.forwardRef(({
383
383
 
384
384
  setTextValue("");
385
385
  const isAlreadySelected = actualValue.findIndex(val => (0, _isExpectedValue.default)(val, newValue)) !== -1;
386
-
387
- if (!isAlreadySelected && isControlled.current && onChange) {
388
- onChange(createCustomEvent([...actualValue, newValue]));
389
- }
390
-
391
386
  textboxRef.focus();
392
387
  isMouseDownReported.current = false;
393
388
  updateValue(previousValue => {
@@ -397,7 +392,7 @@ const MultiSelect = /*#__PURE__*/_react.default.forwardRef(({
397
392
 
398
393
  return [...previousValue, newValue];
399
394
  });
400
- }, [createCustomEvent, onChange, textboxRef, actualValue, updateValue]);
395
+ }, [textboxRef, actualValue, updateValue]);
401
396
 
402
397
  function onSelectListClose() {
403
398
  setOpenState(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carbon-react",
3
- "version": "114.12.2",
3
+ "version": "114.13.1",
4
4
  "description": "A library of reusable React components for easily building user interfaces.",
5
5
  "files": [
6
6
  "lib",