@ionic/core 8.7.3-dev.11755190119.16c6a375 → 8.7.3-dev.11755195979.1ec83531
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/components/ion-input.js +38 -7
- package/components/ion-textarea.js +37 -6
- package/dist/cjs/ion-input.cjs.entry.js +37 -7
- package/dist/cjs/ion-textarea.cjs.entry.js +36 -6
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/input/input.js +39 -8
- package/dist/collection/components/textarea/textarea.js +38 -7
- package/dist/docs.json +1 -1
- package/dist/esm/ion-input.entry.js +37 -7
- package/dist/esm/ion-textarea.entry.js +36 -6
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-44415a3b.entry.js +4 -0
- package/dist/ionic/p-f80f7416.entry.js +4 -0
- package/dist/types/components/input/input.d.ts +9 -0
- package/dist/types/components/textarea/textarea.d.ts +9 -0
- package/hydrate/index.js +45 -13
- package/hydrate/index.mjs +45 -13
- package/package.json +1 -1
- package/dist/ionic/p-9f36c56a.entry.js +0 -4
- package/dist/ionic/p-c5210d3e.entry.js +0 -4
package/components/ion-input.js
CHANGED
|
@@ -43,6 +43,10 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
43
43
|
* is applied in both cases.
|
|
44
44
|
*/
|
|
45
45
|
this.hasFocus = false;
|
|
46
|
+
/**
|
|
47
|
+
* Track validation state for proper aria-live announcements
|
|
48
|
+
*/
|
|
49
|
+
this.isInvalid = false;
|
|
46
50
|
/**
|
|
47
51
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
48
52
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -227,10 +231,31 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
227
231
|
componentWillLoad() {
|
|
228
232
|
this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
|
|
229
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Checks if the input is in an invalid state based on validation classes
|
|
236
|
+
*/
|
|
237
|
+
checkValidationState() {
|
|
238
|
+
return this.el.classList.contains('ion-touched') && this.el.classList.contains('ion-invalid');
|
|
239
|
+
}
|
|
230
240
|
connectedCallback() {
|
|
231
241
|
const { el } = this;
|
|
232
242
|
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
|
|
233
243
|
this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
|
|
244
|
+
// Watch for class changes to update validation state
|
|
245
|
+
if (Build.isBrowser) {
|
|
246
|
+
this.validationObserver = new MutationObserver(() => {
|
|
247
|
+
const newIsInvalid = this.checkValidationState();
|
|
248
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
249
|
+
this.isInvalid = newIsInvalid;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
this.validationObserver.observe(el, {
|
|
253
|
+
attributes: true,
|
|
254
|
+
attributeFilter: ['class'],
|
|
255
|
+
});
|
|
256
|
+
// Set initial state
|
|
257
|
+
this.isInvalid = this.checkValidationState();
|
|
258
|
+
}
|
|
234
259
|
this.debounceChanged();
|
|
235
260
|
if (Build.isBrowser) {
|
|
236
261
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -267,6 +292,11 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
267
292
|
this.notchController.destroy();
|
|
268
293
|
this.notchController = undefined;
|
|
269
294
|
}
|
|
295
|
+
// Clean up validation observer to prevent memory leaks
|
|
296
|
+
if (this.validationObserver) {
|
|
297
|
+
this.validationObserver.disconnect();
|
|
298
|
+
this.validationObserver = undefined;
|
|
299
|
+
}
|
|
270
300
|
}
|
|
271
301
|
/**
|
|
272
302
|
* Sets focus on the native `input` in `ion-input`. Use this method instead of the global
|
|
@@ -368,15 +398,15 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
368
398
|
* Renders the helper text or error text values
|
|
369
399
|
*/
|
|
370
400
|
renderHintText() {
|
|
371
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
401
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
372
402
|
return [
|
|
373
403
|
h("div", { id: helperTextId, class: "helper-text" }, helperText),
|
|
374
|
-
h("div", { id: errorTextId, class: "error-text", "aria-live":
|
|
404
|
+
h("div", { id: errorTextId, class: "error-text", role: isInvalid && errorText ? 'alert' : undefined, "aria-live": isInvalid && errorText ? 'polite' : 'off', "aria-atomic": "true" }, isInvalid && errorText ? errorText : ''),
|
|
375
405
|
];
|
|
376
406
|
}
|
|
377
407
|
getHintTextID() {
|
|
378
|
-
const {
|
|
379
|
-
if (
|
|
408
|
+
const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
|
|
409
|
+
if (isInvalid && errorText) {
|
|
380
410
|
return errorTextId;
|
|
381
411
|
}
|
|
382
412
|
if (helperText) {
|
|
@@ -489,7 +519,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
489
519
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
490
520
|
*/
|
|
491
521
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
492
|
-
return (h(Host, { key: '
|
|
522
|
+
return (h(Host, { key: 'fcb5d96c872ce63cb6252b6d7d302ab30e6ea83b', class: createColorClasses(this.color, {
|
|
493
523
|
[mode]: true,
|
|
494
524
|
'has-value': hasValue,
|
|
495
525
|
'has-focus': hasFocus,
|
|
@@ -500,14 +530,14 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
500
530
|
'in-item': inItem,
|
|
501
531
|
'in-item-color': hostContext('ion-item.ion-color', this.el),
|
|
502
532
|
'input-disabled': disabled,
|
|
503
|
-
}) }, h("label", { key: '
|
|
533
|
+
}) }, h("label", { key: '73bcaaf2527cbf295283b4581046fb9b361ce2dd', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '22dd6c6623a284e6a51910fa47b0a2ebdbd5971d', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: 'c02dac4336eb8e7570ed07cfa18fadca4bc6dd9d', name: "start" }), h("input", Object.assign({ key: '42fc6409e9ee51feed480a672bf074cfa6f74c8f', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (h("button", { key: 'b124a2b970542daa6fcb45ac5546a9fde22b45be', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
|
|
504
534
|
/**
|
|
505
535
|
* This prevents mobile browsers from
|
|
506
536
|
* blurring the input when the clear
|
|
507
537
|
* button is activated.
|
|
508
538
|
*/
|
|
509
539
|
ev.preventDefault();
|
|
510
|
-
}, onClick: this.clearTextInput }, h("ion-icon", { key: '
|
|
540
|
+
}, onClick: this.clearTextInput }, h("ion-icon", { key: '243037c08825c2bf26c53bb9507f7cd73e56a4de', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '9faff25f968c089b7716a8afb405ba5afb86a1ac', name: "end" })), shouldRenderHighlight && h("div", { key: '54bf3da059a2bf30e4990ba34abb23fd3bf137b0', class: "input-highlight" })), this.renderBottomContent()));
|
|
511
541
|
}
|
|
512
542
|
get el() { return this; }
|
|
513
543
|
static get watchers() { return {
|
|
@@ -556,6 +586,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
|
|
|
556
586
|
"type": [1],
|
|
557
587
|
"value": [1032],
|
|
558
588
|
"hasFocus": [32],
|
|
589
|
+
"isInvalid": [32],
|
|
559
590
|
"setFocus": [64],
|
|
560
591
|
"getInputElement": [64]
|
|
561
592
|
}, [[2, "click", "onClickCapture"]], {
|
|
@@ -40,6 +40,10 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
40
40
|
* is applied in both cases.
|
|
41
41
|
*/
|
|
42
42
|
this.hasFocus = false;
|
|
43
|
+
/**
|
|
44
|
+
* Track validation state for proper aria-live announcements
|
|
45
|
+
*/
|
|
46
|
+
this.isInvalid = false;
|
|
43
47
|
/**
|
|
44
48
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
45
49
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -186,10 +190,31 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
186
190
|
this.el.click();
|
|
187
191
|
}
|
|
188
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Checks if the textarea is in an invalid state based on validation classes
|
|
195
|
+
*/
|
|
196
|
+
checkValidationState() {
|
|
197
|
+
return this.el.classList.contains('ion-touched') && this.el.classList.contains('ion-invalid');
|
|
198
|
+
}
|
|
189
199
|
connectedCallback() {
|
|
190
200
|
const { el } = this;
|
|
191
201
|
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
|
|
192
202
|
this.notchController = createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
|
|
203
|
+
// Watch for class changes to update validation state
|
|
204
|
+
if (Build.isBrowser) {
|
|
205
|
+
this.validationObserver = new MutationObserver(() => {
|
|
206
|
+
const newIsInvalid = this.checkValidationState();
|
|
207
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
208
|
+
this.isInvalid = newIsInvalid;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
this.validationObserver.observe(el, {
|
|
212
|
+
attributes: true,
|
|
213
|
+
attributeFilter: ['class'],
|
|
214
|
+
});
|
|
215
|
+
// Set initial state
|
|
216
|
+
this.isInvalid = this.checkValidationState();
|
|
217
|
+
}
|
|
193
218
|
this.debounceChanged();
|
|
194
219
|
if (Build.isBrowser) {
|
|
195
220
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -211,6 +236,11 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
211
236
|
this.notchController.destroy();
|
|
212
237
|
this.notchController = undefined;
|
|
213
238
|
}
|
|
239
|
+
// Clean up validation observer to prevent memory leaks
|
|
240
|
+
if (this.validationObserver) {
|
|
241
|
+
this.validationObserver.disconnect();
|
|
242
|
+
this.validationObserver = undefined;
|
|
243
|
+
}
|
|
214
244
|
}
|
|
215
245
|
componentWillLoad() {
|
|
216
246
|
this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
|
|
@@ -381,15 +411,15 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
381
411
|
* Renders the helper text or error text values
|
|
382
412
|
*/
|
|
383
413
|
renderHintText() {
|
|
384
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
414
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
385
415
|
return [
|
|
386
416
|
h("div", { id: helperTextId, class: "helper-text" }, helperText),
|
|
387
|
-
h("div", { id: errorTextId, class: "error-text" }, errorText),
|
|
417
|
+
h("div", { id: errorTextId, class: "error-text", role: isInvalid && errorText ? 'alert' : undefined, "aria-live": isInvalid && errorText ? 'polite' : 'off', "aria-atomic": "true" }, isInvalid && errorText ? errorText : ''),
|
|
388
418
|
];
|
|
389
419
|
}
|
|
390
420
|
getHintTextID() {
|
|
391
|
-
const {
|
|
392
|
-
if (
|
|
421
|
+
const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
|
|
422
|
+
if (isInvalid && errorText) {
|
|
393
423
|
return errorTextId;
|
|
394
424
|
}
|
|
395
425
|
if (helperText) {
|
|
@@ -448,7 +478,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
448
478
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
449
479
|
*/
|
|
450
480
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
451
|
-
return (h(Host, { key: '
|
|
481
|
+
return (h(Host, { key: 'b67193cbdbd70844901e7f58346cb6021ea8ff9b', class: createColorClasses(this.color, {
|
|
452
482
|
[mode]: true,
|
|
453
483
|
'has-value': hasValue,
|
|
454
484
|
'has-focus': hasFocus,
|
|
@@ -457,7 +487,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
457
487
|
[`textarea-shape-${shape}`]: shape !== undefined,
|
|
458
488
|
[`textarea-label-placement-${labelPlacement}`]: true,
|
|
459
489
|
'textarea-disabled': disabled,
|
|
460
|
-
}) }, h("label", { key: '
|
|
490
|
+
}) }, h("label", { key: '11cb5c9eefd6c82f0d94a283f65e3bfea7cfd31f', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'f23c54b1d02b07ae54a729a1c871788b804031fe', class: "textarea-wrapper-inner" }, h("div", { key: 'd467679b2132c67307eacf29f0cb9cffe9fa3f70', class: "start-slot-wrapper" }, h("slot", { key: 'e75629aeb663218b24598803a400c0755bbc0958', name: "start" })), h("div", { key: 'dacfbeaee43984034a59375adcda329e5cf73de4', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: 'cee91118b3d2fa7f6b0660bab8384cd508e432b1', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes), value)), h("div", { key: '7802d173e3e338776ae2a095037191fdfe771dda', class: "end-slot-wrapper" }, h("slot", { key: '1b721a633799701fab2527b046c03ee23fa30c2e', name: "end" }))), shouldRenderHighlight && h("div", { key: 'ae2836d8d7ab9a1a6e70c3c17bbf99e30835c39d', class: "textarea-highlight" })), this.renderBottomContent()));
|
|
461
491
|
}
|
|
462
492
|
get el() { return this; }
|
|
463
493
|
static get watchers() { return {
|
|
@@ -499,6 +529,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
|
|
|
499
529
|
"labelPlacement": [1, "label-placement"],
|
|
500
530
|
"shape": [1],
|
|
501
531
|
"hasFocus": [32],
|
|
532
|
+
"isInvalid": [32],
|
|
502
533
|
"setFocus": [64],
|
|
503
534
|
"getInputElement": [64]
|
|
504
535
|
}, [[2, "click", "onClickCapture"]], {
|
|
@@ -44,6 +44,10 @@ const Input = class {
|
|
|
44
44
|
* is applied in both cases.
|
|
45
45
|
*/
|
|
46
46
|
this.hasFocus = false;
|
|
47
|
+
/**
|
|
48
|
+
* Track validation state for proper aria-live announcements
|
|
49
|
+
*/
|
|
50
|
+
this.isInvalid = false;
|
|
47
51
|
/**
|
|
48
52
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
49
53
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -228,10 +232,31 @@ const Input = class {
|
|
|
228
232
|
componentWillLoad() {
|
|
229
233
|
this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
|
|
230
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Checks if the input is in an invalid state based on validation classes
|
|
237
|
+
*/
|
|
238
|
+
checkValidationState() {
|
|
239
|
+
return this.el.classList.contains('ion-touched') && this.el.classList.contains('ion-invalid');
|
|
240
|
+
}
|
|
231
241
|
connectedCallback() {
|
|
232
242
|
const { el } = this;
|
|
233
243
|
this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
|
|
234
244
|
this.notchController = notchController.createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
|
|
245
|
+
// Watch for class changes to update validation state
|
|
246
|
+
{
|
|
247
|
+
this.validationObserver = new MutationObserver(() => {
|
|
248
|
+
const newIsInvalid = this.checkValidationState();
|
|
249
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
250
|
+
this.isInvalid = newIsInvalid;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
this.validationObserver.observe(el, {
|
|
254
|
+
attributes: true,
|
|
255
|
+
attributeFilter: ['class'],
|
|
256
|
+
});
|
|
257
|
+
// Set initial state
|
|
258
|
+
this.isInvalid = this.checkValidationState();
|
|
259
|
+
}
|
|
235
260
|
this.debounceChanged();
|
|
236
261
|
{
|
|
237
262
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -268,6 +293,11 @@ const Input = class {
|
|
|
268
293
|
this.notchController.destroy();
|
|
269
294
|
this.notchController = undefined;
|
|
270
295
|
}
|
|
296
|
+
// Clean up validation observer to prevent memory leaks
|
|
297
|
+
if (this.validationObserver) {
|
|
298
|
+
this.validationObserver.disconnect();
|
|
299
|
+
this.validationObserver = undefined;
|
|
300
|
+
}
|
|
271
301
|
}
|
|
272
302
|
/**
|
|
273
303
|
* Sets focus on the native `input` in `ion-input`. Use this method instead of the global
|
|
@@ -369,15 +399,15 @@ const Input = class {
|
|
|
369
399
|
* Renders the helper text or error text values
|
|
370
400
|
*/
|
|
371
401
|
renderHintText() {
|
|
372
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
402
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
373
403
|
return [
|
|
374
404
|
index.h("div", { id: helperTextId, class: "helper-text" }, helperText),
|
|
375
|
-
index.h("div", { id: errorTextId, class: "error-text", "aria-live":
|
|
405
|
+
index.h("div", { id: errorTextId, class: "error-text", role: isInvalid && errorText ? 'alert' : undefined, "aria-live": isInvalid && errorText ? 'polite' : 'off', "aria-atomic": "true" }, isInvalid && errorText ? errorText : ''),
|
|
376
406
|
];
|
|
377
407
|
}
|
|
378
408
|
getHintTextID() {
|
|
379
|
-
const {
|
|
380
|
-
if (
|
|
409
|
+
const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
|
|
410
|
+
if (isInvalid && errorText) {
|
|
381
411
|
return errorTextId;
|
|
382
412
|
}
|
|
383
413
|
if (helperText) {
|
|
@@ -490,7 +520,7 @@ const Input = class {
|
|
|
490
520
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
491
521
|
*/
|
|
492
522
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
493
|
-
return (index.h(index.Host, { key: '
|
|
523
|
+
return (index.h(index.Host, { key: 'fcb5d96c872ce63cb6252b6d7d302ab30e6ea83b', class: theme.createColorClasses(this.color, {
|
|
494
524
|
[mode]: true,
|
|
495
525
|
'has-value': hasValue,
|
|
496
526
|
'has-focus': hasFocus,
|
|
@@ -501,14 +531,14 @@ const Input = class {
|
|
|
501
531
|
'in-item': inItem,
|
|
502
532
|
'in-item-color': theme.hostContext('ion-item.ion-color', this.el),
|
|
503
533
|
'input-disabled': disabled,
|
|
504
|
-
}) }, index.h("label", { key: '
|
|
534
|
+
}) }, index.h("label", { key: '73bcaaf2527cbf295283b4581046fb9b361ce2dd', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: '22dd6c6623a284e6a51910fa47b0a2ebdbd5971d', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: 'c02dac4336eb8e7570ed07cfa18fadca4bc6dd9d', name: "start" }), index.h("input", Object.assign({ key: '42fc6409e9ee51feed480a672bf074cfa6f74c8f', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (index.h("button", { key: 'b124a2b970542daa6fcb45ac5546a9fde22b45be', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
|
|
505
535
|
/**
|
|
506
536
|
* This prevents mobile browsers from
|
|
507
537
|
* blurring the input when the clear
|
|
508
538
|
* button is activated.
|
|
509
539
|
*/
|
|
510
540
|
ev.preventDefault();
|
|
511
|
-
}, onClick: this.clearTextInput }, index.h("ion-icon", { key: '
|
|
541
|
+
}, onClick: this.clearTextInput }, index.h("ion-icon", { key: '243037c08825c2bf26c53bb9507f7cd73e56a4de', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: '9faff25f968c089b7716a8afb405ba5afb86a1ac', name: "end" })), shouldRenderHighlight && index.h("div", { key: '54bf3da059a2bf30e4990ba34abb23fd3bf137b0', class: "input-highlight" })), this.renderBottomContent()));
|
|
512
542
|
}
|
|
513
543
|
get el() { return index.getElement(this); }
|
|
514
544
|
static get watchers() { return {
|
|
@@ -42,6 +42,10 @@ const Textarea = class {
|
|
|
42
42
|
* is applied in both cases.
|
|
43
43
|
*/
|
|
44
44
|
this.hasFocus = false;
|
|
45
|
+
/**
|
|
46
|
+
* Track validation state for proper aria-live announcements
|
|
47
|
+
*/
|
|
48
|
+
this.isInvalid = false;
|
|
45
49
|
/**
|
|
46
50
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
47
51
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -188,10 +192,31 @@ const Textarea = class {
|
|
|
188
192
|
this.el.click();
|
|
189
193
|
}
|
|
190
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Checks if the textarea is in an invalid state based on validation classes
|
|
197
|
+
*/
|
|
198
|
+
checkValidationState() {
|
|
199
|
+
return this.el.classList.contains('ion-touched') && this.el.classList.contains('ion-invalid');
|
|
200
|
+
}
|
|
191
201
|
connectedCallback() {
|
|
192
202
|
const { el } = this;
|
|
193
203
|
this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
|
|
194
204
|
this.notchController = notchController.createNotchController(el, () => this.notchSpacerEl, () => this.labelSlot);
|
|
205
|
+
// Watch for class changes to update validation state
|
|
206
|
+
{
|
|
207
|
+
this.validationObserver = new MutationObserver(() => {
|
|
208
|
+
const newIsInvalid = this.checkValidationState();
|
|
209
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
210
|
+
this.isInvalid = newIsInvalid;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
this.validationObserver.observe(el, {
|
|
214
|
+
attributes: true,
|
|
215
|
+
attributeFilter: ['class'],
|
|
216
|
+
});
|
|
217
|
+
// Set initial state
|
|
218
|
+
this.isInvalid = this.checkValidationState();
|
|
219
|
+
}
|
|
195
220
|
this.debounceChanged();
|
|
196
221
|
{
|
|
197
222
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -213,6 +238,11 @@ const Textarea = class {
|
|
|
213
238
|
this.notchController.destroy();
|
|
214
239
|
this.notchController = undefined;
|
|
215
240
|
}
|
|
241
|
+
// Clean up validation observer to prevent memory leaks
|
|
242
|
+
if (this.validationObserver) {
|
|
243
|
+
this.validationObserver.disconnect();
|
|
244
|
+
this.validationObserver = undefined;
|
|
245
|
+
}
|
|
216
246
|
}
|
|
217
247
|
componentWillLoad() {
|
|
218
248
|
this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
|
|
@@ -383,15 +413,15 @@ const Textarea = class {
|
|
|
383
413
|
* Renders the helper text or error text values
|
|
384
414
|
*/
|
|
385
415
|
renderHintText() {
|
|
386
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
416
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
387
417
|
return [
|
|
388
418
|
index.h("div", { id: helperTextId, class: "helper-text" }, helperText),
|
|
389
|
-
index.h("div", { id: errorTextId, class: "error-text" }, errorText),
|
|
419
|
+
index.h("div", { id: errorTextId, class: "error-text", role: isInvalid && errorText ? 'alert' : undefined, "aria-live": isInvalid && errorText ? 'polite' : 'off', "aria-atomic": "true" }, isInvalid && errorText ? errorText : ''),
|
|
390
420
|
];
|
|
391
421
|
}
|
|
392
422
|
getHintTextID() {
|
|
393
|
-
const {
|
|
394
|
-
if (
|
|
423
|
+
const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
|
|
424
|
+
if (isInvalid && errorText) {
|
|
395
425
|
return errorTextId;
|
|
396
426
|
}
|
|
397
427
|
if (helperText) {
|
|
@@ -450,7 +480,7 @@ const Textarea = class {
|
|
|
450
480
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
451
481
|
*/
|
|
452
482
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
453
|
-
return (index.h(index.Host, { key: '
|
|
483
|
+
return (index.h(index.Host, { key: 'b67193cbdbd70844901e7f58346cb6021ea8ff9b', class: theme.createColorClasses(this.color, {
|
|
454
484
|
[mode]: true,
|
|
455
485
|
'has-value': hasValue,
|
|
456
486
|
'has-focus': hasFocus,
|
|
@@ -459,7 +489,7 @@ const Textarea = class {
|
|
|
459
489
|
[`textarea-shape-${shape}`]: shape !== undefined,
|
|
460
490
|
[`textarea-label-placement-${labelPlacement}`]: true,
|
|
461
491
|
'textarea-disabled': disabled,
|
|
462
|
-
}) }, index.h("label", { key: '
|
|
492
|
+
}) }, index.h("label", { key: '11cb5c9eefd6c82f0d94a283f65e3bfea7cfd31f', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'f23c54b1d02b07ae54a729a1c871788b804031fe', class: "textarea-wrapper-inner" }, index.h("div", { key: 'd467679b2132c67307eacf29f0cb9cffe9fa3f70', class: "start-slot-wrapper" }, index.h("slot", { key: 'e75629aeb663218b24598803a400c0755bbc0958', name: "start" })), index.h("div", { key: 'dacfbeaee43984034a59375adcda329e5cf73de4', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, index.h("textarea", Object.assign({ key: 'cee91118b3d2fa7f6b0660bab8384cd508e432b1', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId }, this.inheritedAttributes), value)), index.h("div", { key: '7802d173e3e338776ae2a095037191fdfe771dda', class: "end-slot-wrapper" }, index.h("slot", { key: '1b721a633799701fab2527b046c03ee23fa30c2e', name: "end" }))), shouldRenderHighlight && index.h("div", { key: 'ae2836d8d7ab9a1a6e70c3c17bbf99e30835c39d', class: "textarea-highlight" })), this.renderBottomContent()));
|
|
463
493
|
}
|
|
464
494
|
get el() { return index.getElement(this); }
|
|
465
495
|
static get watchers() { return {
|