@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/lib/Table/v2/index.js
CHANGED
|
@@ -56,21 +56,49 @@ let Table = exports.Table = (_dec = (0, _emotion.withStyle)(_styles.default), _d
|
|
|
56
56
|
constructor(...args) {
|
|
57
57
|
super(...args);
|
|
58
58
|
this.ref = null;
|
|
59
|
+
// Reference to hidden aria-live region for announcing caption changes to screen readers
|
|
60
|
+
this._liveRegionRef = null;
|
|
61
|
+
// Timeout for delayed announcement (workaround for Safari/VoiceOver caption update bug)
|
|
62
|
+
this._announcementTimeout = void 0;
|
|
59
63
|
this.handleRef = el => {
|
|
60
|
-
|
|
64
|
+
var _this$props$elementRe, _this$props;
|
|
61
65
|
this.ref = el;
|
|
62
|
-
|
|
63
|
-
elementRef(el);
|
|
64
|
-
}
|
|
66
|
+
(_this$props$elementRe = (_this$props = this.props).elementRef) === null || _this$props$elementRe === void 0 ? void 0 : _this$props$elementRe.call(_this$props, el);
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
componentDidMount() {
|
|
68
|
-
var _this$props$makeStyle, _this$
|
|
69
|
-
(_this$props$makeStyle = (_this$
|
|
70
|
+
var _this$props$makeStyle, _this$props2;
|
|
71
|
+
(_this$props$makeStyle = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props2);
|
|
72
|
+
}
|
|
73
|
+
componentDidUpdate(prevProps) {
|
|
74
|
+
var _this$props$makeStyle2, _this$props3;
|
|
75
|
+
(_this$props$makeStyle2 = (_this$props3 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props3);
|
|
76
|
+
// Announce caption changes for screen readers (especially VoiceOver)
|
|
77
|
+
// Safari/VoiceOver has a known bug where dynamic <caption> updates aren't announced,
|
|
78
|
+
// so we use an aria-live region as a workaround
|
|
79
|
+
const prevSortInfo = this.getSortedHeaderInfo(prevProps);
|
|
80
|
+
const currentSortInfo = this.getSortedHeaderInfo(this.props);
|
|
81
|
+
// Only announce if sorting actually changed
|
|
82
|
+
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);
|
|
83
|
+
if (sortingChanged && currentSortInfo && this._liveRegionRef) {
|
|
84
|
+
// Clear any pending announcement
|
|
85
|
+
clearTimeout(this._announcementTimeout);
|
|
86
|
+
// Clear the live region first (part of the clear-then-set pattern)
|
|
87
|
+
this._liveRegionRef.textContent = '';
|
|
88
|
+
// Wait 100ms before setting new content to ensure screen readers detect the change
|
|
89
|
+
this._announcementTimeout = setTimeout(() => {
|
|
90
|
+
if (this._liveRegionRef) {
|
|
91
|
+
const currentCaption = this.getCaptionText(this.props);
|
|
92
|
+
// Append non-breaking space (\u00A0) to force Safari/VoiceOver to treat
|
|
93
|
+
// repeated captions as different announcements
|
|
94
|
+
this._liveRegionRef.textContent = currentCaption + '\u00A0';
|
|
95
|
+
}
|
|
96
|
+
}, 100);
|
|
97
|
+
}
|
|
70
98
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
(
|
|
99
|
+
componentWillUnmount() {
|
|
100
|
+
// Clean up pending announcement timeout
|
|
101
|
+
clearTimeout(this._announcementTimeout);
|
|
74
102
|
}
|
|
75
103
|
getHeaders() {
|
|
76
104
|
const _Children$toArray = _react.Children.toArray(this.props.children),
|
|
@@ -86,27 +114,64 @@ let Table = exports.Table = (_dec = (0, _emotion.withStyle)(_styles.default), _d
|
|
|
86
114
|
return colHeader.props.children;
|
|
87
115
|
});
|
|
88
116
|
}
|
|
117
|
+
getSortedHeaderInfo(props) {
|
|
118
|
+
const _Children$toArray5 = _react.Children.toArray(props.children),
|
|
119
|
+
_Children$toArray6 = (0, _slicedToArray2.default)(_Children$toArray5, 1),
|
|
120
|
+
headChild = _Children$toArray6[0];
|
|
121
|
+
const _Children$toArray7 = _react.Children.toArray(/*#__PURE__*/(0, _react.isValidElement)(headChild) ? headChild.props.children : []),
|
|
122
|
+
_Children$toArray8 = (0, _slicedToArray2.default)(_Children$toArray7, 1),
|
|
123
|
+
firstRow = _Children$toArray8[0];
|
|
124
|
+
const colHeaders = _react.Children.toArray(/*#__PURE__*/(0, _react.isValidElement)(firstRow) ? firstRow.props.children : []);
|
|
125
|
+
// Find the column with an active sort direction
|
|
126
|
+
for (const colHeader of colHeaders) {
|
|
127
|
+
if (/*#__PURE__*/(0, _react.isValidElement)(colHeader) && colHeader.props.sortDirection && colHeader.props.sortDirection !== 'none') {
|
|
128
|
+
var _colHeader$props$chil, _colHeader$props$chil2, _colHeader$props$chil3;
|
|
129
|
+
// Extract header text (may be nested in child components)
|
|
130
|
+
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 : '';
|
|
131
|
+
return {
|
|
132
|
+
header: headerText,
|
|
133
|
+
direction: colHeader.props.sortDirection
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
getCaptionText(props) {
|
|
140
|
+
const sortInfo = this.getSortedHeaderInfo(props);
|
|
141
|
+
const caption = props.caption;
|
|
142
|
+
if (!sortInfo) return caption;
|
|
143
|
+
const sortText = ` Sorted by ${sortInfo.header} (${sortInfo.direction})`;
|
|
144
|
+
return caption ? caption + sortText : sortText.trim();
|
|
145
|
+
}
|
|
89
146
|
render() {
|
|
90
|
-
const _this$
|
|
91
|
-
margin = _this$
|
|
92
|
-
layout = _this$
|
|
93
|
-
caption = _this$
|
|
94
|
-
children = _this$
|
|
95
|
-
hover = _this$
|
|
96
|
-
styles = _this$
|
|
97
|
-
minWidth = _this$
|
|
147
|
+
const _this$props4 = this.props,
|
|
148
|
+
margin = _this$props4.margin,
|
|
149
|
+
layout = _this$props4.layout,
|
|
150
|
+
caption = _this$props4.caption,
|
|
151
|
+
children = _this$props4.children,
|
|
152
|
+
hover = _this$props4.hover,
|
|
153
|
+
styles = _this$props4.styles,
|
|
154
|
+
minWidth = _this$props4.minWidth;
|
|
98
155
|
const isStacked = layout === 'stacked';
|
|
99
|
-
const
|
|
156
|
+
const captionText = this.getCaptionText(this.props);
|
|
100
157
|
if (!caption) {
|
|
101
158
|
(0, _console.error)(false, `[Table] required prop caption is not set.`);
|
|
102
159
|
}
|
|
103
|
-
return (0, _jsxRuntime.
|
|
160
|
+
return (0, _jsxRuntime.jsxs)(_TableContext.default.Provider, {
|
|
104
161
|
value: {
|
|
105
|
-
isStacked
|
|
162
|
+
isStacked,
|
|
106
163
|
hover: hover,
|
|
107
|
-
headers:
|
|
164
|
+
headers: isStacked ? this.getHeaders() : void 0
|
|
108
165
|
},
|
|
109
|
-
children: (0, _jsxRuntime.
|
|
166
|
+
children: [(0, _jsxRuntime.jsx)("div", {
|
|
167
|
+
ref: el => {
|
|
168
|
+
this._liveRegionRef = el;
|
|
169
|
+
},
|
|
170
|
+
"aria-live": "polite",
|
|
171
|
+
"aria-atomic": "true",
|
|
172
|
+
role: "status",
|
|
173
|
+
css: styles === null || styles === void 0 ? void 0 : styles.liveRegion
|
|
174
|
+
}), (0, _jsxRuntime.jsxs)(_latest.View
|
|
110
175
|
// All HTML props, except the ones accepted by `View` and `Table`
|
|
111
176
|
, {
|
|
112
177
|
..._latest.View.omitViewProps((0, _omitProps.omitProps)(this.props, Table.allowedProps), Table),
|
|
@@ -116,20 +181,15 @@ let Table = exports.Table = (_dec = (0, _emotion.withStyle)(_styles.default), _d
|
|
|
116
181
|
elementRef: this.handleRef,
|
|
117
182
|
css: styles === null || styles === void 0 ? void 0 : styles.table,
|
|
118
183
|
role: isStacked ? 'table' : void 0,
|
|
119
|
-
"aria-label":
|
|
120
|
-
children: [!isStacked && (0, _jsxRuntime.jsx)("caption", {
|
|
184
|
+
"aria-label": captionText,
|
|
185
|
+
children: [!isStacked && caption && (0, _jsxRuntime.jsx)("caption", {
|
|
121
186
|
children: (0, _jsxRuntime.jsx)(_ScreenReaderContent.ScreenReaderContent, {
|
|
122
|
-
children:
|
|
187
|
+
children: captionText
|
|
123
188
|
})
|
|
124
|
-
}), _react.Children.map(children, child => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return child;
|
|
131
|
-
})]
|
|
132
|
-
})
|
|
189
|
+
}), _react.Children.map(children, child => /*#__PURE__*/(0, _react.isValidElement)(child) ? (0, _safeCloneElement.safeCloneElement)(child, {
|
|
190
|
+
key: child.props.name
|
|
191
|
+
}) : child)]
|
|
192
|
+
})]
|
|
133
193
|
});
|
|
134
194
|
}
|
|
135
195
|
}, _Table.displayName = "Table", _Table.componentId = 'Table', _Table.allowedProps = _props.allowedProps, _Table.defaultProps = {
|
package/lib/Table/v2/styles.js
CHANGED
|
@@ -58,6 +58,14 @@ const generateStyle = (componentTheme, props, _sharedTokens) => {
|
|
|
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
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-table",
|
|
3
|
-
"version": "11.7.2-snapshot-
|
|
3
|
+
"version": "11.7.2-snapshot-52",
|
|
4
4
|
"description": "A styled HTML table component",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -15,25 +15,25 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@babel/runtime": "^7.27.6",
|
|
18
|
-
"@instructure/
|
|
19
|
-
"@instructure/
|
|
20
|
-
"@instructure/
|
|
21
|
-
"@instructure/
|
|
22
|
-
"@instructure/
|
|
23
|
-
"@instructure/ui-react-utils": "11.7.2-snapshot-
|
|
24
|
-
"@instructure/ui-simple-select": "11.7.2-snapshot-
|
|
25
|
-
"@instructure/ui-
|
|
26
|
-
"@instructure/ui-
|
|
18
|
+
"@instructure/emotion": "11.7.2-snapshot-52",
|
|
19
|
+
"@instructure/ui-a11y-content": "11.7.2-snapshot-52",
|
|
20
|
+
"@instructure/ui-icons": "11.7.2-snapshot-52",
|
|
21
|
+
"@instructure/console": "11.7.2-snapshot-52",
|
|
22
|
+
"@instructure/shared-types": "11.7.2-snapshot-52",
|
|
23
|
+
"@instructure/ui-react-utils": "11.7.2-snapshot-52",
|
|
24
|
+
"@instructure/ui-simple-select": "11.7.2-snapshot-52",
|
|
25
|
+
"@instructure/ui-utils": "11.7.2-snapshot-52",
|
|
26
|
+
"@instructure/ui-view": "11.7.2-snapshot-52"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@testing-library/jest-dom": "^6.6.3",
|
|
30
30
|
"@testing-library/react": "15.0.7",
|
|
31
31
|
"@testing-library/user-event": "^14.6.1",
|
|
32
32
|
"vitest": "^3.2.2",
|
|
33
|
-
"@instructure/ui-
|
|
34
|
-
"@instructure/ui-
|
|
35
|
-
"@instructure/ui-
|
|
36
|
-
"@instructure/ui-
|
|
33
|
+
"@instructure/ui-babel-preset": "11.7.2-snapshot-52",
|
|
34
|
+
"@instructure/ui-themes": "11.7.2-snapshot-52",
|
|
35
|
+
"@instructure/ui-color-utils": "11.7.2-snapshot-52",
|
|
36
|
+
"@instructure/ui-axe-check": "11.7.2-snapshot-52"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"react": ">=18 <=19"
|
package/src/Table/v1/README.md
CHANGED
|
@@ -351,10 +351,7 @@ const SortableTable = ({ caption, headers, rows }) => {
|
|
|
351
351
|
</View>
|
|
352
352
|
)}
|
|
353
353
|
|
|
354
|
-
<Table
|
|
355
|
-
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
|
356
|
-
{...props}
|
|
357
|
-
>
|
|
354
|
+
<Table caption={caption} {...props}>
|
|
358
355
|
<Table.Head renderSortLabel="Sort by">
|
|
359
356
|
{renderHeaderRow(direction)}
|
|
360
357
|
</Table.Head>
|
|
@@ -370,13 +367,6 @@ const SortableTable = ({ caption, headers, rows }) => {
|
|
|
370
367
|
))}
|
|
371
368
|
</Table.Body>
|
|
372
369
|
</Table>
|
|
373
|
-
<Alert
|
|
374
|
-
liveRegion={() => document.getElementById('flash-messages')}
|
|
375
|
-
liveRegionPoliteness="polite"
|
|
376
|
-
screenReaderOnly
|
|
377
|
-
>
|
|
378
|
-
{`Sorted by ${sortBy} in ${direction} order`}
|
|
379
|
-
</Alert>
|
|
380
370
|
</div>
|
|
381
371
|
)}
|
|
382
372
|
</Responsive>
|
|
@@ -505,10 +495,7 @@ const SelectableTable = ({
|
|
|
505
495
|
<View as="div" padding="small" background="primary-inverse">
|
|
506
496
|
{`${selected.size} of ${rowIds.length} selected`}
|
|
507
497
|
</View>
|
|
508
|
-
<Table
|
|
509
|
-
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
|
510
|
-
{...props}
|
|
511
|
-
>
|
|
498
|
+
<Table caption={caption} {...props}>
|
|
512
499
|
<Table.Head
|
|
513
500
|
renderSortLabel={
|
|
514
501
|
<ScreenReaderContent>Sort by</ScreenReaderContent>
|
|
@@ -685,15 +672,6 @@ const SortableTable = ({ caption, headers, rows, perPage }) => {
|
|
|
685
672
|
ascending={ascending}
|
|
686
673
|
perPage={perPage}
|
|
687
674
|
/>
|
|
688
|
-
<Alert
|
|
689
|
-
liveRegion={() => document.getElementById('flash-messages')}
|
|
690
|
-
liveRegionPoliteness="polite"
|
|
691
|
-
screenReaderOnly
|
|
692
|
-
>
|
|
693
|
-
{`Sorted by ${sortBy} in ${
|
|
694
|
-
ascending ? 'ascending' : 'descending'
|
|
695
|
-
} order`}
|
|
696
|
-
</Alert>
|
|
697
675
|
</div>
|
|
698
676
|
)
|
|
699
677
|
}
|
package/src/Table/v1/index.tsx
CHANGED
|
@@ -71,23 +71,51 @@ class Table extends Component<TableProps> {
|
|
|
71
71
|
static Cell = Cell
|
|
72
72
|
|
|
73
73
|
ref: Element | null = null
|
|
74
|
+
// Reference to hidden aria-live region for announcing caption changes to screen readers
|
|
75
|
+
_liveRegionRef: HTMLDivElement | null = null
|
|
76
|
+
// Timeout for delayed announcement (workaround for Safari/VoiceOver caption update bug)
|
|
77
|
+
_announcementTimeout?: ReturnType<typeof setTimeout>
|
|
74
78
|
|
|
75
79
|
handleRef = (el: Element | null) => {
|
|
76
|
-
const { elementRef } = this.props
|
|
77
|
-
|
|
78
80
|
this.ref = el
|
|
79
|
-
|
|
80
|
-
if (typeof elementRef === 'function') {
|
|
81
|
-
elementRef(el)
|
|
82
|
-
}
|
|
81
|
+
this.props.elementRef?.(el)
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
componentDidMount() {
|
|
86
85
|
this.props.makeStyles?.()
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
componentDidUpdate() {
|
|
88
|
+
componentDidUpdate(prevProps: TableProps) {
|
|
90
89
|
this.props.makeStyles?.()
|
|
90
|
+
// Announce caption changes for screen readers (especially VoiceOver)
|
|
91
|
+
// Safari/VoiceOver has a known bug where dynamic <caption> updates aren't announced,
|
|
92
|
+
// so we use an aria-live region as a workaround
|
|
93
|
+
const prevSortInfo = this.getSortedHeaderInfo(prevProps)
|
|
94
|
+
const currentSortInfo = this.getSortedHeaderInfo(this.props)
|
|
95
|
+
// Only announce if sorting actually changed
|
|
96
|
+
const sortingChanged =
|
|
97
|
+
prevSortInfo?.header !== currentSortInfo?.header ||
|
|
98
|
+
prevSortInfo?.direction !== currentSortInfo?.direction
|
|
99
|
+
if (sortingChanged && currentSortInfo && this._liveRegionRef) {
|
|
100
|
+
// Clear any pending announcement
|
|
101
|
+
clearTimeout(this._announcementTimeout)
|
|
102
|
+
// Clear the live region first (part of the clear-then-set pattern)
|
|
103
|
+
this._liveRegionRef.textContent = ''
|
|
104
|
+
// Wait 100ms before setting new content to ensure screen readers detect the change
|
|
105
|
+
this._announcementTimeout = setTimeout(() => {
|
|
106
|
+
if (this._liveRegionRef) {
|
|
107
|
+
const currentCaption = this.getCaptionText(this.props)
|
|
108
|
+
// Append non-breaking space (\u00A0) to force Safari/VoiceOver to treat
|
|
109
|
+
// repeated captions as different announcements
|
|
110
|
+
this._liveRegionRef.textContent = currentCaption + '\u00A0'
|
|
111
|
+
}
|
|
112
|
+
}, 100)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
componentWillUnmount() {
|
|
117
|
+
// Clean up pending announcement timeout
|
|
118
|
+
clearTimeout(this._announcementTimeout)
|
|
91
119
|
}
|
|
92
120
|
|
|
93
121
|
getHeaders() {
|
|
@@ -106,11 +134,45 @@ class Table extends Component<TableProps> {
|
|
|
106
134
|
)
|
|
107
135
|
}
|
|
108
136
|
|
|
137
|
+
getSortedHeaderInfo(props: TableProps) {
|
|
138
|
+
const [headChild] = Children.toArray(props.children)
|
|
139
|
+
const [firstRow] = Children.toArray(
|
|
140
|
+
isValidElement(headChild) ? headChild.props.children : []
|
|
141
|
+
)
|
|
142
|
+
const colHeaders = Children.toArray(
|
|
143
|
+
isValidElement(firstRow) ? firstRow.props.children : []
|
|
144
|
+
)
|
|
145
|
+
// Find the column with an active sort direction
|
|
146
|
+
for (const colHeader of colHeaders) {
|
|
147
|
+
if (
|
|
148
|
+
isValidElement(colHeader) &&
|
|
149
|
+
colHeader.props.sortDirection &&
|
|
150
|
+
colHeader.props.sortDirection !== 'none'
|
|
151
|
+
) {
|
|
152
|
+
// Extract header text (may be nested in child components)
|
|
153
|
+
const headerText =
|
|
154
|
+
typeof colHeader.props.children === 'string'
|
|
155
|
+
? colHeader.props.children
|
|
156
|
+
: colHeader.props.children?.props?.children ?? ''
|
|
157
|
+
return { header: headerText, direction: colHeader.props.sortDirection }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getCaptionText(props: TableProps) {
|
|
164
|
+
const sortInfo = this.getSortedHeaderInfo(props)
|
|
165
|
+
const caption = props.caption as string
|
|
166
|
+
if (!sortInfo) return caption
|
|
167
|
+
const sortText = ` Sorted by ${sortInfo.header} (${sortInfo.direction})`
|
|
168
|
+
return caption ? caption + sortText : sortText.trim()
|
|
169
|
+
}
|
|
170
|
+
|
|
109
171
|
render() {
|
|
110
172
|
const { margin, layout, caption, children, hover, styles, minWidth } =
|
|
111
173
|
this.props
|
|
112
174
|
const isStacked = layout === 'stacked'
|
|
113
|
-
const
|
|
175
|
+
const captionText = this.getCaptionText(this.props)
|
|
114
176
|
|
|
115
177
|
if (!caption) {
|
|
116
178
|
error(false, `[Table] required prop caption is not set.`)
|
|
@@ -119,11 +181,23 @@ class Table extends Component<TableProps> {
|
|
|
119
181
|
return (
|
|
120
182
|
<TableContext.Provider
|
|
121
183
|
value={{
|
|
122
|
-
isStacked
|
|
184
|
+
isStacked,
|
|
123
185
|
hover: hover!,
|
|
124
|
-
headers:
|
|
186
|
+
headers: isStacked ? this.getHeaders() : undefined
|
|
125
187
|
}}
|
|
126
188
|
>
|
|
189
|
+
{/* ARIA live region for dynamic sort announcements.
|
|
190
|
+
MUST be outside <table> due to Safari/VoiceOver bug.
|
|
191
|
+
Empty on page load, populated only when sorting changes. */}
|
|
192
|
+
<div
|
|
193
|
+
ref={(el) => {
|
|
194
|
+
this._liveRegionRef = el
|
|
195
|
+
}}
|
|
196
|
+
aria-live="polite"
|
|
197
|
+
aria-atomic="true"
|
|
198
|
+
role="status"
|
|
199
|
+
css={styles?.liveRegion}
|
|
200
|
+
/>
|
|
127
201
|
<View
|
|
128
202
|
// All HTML props, except the ones accepted by `View` and `Table`
|
|
129
203
|
{...View.omitViewProps(
|
|
@@ -136,21 +210,21 @@ class Table extends Component<TableProps> {
|
|
|
136
210
|
elementRef={this.handleRef}
|
|
137
211
|
css={styles?.table}
|
|
138
212
|
role={isStacked ? 'table' : undefined}
|
|
139
|
-
aria-label={
|
|
213
|
+
aria-label={captionText}
|
|
140
214
|
>
|
|
141
|
-
{
|
|
215
|
+
{/* Caption for visual display and semantic HTML */}
|
|
216
|
+
{!isStacked && caption && (
|
|
142
217
|
<caption>
|
|
143
|
-
<ScreenReaderContent>{
|
|
218
|
+
<ScreenReaderContent>{captionText}</ScreenReaderContent>
|
|
144
219
|
</caption>
|
|
145
220
|
)}
|
|
146
|
-
{Children.map(children, (child) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
})}
|
|
221
|
+
{Children.map(children, (child) =>
|
|
222
|
+
isValidElement(child)
|
|
223
|
+
? safeCloneElement(child, {
|
|
224
|
+
key: (child as ReactElement<any>).props.name
|
|
225
|
+
})
|
|
226
|
+
: child
|
|
227
|
+
)}
|
|
154
228
|
</View>
|
|
155
229
|
</TableContext.Provider>
|
|
156
230
|
)
|
package/src/Table/v1/props.ts
CHANGED
|
@@ -79,7 +79,7 @@ type TableProps = TableOwnProps &
|
|
|
79
79
|
WithStyleProps<TableTheme, TableStyle> &
|
|
80
80
|
OtherHTMLAttributes<TableOwnProps>
|
|
81
81
|
|
|
82
|
-
type TableStyle = ComponentStyle<'table'>
|
|
82
|
+
type TableStyle = ComponentStyle<'table' | 'liveRegion'>
|
|
83
83
|
const allowedProps: AllowedPropKeys = [
|
|
84
84
|
'caption',
|
|
85
85
|
'children',
|
package/src/Table/v1/styles.ts
CHANGED
|
@@ -55,6 +55,14 @@ const generateStyle = (
|
|
|
55
55
|
borderSpacing: 0,
|
|
56
56
|
...(layout === 'fixed' && { tableLayout: 'fixed' }),
|
|
57
57
|
caption: { textAlign: 'start' }
|
|
58
|
+
},
|
|
59
|
+
liveRegion: {
|
|
60
|
+
label: 'table__liveRegion',
|
|
61
|
+
position: 'absolute',
|
|
62
|
+
left: '-10000px',
|
|
63
|
+
width: '1px',
|
|
64
|
+
height: '1px',
|
|
65
|
+
overflow: 'hidden'
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
}
|
package/src/Table/v2/README.md
CHANGED
|
@@ -351,10 +351,7 @@ const SortableTable = ({ caption, headers, rows }) => {
|
|
|
351
351
|
</View>
|
|
352
352
|
)}
|
|
353
353
|
|
|
354
|
-
<Table
|
|
355
|
-
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
|
356
|
-
{...props}
|
|
357
|
-
>
|
|
354
|
+
<Table caption={caption} {...props}>
|
|
358
355
|
<Table.Head renderSortLabel="Sort by">
|
|
359
356
|
{renderHeaderRow(direction)}
|
|
360
357
|
</Table.Head>
|
|
@@ -370,13 +367,6 @@ const SortableTable = ({ caption, headers, rows }) => {
|
|
|
370
367
|
))}
|
|
371
368
|
</Table.Body>
|
|
372
369
|
</Table>
|
|
373
|
-
<Alert
|
|
374
|
-
liveRegion={() => document.getElementById('flash-messages')}
|
|
375
|
-
liveRegionPoliteness="polite"
|
|
376
|
-
screenReaderOnly
|
|
377
|
-
>
|
|
378
|
-
{`Sorted by ${sortBy} in ${direction} order`}
|
|
379
|
-
</Alert>
|
|
380
370
|
</div>
|
|
381
371
|
)}
|
|
382
372
|
</Responsive>
|
|
@@ -505,10 +495,7 @@ const SelectableTable = ({
|
|
|
505
495
|
<View as="div" padding="small" background="primary-inverse">
|
|
506
496
|
{`${selected.size} of ${rowIds.length} selected`}
|
|
507
497
|
</View>
|
|
508
|
-
<Table
|
|
509
|
-
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
|
510
|
-
{...props}
|
|
511
|
-
>
|
|
498
|
+
<Table caption={caption} {...props}>
|
|
512
499
|
<Table.Head
|
|
513
500
|
renderSortLabel={
|
|
514
501
|
<ScreenReaderContent>Sort by</ScreenReaderContent>
|
|
@@ -685,15 +672,6 @@ const SortableTable = ({ caption, headers, rows, perPage }) => {
|
|
|
685
672
|
ascending={ascending}
|
|
686
673
|
perPage={perPage}
|
|
687
674
|
/>
|
|
688
|
-
<Alert
|
|
689
|
-
liveRegion={() => document.getElementById('flash-messages')}
|
|
690
|
-
liveRegionPoliteness="polite"
|
|
691
|
-
screenReaderOnly
|
|
692
|
-
>
|
|
693
|
-
{`Sorted by ${sortBy} in ${
|
|
694
|
-
ascending ? 'ascending' : 'descending'
|
|
695
|
-
} order`}
|
|
696
|
-
</Alert>
|
|
697
675
|
</div>
|
|
698
676
|
)
|
|
699
677
|
}
|