@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/src/Table/v2/index.tsx
CHANGED
|
@@ -70,23 +70,51 @@ class Table extends Component<TableProps> {
|
|
|
70
70
|
static Cell = Cell
|
|
71
71
|
|
|
72
72
|
ref: Element | null = null
|
|
73
|
+
// Reference to hidden aria-live region for announcing caption changes to screen readers
|
|
74
|
+
_liveRegionRef: HTMLDivElement | null = null
|
|
75
|
+
// Timeout for delayed announcement (workaround for Safari/VoiceOver caption update bug)
|
|
76
|
+
_announcementTimeout?: ReturnType<typeof setTimeout>
|
|
73
77
|
|
|
74
78
|
handleRef = (el: Element | null) => {
|
|
75
|
-
const { elementRef } = this.props
|
|
76
|
-
|
|
77
79
|
this.ref = el
|
|
78
|
-
|
|
79
|
-
if (typeof elementRef === 'function') {
|
|
80
|
-
elementRef(el)
|
|
81
|
-
}
|
|
80
|
+
this.props.elementRef?.(el)
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
componentDidMount() {
|
|
85
84
|
this.props.makeStyles?.()
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
componentDidUpdate() {
|
|
87
|
+
componentDidUpdate(prevProps: TableProps) {
|
|
89
88
|
this.props.makeStyles?.()
|
|
89
|
+
// Announce caption changes for screen readers (especially VoiceOver)
|
|
90
|
+
// Safari/VoiceOver has a known bug where dynamic <caption> updates aren't announced,
|
|
91
|
+
// so we use an aria-live region as a workaround
|
|
92
|
+
const prevSortInfo = this.getSortedHeaderInfo(prevProps)
|
|
93
|
+
const currentSortInfo = this.getSortedHeaderInfo(this.props)
|
|
94
|
+
// Only announce if sorting actually changed
|
|
95
|
+
const sortingChanged =
|
|
96
|
+
prevSortInfo?.header !== currentSortInfo?.header ||
|
|
97
|
+
prevSortInfo?.direction !== currentSortInfo?.direction
|
|
98
|
+
if (sortingChanged && currentSortInfo && this._liveRegionRef) {
|
|
99
|
+
// Clear any pending announcement
|
|
100
|
+
clearTimeout(this._announcementTimeout)
|
|
101
|
+
// Clear the live region first (part of the clear-then-set pattern)
|
|
102
|
+
this._liveRegionRef.textContent = ''
|
|
103
|
+
// Wait 100ms before setting new content to ensure screen readers detect the change
|
|
104
|
+
this._announcementTimeout = setTimeout(() => {
|
|
105
|
+
if (this._liveRegionRef) {
|
|
106
|
+
const currentCaption = this.getCaptionText(this.props)
|
|
107
|
+
// Append non-breaking space (\u00A0) to force Safari/VoiceOver to treat
|
|
108
|
+
// repeated captions as different announcements
|
|
109
|
+
this._liveRegionRef.textContent = currentCaption + '\u00A0'
|
|
110
|
+
}
|
|
111
|
+
}, 100)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
componentWillUnmount() {
|
|
116
|
+
// Clean up pending announcement timeout
|
|
117
|
+
clearTimeout(this._announcementTimeout)
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
getHeaders() {
|
|
@@ -105,11 +133,45 @@ class Table extends Component<TableProps> {
|
|
|
105
133
|
)
|
|
106
134
|
}
|
|
107
135
|
|
|
136
|
+
getSortedHeaderInfo(props: TableProps) {
|
|
137
|
+
const [headChild] = Children.toArray(props.children)
|
|
138
|
+
const [firstRow] = Children.toArray(
|
|
139
|
+
isValidElement(headChild) ? headChild.props.children : []
|
|
140
|
+
)
|
|
141
|
+
const colHeaders = Children.toArray(
|
|
142
|
+
isValidElement(firstRow) ? firstRow.props.children : []
|
|
143
|
+
)
|
|
144
|
+
// Find the column with an active sort direction
|
|
145
|
+
for (const colHeader of colHeaders) {
|
|
146
|
+
if (
|
|
147
|
+
isValidElement(colHeader) &&
|
|
148
|
+
colHeader.props.sortDirection &&
|
|
149
|
+
colHeader.props.sortDirection !== 'none'
|
|
150
|
+
) {
|
|
151
|
+
// Extract header text (may be nested in child components)
|
|
152
|
+
const headerText =
|
|
153
|
+
typeof colHeader.props.children === 'string'
|
|
154
|
+
? colHeader.props.children
|
|
155
|
+
: colHeader.props.children?.props?.children ?? ''
|
|
156
|
+
return { header: headerText, direction: colHeader.props.sortDirection }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getCaptionText(props: TableProps) {
|
|
163
|
+
const sortInfo = this.getSortedHeaderInfo(props)
|
|
164
|
+
const caption = props.caption as string
|
|
165
|
+
if (!sortInfo) return caption
|
|
166
|
+
const sortText = ` Sorted by ${sortInfo.header} (${sortInfo.direction})`
|
|
167
|
+
return caption ? caption + sortText : sortText.trim()
|
|
168
|
+
}
|
|
169
|
+
|
|
108
170
|
render() {
|
|
109
171
|
const { margin, layout, caption, children, hover, styles, minWidth } =
|
|
110
172
|
this.props
|
|
111
173
|
const isStacked = layout === 'stacked'
|
|
112
|
-
const
|
|
174
|
+
const captionText = this.getCaptionText(this.props)
|
|
113
175
|
|
|
114
176
|
if (!caption) {
|
|
115
177
|
error(false, `[Table] required prop caption is not set.`)
|
|
@@ -118,11 +180,23 @@ class Table extends Component<TableProps> {
|
|
|
118
180
|
return (
|
|
119
181
|
<TableContext.Provider
|
|
120
182
|
value={{
|
|
121
|
-
isStacked
|
|
183
|
+
isStacked,
|
|
122
184
|
hover: hover!,
|
|
123
|
-
headers:
|
|
185
|
+
headers: isStacked ? this.getHeaders() : undefined
|
|
124
186
|
}}
|
|
125
187
|
>
|
|
188
|
+
{/* ARIA live region for dynamic sort announcements.
|
|
189
|
+
MUST be outside <table> due to Safari/VoiceOver bug.
|
|
190
|
+
Empty on page load, populated only when sorting changes. */}
|
|
191
|
+
<div
|
|
192
|
+
ref={(el) => {
|
|
193
|
+
this._liveRegionRef = el
|
|
194
|
+
}}
|
|
195
|
+
aria-live="polite"
|
|
196
|
+
aria-atomic="true"
|
|
197
|
+
role="status"
|
|
198
|
+
css={styles?.liveRegion}
|
|
199
|
+
/>
|
|
126
200
|
<View
|
|
127
201
|
// All HTML props, except the ones accepted by `View` and `Table`
|
|
128
202
|
{...View.omitViewProps(
|
|
@@ -135,21 +209,21 @@ class Table extends Component<TableProps> {
|
|
|
135
209
|
elementRef={this.handleRef}
|
|
136
210
|
css={styles?.table}
|
|
137
211
|
role={isStacked ? 'table' : undefined}
|
|
138
|
-
aria-label={
|
|
212
|
+
aria-label={captionText}
|
|
139
213
|
>
|
|
140
|
-
{
|
|
214
|
+
{/* Caption for visual display and semantic HTML */}
|
|
215
|
+
{!isStacked && caption && (
|
|
141
216
|
<caption>
|
|
142
|
-
<ScreenReaderContent>{
|
|
217
|
+
<ScreenReaderContent>{captionText}</ScreenReaderContent>
|
|
143
218
|
</caption>
|
|
144
219
|
)}
|
|
145
|
-
{Children.map(children, (child) =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
})}
|
|
220
|
+
{Children.map(children, (child) =>
|
|
221
|
+
isValidElement(child)
|
|
222
|
+
? safeCloneElement(child, {
|
|
223
|
+
key: (child as ReactElement<any>).props.name
|
|
224
|
+
})
|
|
225
|
+
: child
|
|
226
|
+
)}
|
|
153
227
|
</View>
|
|
154
228
|
</TableContext.Provider>
|
|
155
229
|
)
|
package/src/Table/v2/props.ts
CHANGED
|
@@ -79,7 +79,7 @@ type TableProps = TableOwnProps &
|
|
|
79
79
|
WithStyleProps<null, 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/v2/styles.ts
CHANGED
|
@@ -56,6 +56,14 @@ const generateStyle = (
|
|
|
56
56
|
borderSpacing: 0,
|
|
57
57
|
...(layout === 'fixed' && { tableLayout: 'fixed' }),
|
|
58
58
|
caption: { textAlign: 'start' }
|
|
59
|
+
},
|
|
60
|
+
liveRegion: {
|
|
61
|
+
label: 'table__liveRegion',
|
|
62
|
+
position: 'absolute',
|
|
63
|
+
left: '-10000px',
|
|
64
|
+
width: '1px',
|
|
65
|
+
height: '1px',
|
|
66
|
+
overflow: 'hidden'
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
}
|