@instructure/ui-text-input 11.7.4-snapshot-121 → 11.7.4-snapshot-125

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 CHANGED
@@ -3,11 +3,13 @@
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.4-snapshot-121](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-121) (2026-07-22)
6
+ ## [11.7.4-snapshot-125](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-125) (2026-07-22)
7
7
 
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
+ * **many:** keep form field messages out of the control's accessible name ([409d077](https://github.com/instructure/instructure-ui/commit/409d077a3095b318fd7d2e150bb01bc2732c9bf5))
12
+ * **many:** only reference rendered messages via aria-describedby ([ccc9516](https://github.com/instructure/instructure-ui/commit/ccc95165ec8dae6d06e001c0a20fcebef1cf9962))
11
13
  * **ui-text-input:** restore inline cursor flow next to selected tags ([3dd1736](https://github.com/instructure/instructure-ui/commit/3dd1736173ff089970bbbd175bda81faa02895f7))
12
14
 
13
15
 
@@ -56,11 +56,13 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyl
56
56
  super(props);
57
57
  this._defaultId = props.deterministicId();
58
58
  this._messagesId = props.deterministicId('TextInput-messages');
59
+ this._labelId = props.deterministicId('TextInput-label');
59
60
  }
60
61
  ref = null;
61
62
  _input = null;
62
63
  _defaultId;
63
64
  _messagesId;
65
+ _labelId;
64
66
  _focusListener = null;
65
67
  handleRef = el => {
66
68
  const {
@@ -123,7 +125,7 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyl
123
125
  });
124
126
  }
125
127
  get hasMessages() {
126
- return !!this.props.messages && this.props.messages.length > 0;
128
+ return !!this.props.messages?.some(m => !!m.text);
127
129
  }
128
130
  get invalid() {
129
131
  return !!this.props.messages && this.props.messages.findIndex(message => {
@@ -182,6 +184,13 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyl
182
184
  if (props['aria-describedby']) {
183
185
  descriptionIds = `${props['aria-describedby']}`;
184
186
  }
187
+ // Keep messages in the description, not the accessible name.
188
+ // A consumer-provided aria-labelledby wins.
189
+ let labelledById = props['aria-labelledby'];
190
+ if (this.hasMessages) {
191
+ descriptionIds = [descriptionIds, this._messagesId].filter(Boolean).join(' ');
192
+ labelledById = labelledById || this._labelId;
193
+ }
185
194
  return _jsx("input", {
186
195
  ...props,
187
196
  css: this.props.styles?.textInput,
@@ -196,6 +205,7 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyl
196
205
  disabled: interaction === 'disabled',
197
206
  readOnly: interaction === 'readonly',
198
207
  "aria-describedby": descriptionIds !== '' ? descriptionIds : undefined,
208
+ "aria-labelledby": labelledById,
199
209
  size: htmlSize,
200
210
  onChange: this.handleChange,
201
211
  onBlur: this.handleBlur
@@ -221,6 +231,7 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyl
221
231
  id: this.id,
222
232
  label: label,
223
233
  messagesId: this._messagesId,
234
+ labelId: this._labelId,
224
235
  messages: messages,
225
236
  inline: display === 'inline-block',
226
237
  width: width,
@@ -67,11 +67,13 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
67
67
  super(props);
68
68
  this._defaultId = props.deterministicId();
69
69
  this._messagesId = props.deterministicId('TextInput-messages');
70
+ this._labelId = props.deterministicId('TextInput-label');
70
71
  }
71
72
  ref = null;
72
73
  _input = null;
73
74
  _defaultId;
74
75
  _messagesId;
76
+ _labelId;
75
77
  _focusListener = null;
76
78
  handleRef = el => {
77
79
  const {
@@ -134,7 +136,7 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
134
136
  });
135
137
  }
136
138
  get hasMessages() {
137
- return !!this.props.messages && this.props.messages.length > 0;
139
+ return !!this.props.messages?.some(m => !!m.text);
138
140
  }
139
141
  get invalid() {
140
142
  return !!this.props.messages && this.props.messages.findIndex(message => {
@@ -193,6 +195,13 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
193
195
  if (props['aria-describedby']) {
194
196
  descriptionIds = `${props['aria-describedby']}`;
195
197
  }
198
+ // Keep messages in the description, not the accessible name.
199
+ // A consumer-provided aria-labelledby wins.
200
+ let labelledById = props['aria-labelledby'];
201
+ if (this.hasMessages) {
202
+ descriptionIds = [descriptionIds, this._messagesId].filter(Boolean).join(' ');
203
+ labelledById = labelledById || this._labelId;
204
+ }
196
205
  return (0, _jsxRuntime.jsx)("input", {
197
206
  ...props,
198
207
  css: this.props.styles?.textInput,
@@ -207,6 +216,7 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
207
216
  disabled: interaction === 'disabled',
208
217
  readOnly: interaction === 'readonly',
209
218
  "aria-describedby": descriptionIds !== '' ? descriptionIds : undefined,
219
+ "aria-labelledby": labelledById,
210
220
  size: htmlSize,
211
221
  onChange: this.handleChange,
212
222
  onBlur: this.handleBlur
@@ -232,6 +242,7 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
232
242
  id: this.id,
233
243
  label: label,
234
244
  messagesId: this._messagesId,
245
+ labelId: this._labelId,
235
246
  messages: messages,
236
247
  inline: display === 'inline-block',
237
248
  width: width,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-text-input",
3
- "version": "11.7.4-snapshot-121",
3
+ "version": "11.7.4-snapshot-125",
4
4
  "description": "A styled HTML text input component.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -18,21 +18,21 @@
18
18
  "@testing-library/react": "16.3.2",
19
19
  "@testing-library/user-event": "^14.6.1",
20
20
  "vitest": "^4.1.9",
21
- "@instructure/ui-axe-check": "11.7.4-snapshot-121",
22
- "@instructure/ui-babel-preset": "11.7.4-snapshot-121",
23
- "@instructure/ui-color-utils": "11.7.4-snapshot-121",
24
- "@instructure/ui-badge": "11.7.4-snapshot-121"
21
+ "@instructure/ui-axe-check": "11.7.4-snapshot-125",
22
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-125",
23
+ "@instructure/ui-badge": "11.7.4-snapshot-125",
24
+ "@instructure/ui-color-utils": "11.7.4-snapshot-125"
25
25
  },
26
26
  "dependencies": {
27
27
  "@babel/runtime": "^7.29.7",
28
- "@instructure/shared-types": "11.7.4-snapshot-121",
29
- "@instructure/emotion": "11.7.4-snapshot-121",
30
- "@instructure/ui-a11y-utils": "11.7.4-snapshot-121",
31
- "@instructure/ui-form-field": "11.7.4-snapshot-121",
32
- "@instructure/ui-dom-utils": "11.7.4-snapshot-121",
33
- "@instructure/ui-react-utils": "11.7.4-snapshot-121",
34
- "@instructure/ui-themes": "11.7.4-snapshot-121",
35
- "@instructure/ui-tag": "11.7.4-snapshot-121"
28
+ "@instructure/emotion": "11.7.4-snapshot-125",
29
+ "@instructure/shared-types": "11.7.4-snapshot-125",
30
+ "@instructure/ui-dom-utils": "11.7.4-snapshot-125",
31
+ "@instructure/ui-form-field": "11.7.4-snapshot-125",
32
+ "@instructure/ui-a11y-utils": "11.7.4-snapshot-125",
33
+ "@instructure/ui-react-utils": "11.7.4-snapshot-125",
34
+ "@instructure/ui-tag": "11.7.4-snapshot-125",
35
+ "@instructure/ui-themes": "11.7.4-snapshot-125"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "react": ">=18 <=19"
@@ -70,6 +70,7 @@ class TextInput extends Component<TextInputProps> {
70
70
  super(props)
71
71
  this._defaultId = props.deterministicId!()
72
72
  this._messagesId = props.deterministicId!('TextInput-messages')
73
+ this._labelId = props.deterministicId!('TextInput-label')
73
74
  }
74
75
 
75
76
  ref: Element | null = null
@@ -78,6 +79,7 @@ class TextInput extends Component<TextInputProps> {
78
79
 
79
80
  private readonly _defaultId: string
80
81
  private readonly _messagesId: string
82
+ private readonly _labelId: string
81
83
 
82
84
  private _focusListener: { remove(): void } | null = null
83
85
 
@@ -158,7 +160,7 @@ class TextInput extends Component<TextInputProps> {
158
160
  }
159
161
 
160
162
  get hasMessages() {
161
- return !!this.props.messages && this.props.messages.length > 0
163
+ return !!this.props.messages?.some((m) => !!m.text)
162
164
  }
163
165
 
164
166
  get invalid() {
@@ -231,6 +233,15 @@ class TextInput extends Component<TextInputProps> {
231
233
  if (props['aria-describedby']) {
232
234
  descriptionIds = `${props['aria-describedby']}`
233
235
  }
236
+ // Keep messages in the description, not the accessible name.
237
+ // A consumer-provided aria-labelledby wins.
238
+ let labelledById = props['aria-labelledby'] as string | undefined
239
+ if (this.hasMessages) {
240
+ descriptionIds = [descriptionIds, this._messagesId]
241
+ .filter(Boolean)
242
+ .join(' ')
243
+ labelledById = labelledById || this._labelId
244
+ }
234
245
 
235
246
  return (
236
247
  <input
@@ -247,6 +258,7 @@ class TextInput extends Component<TextInputProps> {
247
258
  disabled={interaction === 'disabled'}
248
259
  readOnly={interaction === 'readonly'}
249
260
  aria-describedby={descriptionIds !== '' ? descriptionIds : undefined}
261
+ aria-labelledby={labelledById}
250
262
  size={htmlSize}
251
263
  onChange={this.handleChange}
252
264
  onBlur={this.handleBlur}
@@ -283,6 +295,7 @@ class TextInput extends Component<TextInputProps> {
283
295
  id={this.id}
284
296
  label={label}
285
297
  messagesId={this._messagesId}
298
+ labelId={this._labelId}
286
299
  messages={messages}
287
300
  inline={display === 'inline-block'}
288
301
  width={width}