@instructure/ui-table 11.7.2-snapshot-50 → 11.7.2-snapshot-52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -2
- package/es/Table/v1/index.js +94 -34
- package/es/Table/v1/styles.js +8 -0
- package/es/Table/v2/index.js +94 -34
- package/es/Table/v2/styles.js +8 -0
- package/lib/Table/v1/index.js +94 -34
- package/lib/Table/v1/styles.js +8 -0
- package/lib/Table/v2/index.js +94 -34
- package/lib/Table/v2/styles.js +8 -0
- package/package.json +14 -14
- package/src/Table/v1/README.md +2 -24
- package/src/Table/v1/index.tsx +95 -21
- package/src/Table/v1/props.ts +1 -1
- package/src/Table/v1/styles.ts +8 -0
- package/src/Table/v2/README.md +2 -24
- package/src/Table/v2/index.tsx +95 -21
- package/src/Table/v2/props.ts +1 -1
- package/src/Table/v2/styles.ts +8 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Table/v1/index.d.ts +9 -1
- package/types/Table/v1/index.d.ts.map +1 -1
- package/types/Table/v1/props.d.ts +1 -1
- package/types/Table/v1/props.d.ts.map +1 -1
- package/types/Table/v1/styles.d.ts.map +1 -1
- package/types/Table/v2/index.d.ts +9 -1
- package/types/Table/v2/index.d.ts.map +1 -1
- package/types/Table/v2/props.d.ts +1 -1
- package/types/Table/v2/props.d.ts.map +1 -1
- package/types/Table/v2/styles.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [11.7.2-snapshot-
|
|
6
|
+
## [11.7.2-snapshot-52](https://github.com/instructure/instructure-ui/compare/v11.7.1...v11.7.2-snapshot-52) (2026-04-22)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **ui-table:** add screen reader announcements for column sorting ([6b23662](https://github.com/instructure/instructure-ui/commit/6b236623f3818007f17aa73a124ec5627467ece6))
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
|
package/es/Table/v1/index.js
CHANGED
|
@@ -51,21 +51,49 @@ let Table = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
|
|
|
51
51
|
constructor(...args) {
|
|
52
52
|
super(...args);
|
|
53
53
|
this.ref = null;
|
|
54
|
+
// Reference to hidden aria-live region for announcing caption changes to screen readers
|
|
55
|
+
this._liveRegionRef = null;
|
|
56
|
+
// Timeout for delayed announcement (workaround for Safari/VoiceOver caption update bug)
|
|
57
|
+
this._announcementTimeout = void 0;
|
|
54
58
|
this.handleRef = el => {
|
|
55
|
-
|
|
59
|
+
var _this$props$elementRe, _this$props;
|
|
56
60
|
this.ref = el;
|
|
57
|
-
|
|
58
|
-
elementRef(el);
|
|
59
|
-
}
|
|
61
|
+
(_this$props$elementRe = (_this$props = this.props).elementRef) === null || _this$props$elementRe === void 0 ? void 0 : _this$props$elementRe.call(_this$props, el);
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
componentDidMount() {
|
|
63
|
-
var _this$props$makeStyle, _this$
|
|
64
|
-
(_this$props$makeStyle = (_this$
|
|
65
|
+
var _this$props$makeStyle, _this$props2;
|
|
66
|
+
(_this$props$makeStyle = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props2);
|
|
67
|
+
}
|
|
68
|
+
componentDidUpdate(prevProps) {
|
|
69
|
+
var _this$props$makeStyle2, _this$props3;
|
|
70
|
+
(_this$props$makeStyle2 = (_this$props3 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props3);
|
|
71
|
+
// Announce caption changes for screen readers (especially VoiceOver)
|
|
72
|
+
// Safari/VoiceOver has a known bug where dynamic <caption> updates aren't announced,
|
|
73
|
+
// so we use an aria-live region as a workaround
|
|
74
|
+
const prevSortInfo = this.getSortedHeaderInfo(prevProps);
|
|
75
|
+
const currentSortInfo = this.getSortedHeaderInfo(this.props);
|
|
76
|
+
// Only announce if sorting actually changed
|
|
77
|
+
const sortingChanged = (prevSortInfo === null || prevSortInfo === void 0 ? void 0 : prevSortInfo.header) !== (currentSortInfo === null || currentSortInfo === void 0 ? void 0 : currentSortInfo.header) || (prevSortInfo === null || prevSortInfo === void 0 ? void 0 : prevSortInfo.direction) !== (currentSortInfo === null || currentSortInfo === void 0 ? void 0 : currentSortInfo.direction);
|
|
78
|
+
if (sortingChanged && currentSortInfo && this._liveRegionRef) {
|
|
79
|
+
// Clear any pending announcement
|
|
80
|
+
clearTimeout(this._announcementTimeout);
|
|
81
|
+
// Clear the live region first (part of the clear-then-set pattern)
|
|
82
|
+
this._liveRegionRef.textContent = '';
|
|
83
|
+
// Wait 100ms before setting new content to ensure screen readers detect the change
|
|
84
|
+
this._announcementTimeout = setTimeout(() => {
|
|
85
|
+
if (this._liveRegionRef) {
|
|
86
|
+
const currentCaption = this.getCaptionText(this.props);
|
|
87
|
+
// Append non-breaking space (\u00A0) to force Safari/VoiceOver to treat
|
|
88
|
+
// repeated captions as different announcements
|
|
89
|
+
this._liveRegionRef.textContent = currentCaption + '\u00A0';
|
|
90
|
+
}
|
|
91
|
+
}, 100);
|
|
92
|
+
}
|
|
65
93
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(
|
|
94
|
+
componentWillUnmount() {
|
|
95
|
+
// Clean up pending announcement timeout
|
|
96
|
+
clearTimeout(this._announcementTimeout);
|
|
69
97
|
}
|
|
70
98
|
getHeaders() {
|
|
71
99
|
const _Children$toArray = Children.toArray(this.props.children),
|
|
@@ -81,27 +109,64 @@ let Table = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
|
|
|
81
109
|
return colHeader.props.children;
|
|
82
110
|
});
|
|
83
111
|
}
|
|
112
|
+
getSortedHeaderInfo(props) {
|
|
113
|
+
const _Children$toArray5 = Children.toArray(props.children),
|
|
114
|
+
_Children$toArray6 = _slicedToArray(_Children$toArray5, 1),
|
|
115
|
+
headChild = _Children$toArray6[0];
|
|
116
|
+
const _Children$toArray7 = Children.toArray(/*#__PURE__*/isValidElement(headChild) ? headChild.props.children : []),
|
|
117
|
+
_Children$toArray8 = _slicedToArray(_Children$toArray7, 1),
|
|
118
|
+
firstRow = _Children$toArray8[0];
|
|
119
|
+
const colHeaders = Children.toArray(/*#__PURE__*/isValidElement(firstRow) ? firstRow.props.children : []);
|
|
120
|
+
// Find the column with an active sort direction
|
|
121
|
+
for (const colHeader of colHeaders) {
|
|
122
|
+
if (/*#__PURE__*/isValidElement(colHeader) && colHeader.props.sortDirection && colHeader.props.sortDirection !== 'none') {
|
|
123
|
+
var _colHeader$props$chil, _colHeader$props$chil2, _colHeader$props$chil3;
|
|
124
|
+
// Extract header text (may be nested in child components)
|
|
125
|
+
const headerText = typeof colHeader.props.children === 'string' ? colHeader.props.children : (_colHeader$props$chil = (_colHeader$props$chil2 = colHeader.props.children) === null || _colHeader$props$chil2 === void 0 ? void 0 : (_colHeader$props$chil3 = _colHeader$props$chil2.props) === null || _colHeader$props$chil3 === void 0 ? void 0 : _colHeader$props$chil3.children) !== null && _colHeader$props$chil !== void 0 ? _colHeader$props$chil : '';
|
|
126
|
+
return {
|
|
127
|
+
header: headerText,
|
|
128
|
+
direction: colHeader.props.sortDirection
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
getCaptionText(props) {
|
|
135
|
+
const sortInfo = this.getSortedHeaderInfo(props);
|
|
136
|
+
const caption = props.caption;
|
|
137
|
+
if (!sortInfo) return caption;
|
|
138
|
+
const sortText = ` Sorted by ${sortInfo.header} (${sortInfo.direction})`;
|
|
139
|
+
return caption ? caption + sortText : sortText.trim();
|
|
140
|
+
}
|
|
84
141
|
render() {
|
|
85
|
-
const _this$
|
|
86
|
-
margin = _this$
|
|
87
|
-
layout = _this$
|
|
88
|
-
caption = _this$
|
|
89
|
-
children = _this$
|
|
90
|
-
hover = _this$
|
|
91
|
-
styles = _this$
|
|
92
|
-
minWidth = _this$
|
|
142
|
+
const _this$props4 = this.props,
|
|
143
|
+
margin = _this$props4.margin,
|
|
144
|
+
layout = _this$props4.layout,
|
|
145
|
+
caption = _this$props4.caption,
|
|
146
|
+
children = _this$props4.children,
|
|
147
|
+
hover = _this$props4.hover,
|
|
148
|
+
styles = _this$props4.styles,
|
|
149
|
+
minWidth = _this$props4.minWidth;
|
|
93
150
|
const isStacked = layout === 'stacked';
|
|
94
|
-
const
|
|
151
|
+
const captionText = this.getCaptionText(this.props);
|
|
95
152
|
if (!caption) {
|
|
96
153
|
error(false, `[Table] required prop caption is not set.`);
|
|
97
154
|
}
|
|
98
|
-
return
|
|
155
|
+
return _jsxs(TableContext.Provider, {
|
|
99
156
|
value: {
|
|
100
|
-
isStacked
|
|
157
|
+
isStacked,
|
|
101
158
|
hover: hover,
|
|
102
|
-
headers:
|
|
159
|
+
headers: isStacked ? this.getHeaders() : void 0
|
|
103
160
|
},
|
|
104
|
-
children:
|
|
161
|
+
children: [_jsx("div", {
|
|
162
|
+
ref: el => {
|
|
163
|
+
this._liveRegionRef = el;
|
|
164
|
+
},
|
|
165
|
+
"aria-live": "polite",
|
|
166
|
+
"aria-atomic": "true",
|
|
167
|
+
role: "status",
|
|
168
|
+
css: styles === null || styles === void 0 ? void 0 : styles.liveRegion
|
|
169
|
+
}), _jsxs(View
|
|
105
170
|
// All HTML props, except the ones accepted by `View` and `Table`
|
|
106
171
|
, {
|
|
107
172
|
...View.omitViewProps(omitProps(this.props, Table.allowedProps), Table),
|
|
@@ -111,20 +176,15 @@ let Table = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
|
|
|
111
176
|
elementRef: this.handleRef,
|
|
112
177
|
css: styles === null || styles === void 0 ? void 0 : styles.table,
|
|
113
178
|
role: isStacked ? 'table' : void 0,
|
|
114
|
-
"aria-label":
|
|
115
|
-
children: [!isStacked && _jsx("caption", {
|
|
179
|
+
"aria-label": captionText,
|
|
180
|
+
children: [!isStacked && caption && _jsx("caption", {
|
|
116
181
|
children: _jsx(ScreenReaderContent, {
|
|
117
|
-
children:
|
|
182
|
+
children: captionText
|
|
118
183
|
})
|
|
119
|
-
}), Children.map(children, child => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return child;
|
|
126
|
-
})]
|
|
127
|
-
})
|
|
184
|
+
}), Children.map(children, child => /*#__PURE__*/isValidElement(child) ? safeCloneElement(child, {
|
|
185
|
+
key: child.props.name
|
|
186
|
+
}) : child)]
|
|
187
|
+
})]
|
|
128
188
|
});
|
|
129
189
|
}
|
|
130
190
|
}, _Table.displayName = "Table", _Table.componentId = 'Table', _Table.allowedProps = allowedProps, _Table.defaultProps = {
|
package/es/Table/v1/styles.js
CHANGED
|
@@ -52,6 +52,14 @@ const generateStyle = (componentTheme, props) => {
|
|
|
52
52
|
caption: {
|
|
53
53
|
textAlign: 'start'
|
|
54
54
|
}
|
|
55
|
+
},
|
|
56
|
+
liveRegion: {
|
|
57
|
+
label: 'table__liveRegion',
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
left: '-10000px',
|
|
60
|
+
width: '1px',
|
|
61
|
+
height: '1px',
|
|
62
|
+
overflow: 'hidden'
|
|
55
63
|
}
|
|
56
64
|
};
|
|
57
65
|
};
|
package/es/Table/v2/index.js
CHANGED
|
@@ -50,21 +50,49 @@ let Table = (_dec = withStyle(generateStyle), _dec(_class = (_Table = class Tabl
|
|
|
50
50
|
constructor(...args) {
|
|
51
51
|
super(...args);
|
|
52
52
|
this.ref = null;
|
|
53
|
+
// Reference to hidden aria-live region for announcing caption changes to screen readers
|
|
54
|
+
this._liveRegionRef = null;
|
|
55
|
+
// Timeout for delayed announcement (workaround for Safari/VoiceOver caption update bug)
|
|
56
|
+
this._announcementTimeout = void 0;
|
|
53
57
|
this.handleRef = el => {
|
|
54
|
-
|
|
58
|
+
var _this$props$elementRe, _this$props;
|
|
55
59
|
this.ref = el;
|
|
56
|
-
|
|
57
|
-
elementRef(el);
|
|
58
|
-
}
|
|
60
|
+
(_this$props$elementRe = (_this$props = this.props).elementRef) === null || _this$props$elementRe === void 0 ? void 0 : _this$props$elementRe.call(_this$props, el);
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
componentDidMount() {
|
|
62
|
-
var _this$props$makeStyle, _this$
|
|
63
|
-
(_this$props$makeStyle = (_this$
|
|
64
|
+
var _this$props$makeStyle, _this$props2;
|
|
65
|
+
(_this$props$makeStyle = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props2);
|
|
66
|
+
}
|
|
67
|
+
componentDidUpdate(prevProps) {
|
|
68
|
+
var _this$props$makeStyle2, _this$props3;
|
|
69
|
+
(_this$props$makeStyle2 = (_this$props3 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props3);
|
|
70
|
+
// Announce caption changes for screen readers (especially VoiceOver)
|
|
71
|
+
// Safari/VoiceOver has a known bug where dynamic <caption> updates aren't announced,
|
|
72
|
+
// so we use an aria-live region as a workaround
|
|
73
|
+
const prevSortInfo = this.getSortedHeaderInfo(prevProps);
|
|
74
|
+
const currentSortInfo = this.getSortedHeaderInfo(this.props);
|
|
75
|
+
// Only announce if sorting actually changed
|
|
76
|
+
const sortingChanged = (prevSortInfo === null || prevSortInfo === void 0 ? void 0 : prevSortInfo.header) !== (currentSortInfo === null || currentSortInfo === void 0 ? void 0 : currentSortInfo.header) || (prevSortInfo === null || prevSortInfo === void 0 ? void 0 : prevSortInfo.direction) !== (currentSortInfo === null || currentSortInfo === void 0 ? void 0 : currentSortInfo.direction);
|
|
77
|
+
if (sortingChanged && currentSortInfo && this._liveRegionRef) {
|
|
78
|
+
// Clear any pending announcement
|
|
79
|
+
clearTimeout(this._announcementTimeout);
|
|
80
|
+
// Clear the live region first (part of the clear-then-set pattern)
|
|
81
|
+
this._liveRegionRef.textContent = '';
|
|
82
|
+
// Wait 100ms before setting new content to ensure screen readers detect the change
|
|
83
|
+
this._announcementTimeout = setTimeout(() => {
|
|
84
|
+
if (this._liveRegionRef) {
|
|
85
|
+
const currentCaption = this.getCaptionText(this.props);
|
|
86
|
+
// Append non-breaking space (\u00A0) to force Safari/VoiceOver to treat
|
|
87
|
+
// repeated captions as different announcements
|
|
88
|
+
this._liveRegionRef.textContent = currentCaption + '\u00A0';
|
|
89
|
+
}
|
|
90
|
+
}, 100);
|
|
91
|
+
}
|
|
64
92
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
(
|
|
93
|
+
componentWillUnmount() {
|
|
94
|
+
// Clean up pending announcement timeout
|
|
95
|
+
clearTimeout(this._announcementTimeout);
|
|
68
96
|
}
|
|
69
97
|
getHeaders() {
|
|
70
98
|
const _Children$toArray = Children.toArray(this.props.children),
|
|
@@ -80,27 +108,64 @@ let Table = (_dec = withStyle(generateStyle), _dec(_class = (_Table = class Tabl
|
|
|
80
108
|
return colHeader.props.children;
|
|
81
109
|
});
|
|
82
110
|
}
|
|
111
|
+
getSortedHeaderInfo(props) {
|
|
112
|
+
const _Children$toArray5 = Children.toArray(props.children),
|
|
113
|
+
_Children$toArray6 = _slicedToArray(_Children$toArray5, 1),
|
|
114
|
+
headChild = _Children$toArray6[0];
|
|
115
|
+
const _Children$toArray7 = Children.toArray(/*#__PURE__*/isValidElement(headChild) ? headChild.props.children : []),
|
|
116
|
+
_Children$toArray8 = _slicedToArray(_Children$toArray7, 1),
|
|
117
|
+
firstRow = _Children$toArray8[0];
|
|
118
|
+
const colHeaders = Children.toArray(/*#__PURE__*/isValidElement(firstRow) ? firstRow.props.children : []);
|
|
119
|
+
// Find the column with an active sort direction
|
|
120
|
+
for (const colHeader of colHeaders) {
|
|
121
|
+
if (/*#__PURE__*/isValidElement(colHeader) && colHeader.props.sortDirection && colHeader.props.sortDirection !== 'none') {
|
|
122
|
+
var _colHeader$props$chil, _colHeader$props$chil2, _colHeader$props$chil3;
|
|
123
|
+
// Extract header text (may be nested in child components)
|
|
124
|
+
const headerText = typeof colHeader.props.children === 'string' ? colHeader.props.children : (_colHeader$props$chil = (_colHeader$props$chil2 = colHeader.props.children) === null || _colHeader$props$chil2 === void 0 ? void 0 : (_colHeader$props$chil3 = _colHeader$props$chil2.props) === null || _colHeader$props$chil3 === void 0 ? void 0 : _colHeader$props$chil3.children) !== null && _colHeader$props$chil !== void 0 ? _colHeader$props$chil : '';
|
|
125
|
+
return {
|
|
126
|
+
header: headerText,
|
|
127
|
+
direction: colHeader.props.sortDirection
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
getCaptionText(props) {
|
|
134
|
+
const sortInfo = this.getSortedHeaderInfo(props);
|
|
135
|
+
const caption = props.caption;
|
|
136
|
+
if (!sortInfo) return caption;
|
|
137
|
+
const sortText = ` Sorted by ${sortInfo.header} (${sortInfo.direction})`;
|
|
138
|
+
return caption ? caption + sortText : sortText.trim();
|
|
139
|
+
}
|
|
83
140
|
render() {
|
|
84
|
-
const _this$
|
|
85
|
-
margin = _this$
|
|
86
|
-
layout = _this$
|
|
87
|
-
caption = _this$
|
|
88
|
-
children = _this$
|
|
89
|
-
hover = _this$
|
|
90
|
-
styles = _this$
|
|
91
|
-
minWidth = _this$
|
|
141
|
+
const _this$props4 = this.props,
|
|
142
|
+
margin = _this$props4.margin,
|
|
143
|
+
layout = _this$props4.layout,
|
|
144
|
+
caption = _this$props4.caption,
|
|
145
|
+
children = _this$props4.children,
|
|
146
|
+
hover = _this$props4.hover,
|
|
147
|
+
styles = _this$props4.styles,
|
|
148
|
+
minWidth = _this$props4.minWidth;
|
|
92
149
|
const isStacked = layout === 'stacked';
|
|
93
|
-
const
|
|
150
|
+
const captionText = this.getCaptionText(this.props);
|
|
94
151
|
if (!caption) {
|
|
95
152
|
error(false, `[Table] required prop caption is not set.`);
|
|
96
153
|
}
|
|
97
|
-
return
|
|
154
|
+
return _jsxs(TableContext.Provider, {
|
|
98
155
|
value: {
|
|
99
|
-
isStacked
|
|
156
|
+
isStacked,
|
|
100
157
|
hover: hover,
|
|
101
|
-
headers:
|
|
158
|
+
headers: isStacked ? this.getHeaders() : void 0
|
|
102
159
|
},
|
|
103
|
-
children:
|
|
160
|
+
children: [_jsx("div", {
|
|
161
|
+
ref: el => {
|
|
162
|
+
this._liveRegionRef = el;
|
|
163
|
+
},
|
|
164
|
+
"aria-live": "polite",
|
|
165
|
+
"aria-atomic": "true",
|
|
166
|
+
role: "status",
|
|
167
|
+
css: styles === null || styles === void 0 ? void 0 : styles.liveRegion
|
|
168
|
+
}), _jsxs(View
|
|
104
169
|
// All HTML props, except the ones accepted by `View` and `Table`
|
|
105
170
|
, {
|
|
106
171
|
...View.omitViewProps(omitProps(this.props, Table.allowedProps), Table),
|
|
@@ -110,20 +175,15 @@ let Table = (_dec = withStyle(generateStyle), _dec(_class = (_Table = class Tabl
|
|
|
110
175
|
elementRef: this.handleRef,
|
|
111
176
|
css: styles === null || styles === void 0 ? void 0 : styles.table,
|
|
112
177
|
role: isStacked ? 'table' : void 0,
|
|
113
|
-
"aria-label":
|
|
114
|
-
children: [!isStacked && _jsx("caption", {
|
|
178
|
+
"aria-label": captionText,
|
|
179
|
+
children: [!isStacked && caption && _jsx("caption", {
|
|
115
180
|
children: _jsx(ScreenReaderContent, {
|
|
116
|
-
children:
|
|
181
|
+
children: captionText
|
|
117
182
|
})
|
|
118
|
-
}), Children.map(children, child => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
return child;
|
|
125
|
-
})]
|
|
126
|
-
})
|
|
183
|
+
}), Children.map(children, child => /*#__PURE__*/isValidElement(child) ? safeCloneElement(child, {
|
|
184
|
+
key: child.props.name
|
|
185
|
+
}) : child)]
|
|
186
|
+
})]
|
|
127
187
|
});
|
|
128
188
|
}
|
|
129
189
|
}, _Table.displayName = "Table", _Table.componentId = 'Table', _Table.allowedProps = allowedProps, _Table.defaultProps = {
|
package/es/Table/v2/styles.js
CHANGED
|
@@ -52,6 +52,14 @@ const generateStyle = (componentTheme, props, _sharedTokens) => {
|
|
|
52
52
|
caption: {
|
|
53
53
|
textAlign: 'start'
|
|
54
54
|
}
|
|
55
|
+
},
|
|
56
|
+
liveRegion: {
|
|
57
|
+
label: 'table__liveRegion',
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
left: '-10000px',
|
|
60
|
+
width: '1px',
|
|
61
|
+
height: '1px',
|
|
62
|
+
overflow: 'hidden'
|
|
55
63
|
}
|
|
56
64
|
};
|
|
57
65
|
};
|
package/lib/Table/v1/index.js
CHANGED
|
@@ -57,21 +57,49 @@ let Table = exports.Table = (_dec = (0, _emotion.withStyleLegacy)(_styles.defaul
|
|
|
57
57
|
constructor(...args) {
|
|
58
58
|
super(...args);
|
|
59
59
|
this.ref = null;
|
|
60
|
+
// Reference to hidden aria-live region for announcing caption changes to screen readers
|
|
61
|
+
this._liveRegionRef = null;
|
|
62
|
+
// Timeout for delayed announcement (workaround for Safari/VoiceOver caption update bug)
|
|
63
|
+
this._announcementTimeout = void 0;
|
|
60
64
|
this.handleRef = el => {
|
|
61
|
-
|
|
65
|
+
var _this$props$elementRe, _this$props;
|
|
62
66
|
this.ref = el;
|
|
63
|
-
|
|
64
|
-
elementRef(el);
|
|
65
|
-
}
|
|
67
|
+
(_this$props$elementRe = (_this$props = this.props).elementRef) === null || _this$props$elementRe === void 0 ? void 0 : _this$props$elementRe.call(_this$props, el);
|
|
66
68
|
};
|
|
67
69
|
}
|
|
68
70
|
componentDidMount() {
|
|
69
|
-
var _this$props$makeStyle, _this$
|
|
70
|
-
(_this$props$makeStyle = (_this$
|
|
71
|
+
var _this$props$makeStyle, _this$props2;
|
|
72
|
+
(_this$props$makeStyle = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props2);
|
|
73
|
+
}
|
|
74
|
+
componentDidUpdate(prevProps) {
|
|
75
|
+
var _this$props$makeStyle2, _this$props3;
|
|
76
|
+
(_this$props$makeStyle2 = (_this$props3 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props3);
|
|
77
|
+
// Announce caption changes for screen readers (especially VoiceOver)
|
|
78
|
+
// Safari/VoiceOver has a known bug where dynamic <caption> updates aren't announced,
|
|
79
|
+
// so we use an aria-live region as a workaround
|
|
80
|
+
const prevSortInfo = this.getSortedHeaderInfo(prevProps);
|
|
81
|
+
const currentSortInfo = this.getSortedHeaderInfo(this.props);
|
|
82
|
+
// Only announce if sorting actually changed
|
|
83
|
+
const sortingChanged = (prevSortInfo === null || prevSortInfo === void 0 ? void 0 : prevSortInfo.header) !== (currentSortInfo === null || currentSortInfo === void 0 ? void 0 : currentSortInfo.header) || (prevSortInfo === null || prevSortInfo === void 0 ? void 0 : prevSortInfo.direction) !== (currentSortInfo === null || currentSortInfo === void 0 ? void 0 : currentSortInfo.direction);
|
|
84
|
+
if (sortingChanged && currentSortInfo && this._liveRegionRef) {
|
|
85
|
+
// Clear any pending announcement
|
|
86
|
+
clearTimeout(this._announcementTimeout);
|
|
87
|
+
// Clear the live region first (part of the clear-then-set pattern)
|
|
88
|
+
this._liveRegionRef.textContent = '';
|
|
89
|
+
// Wait 100ms before setting new content to ensure screen readers detect the change
|
|
90
|
+
this._announcementTimeout = setTimeout(() => {
|
|
91
|
+
if (this._liveRegionRef) {
|
|
92
|
+
const currentCaption = this.getCaptionText(this.props);
|
|
93
|
+
// Append non-breaking space (\u00A0) to force Safari/VoiceOver to treat
|
|
94
|
+
// repeated captions as different announcements
|
|
95
|
+
this._liveRegionRef.textContent = currentCaption + '\u00A0';
|
|
96
|
+
}
|
|
97
|
+
}, 100);
|
|
98
|
+
}
|
|
71
99
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(
|
|
100
|
+
componentWillUnmount() {
|
|
101
|
+
// Clean up pending announcement timeout
|
|
102
|
+
clearTimeout(this._announcementTimeout);
|
|
75
103
|
}
|
|
76
104
|
getHeaders() {
|
|
77
105
|
const _Children$toArray = _react.Children.toArray(this.props.children),
|
|
@@ -87,27 +115,64 @@ let Table = exports.Table = (_dec = (0, _emotion.withStyleLegacy)(_styles.defaul
|
|
|
87
115
|
return colHeader.props.children;
|
|
88
116
|
});
|
|
89
117
|
}
|
|
118
|
+
getSortedHeaderInfo(props) {
|
|
119
|
+
const _Children$toArray5 = _react.Children.toArray(props.children),
|
|
120
|
+
_Children$toArray6 = (0, _slicedToArray2.default)(_Children$toArray5, 1),
|
|
121
|
+
headChild = _Children$toArray6[0];
|
|
122
|
+
const _Children$toArray7 = _react.Children.toArray(/*#__PURE__*/(0, _react.isValidElement)(headChild) ? headChild.props.children : []),
|
|
123
|
+
_Children$toArray8 = (0, _slicedToArray2.default)(_Children$toArray7, 1),
|
|
124
|
+
firstRow = _Children$toArray8[0];
|
|
125
|
+
const colHeaders = _react.Children.toArray(/*#__PURE__*/(0, _react.isValidElement)(firstRow) ? firstRow.props.children : []);
|
|
126
|
+
// Find the column with an active sort direction
|
|
127
|
+
for (const colHeader of colHeaders) {
|
|
128
|
+
if (/*#__PURE__*/(0, _react.isValidElement)(colHeader) && colHeader.props.sortDirection && colHeader.props.sortDirection !== 'none') {
|
|
129
|
+
var _colHeader$props$chil, _colHeader$props$chil2, _colHeader$props$chil3;
|
|
130
|
+
// Extract header text (may be nested in child components)
|
|
131
|
+
const headerText = typeof colHeader.props.children === 'string' ? colHeader.props.children : (_colHeader$props$chil = (_colHeader$props$chil2 = colHeader.props.children) === null || _colHeader$props$chil2 === void 0 ? void 0 : (_colHeader$props$chil3 = _colHeader$props$chil2.props) === null || _colHeader$props$chil3 === void 0 ? void 0 : _colHeader$props$chil3.children) !== null && _colHeader$props$chil !== void 0 ? _colHeader$props$chil : '';
|
|
132
|
+
return {
|
|
133
|
+
header: headerText,
|
|
134
|
+
direction: colHeader.props.sortDirection
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
getCaptionText(props) {
|
|
141
|
+
const sortInfo = this.getSortedHeaderInfo(props);
|
|
142
|
+
const caption = props.caption;
|
|
143
|
+
if (!sortInfo) return caption;
|
|
144
|
+
const sortText = ` Sorted by ${sortInfo.header} (${sortInfo.direction})`;
|
|
145
|
+
return caption ? caption + sortText : sortText.trim();
|
|
146
|
+
}
|
|
90
147
|
render() {
|
|
91
|
-
const _this$
|
|
92
|
-
margin = _this$
|
|
93
|
-
layout = _this$
|
|
94
|
-
caption = _this$
|
|
95
|
-
children = _this$
|
|
96
|
-
hover = _this$
|
|
97
|
-
styles = _this$
|
|
98
|
-
minWidth = _this$
|
|
148
|
+
const _this$props4 = this.props,
|
|
149
|
+
margin = _this$props4.margin,
|
|
150
|
+
layout = _this$props4.layout,
|
|
151
|
+
caption = _this$props4.caption,
|
|
152
|
+
children = _this$props4.children,
|
|
153
|
+
hover = _this$props4.hover,
|
|
154
|
+
styles = _this$props4.styles,
|
|
155
|
+
minWidth = _this$props4.minWidth;
|
|
99
156
|
const isStacked = layout === 'stacked';
|
|
100
|
-
const
|
|
157
|
+
const captionText = this.getCaptionText(this.props);
|
|
101
158
|
if (!caption) {
|
|
102
159
|
(0, _console.error)(false, `[Table] required prop caption is not set.`);
|
|
103
160
|
}
|
|
104
|
-
return (0, _jsxRuntime.
|
|
161
|
+
return (0, _jsxRuntime.jsxs)(_TableContext.default.Provider, {
|
|
105
162
|
value: {
|
|
106
|
-
isStacked
|
|
163
|
+
isStacked,
|
|
107
164
|
hover: hover,
|
|
108
|
-
headers:
|
|
165
|
+
headers: isStacked ? this.getHeaders() : void 0
|
|
109
166
|
},
|
|
110
|
-
children: (0, _jsxRuntime.
|
|
167
|
+
children: [(0, _jsxRuntime.jsx)("div", {
|
|
168
|
+
ref: el => {
|
|
169
|
+
this._liveRegionRef = el;
|
|
170
|
+
},
|
|
171
|
+
"aria-live": "polite",
|
|
172
|
+
"aria-atomic": "true",
|
|
173
|
+
role: "status",
|
|
174
|
+
css: styles === null || styles === void 0 ? void 0 : styles.liveRegion
|
|
175
|
+
}), (0, _jsxRuntime.jsxs)(_v11_.View
|
|
111
176
|
// All HTML props, except the ones accepted by `View` and `Table`
|
|
112
177
|
, {
|
|
113
178
|
..._v11_.View.omitViewProps((0, _omitProps.omitProps)(this.props, Table.allowedProps), Table),
|
|
@@ -117,20 +182,15 @@ let Table = exports.Table = (_dec = (0, _emotion.withStyleLegacy)(_styles.defaul
|
|
|
117
182
|
elementRef: this.handleRef,
|
|
118
183
|
css: styles === null || styles === void 0 ? void 0 : styles.table,
|
|
119
184
|
role: isStacked ? 'table' : void 0,
|
|
120
|
-
"aria-label":
|
|
121
|
-
children: [!isStacked && (0, _jsxRuntime.jsx)("caption", {
|
|
185
|
+
"aria-label": captionText,
|
|
186
|
+
children: [!isStacked && caption && (0, _jsxRuntime.jsx)("caption", {
|
|
122
187
|
children: (0, _jsxRuntime.jsx)(_ScreenReaderContent.ScreenReaderContent, {
|
|
123
|
-
children:
|
|
188
|
+
children: captionText
|
|
124
189
|
})
|
|
125
|
-
}), _react.Children.map(children, child => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return child;
|
|
132
|
-
})]
|
|
133
|
-
})
|
|
190
|
+
}), _react.Children.map(children, child => /*#__PURE__*/(0, _react.isValidElement)(child) ? (0, _safeCloneElement.safeCloneElement)(child, {
|
|
191
|
+
key: child.props.name
|
|
192
|
+
}) : child)]
|
|
193
|
+
})]
|
|
134
194
|
});
|
|
135
195
|
}
|
|
136
196
|
}, _Table.displayName = "Table", _Table.componentId = 'Table', _Table.allowedProps = _props.allowedProps, _Table.defaultProps = {
|
package/lib/Table/v1/styles.js
CHANGED
|
@@ -58,6 +58,14 @@ const generateStyle = (componentTheme, props) => {
|
|
|
58
58
|
caption: {
|
|
59
59
|
textAlign: 'start'
|
|
60
60
|
}
|
|
61
|
+
},
|
|
62
|
+
liveRegion: {
|
|
63
|
+
label: 'table__liveRegion',
|
|
64
|
+
position: 'absolute',
|
|
65
|
+
left: '-10000px',
|
|
66
|
+
width: '1px',
|
|
67
|
+
height: '1px',
|
|
68
|
+
overflow: 'hidden'
|
|
61
69
|
}
|
|
62
70
|
};
|
|
63
71
|
};
|