@ionic/core 8.7.3-dev.11755195979.1ec83531 → 8.7.3-dev.11755285796.12743331

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.
@@ -43,10 +43,6 @@ 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;
50
46
  /**
51
47
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
52
48
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -231,31 +227,10 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
231
227
  componentWillLoad() {
232
228
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
233
229
  }
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
- }
240
230
  connectedCallback() {
241
231
  const { el } = this;
242
232
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
243
233
  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
- }
259
234
  this.debounceChanged();
260
235
  if (Build.isBrowser) {
261
236
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -292,11 +267,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
292
267
  this.notchController.destroy();
293
268
  this.notchController = undefined;
294
269
  }
295
- // Clean up validation observer to prevent memory leaks
296
- if (this.validationObserver) {
297
- this.validationObserver.disconnect();
298
- this.validationObserver = undefined;
299
- }
300
270
  }
301
271
  /**
302
272
  * Sets focus on the native `input` in `ion-input`. Use this method instead of the global
@@ -398,15 +368,15 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
398
368
  * Renders the helper text or error text values
399
369
  */
400
370
  renderHintText() {
401
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
371
+ const { helperText, errorText, helperTextId, errorTextId } = this;
402
372
  return [
403
373
  h("div", { id: helperTextId, class: "helper-text" }, helperText),
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 : ''),
374
+ h("div", { id: errorTextId, class: "error-text" }, errorText),
405
375
  ];
406
376
  }
407
377
  getHintTextID() {
408
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
409
- if (isInvalid && errorText) {
378
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
379
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
410
380
  return errorTextId;
411
381
  }
412
382
  if (helperText) {
@@ -519,7 +489,7 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
519
489
  * TODO(FW-5592): Remove hasStartEndSlots condition
520
490
  */
521
491
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
522
- return (h(Host, { key: 'fcb5d96c872ce63cb6252b6d7d302ab30e6ea83b', class: createColorClasses(this.color, {
492
+ return (h(Host, { key: '41b2526627e7d2773a80f011b123284203a71ca0', class: createColorClasses(this.color, {
523
493
  [mode]: true,
524
494
  'has-value': hasValue,
525
495
  'has-focus': hasFocus,
@@ -530,14 +500,14 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
530
500
  'in-item': inItem,
531
501
  'in-item-color': hostContext('ion-item.ion-color', this.el),
532
502
  'input-disabled': disabled,
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) => {
503
+ }) }, h("label", { key: '9ab078363e32528102b441ad1791d83f86fdcbdc', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'e34b594980ec62e4c618e827fadf7669a39ad0d8', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '12dc04ead5502e9e5736240e918bf9331bf7b5d9', name: "start" }), h("input", Object.assign({ key: 'df356eb4ced23109b2c0242f36dc043aba8782d6', 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: 'f79f68cabcd4ea99419331174a377827db0c0741', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
534
504
  /**
535
505
  * This prevents mobile browsers from
536
506
  * blurring the input when the clear
537
507
  * button is activated.
538
508
  */
539
509
  ev.preventDefault();
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()));
510
+ }, onClick: this.clearTextInput }, h("ion-icon", { key: '237ec07ec2e10f08818a332bb596578c2c49f770', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: '1f0a3624aa3e8dc3c307a6762230ab698768a5e5', name: "end" })), shouldRenderHighlight && h("div", { key: '8a8cbb82695a722a0010b53dd0b1f1f97534a20b', class: "input-highlight" })), this.renderBottomContent()));
541
511
  }
542
512
  get el() { return this; }
543
513
  static get watchers() { return {
@@ -586,7 +556,6 @@ const Input = /*@__PURE__*/ proxyCustomElement(class Input extends HTMLElement {
586
556
  "type": [1],
587
557
  "value": [1032],
588
558
  "hasFocus": [32],
589
- "isInvalid": [32],
590
559
  "setFocus": [64],
591
560
  "getInputElement": [64]
592
561
  }, [[2, "click", "onClickCapture"]], {
@@ -182,6 +182,14 @@ const Refresher = /*@__PURE__*/ proxyCustomElement(class Refresher extends HTMLE
182
182
  this.beginRefresh();
183
183
  this.didRefresh = true;
184
184
  hapticImpact({ style: ImpactStyle.Light });
185
+ /**
186
+ * Clear focus from any active element to prevent scroll jumps
187
+ * when the refresh completes
188
+ */
189
+ const activeElement = document.activeElement;
190
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
191
+ activeElement.blur();
192
+ }
185
193
  /**
186
194
  * Translate the content element otherwise when pointer is removed
187
195
  * from screen the scroll content will bounce back over the refresher
@@ -597,6 +605,15 @@ const Refresher = /*@__PURE__*/ proxyCustomElement(class Refresher extends HTMLE
597
605
  this.state = 8 /* RefresherState.Refreshing */;
598
606
  // place the content in a hangout position while it thinks
599
607
  this.setCss(this.pullMin, this.snapbackDuration, true, '');
608
+ /**
609
+ * Clear focus from any active element to prevent the browser
610
+ * from restoring focus and causing scroll jumps after refresh.
611
+ * This ensures the view stays at the top after refresh completes.
612
+ */
613
+ const activeElement = document.activeElement;
614
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
615
+ activeElement.blur();
616
+ }
600
617
  // emit "refresh" because it was pulled down far enough
601
618
  // and they let go to begin refreshing
602
619
  this.ionRefresh.emit({
@@ -679,7 +696,7 @@ const Refresher = /*@__PURE__*/ proxyCustomElement(class Refresher extends HTMLE
679
696
  }
680
697
  render() {
681
698
  const mode = getIonMode(this);
682
- return (h(Host, { key: '8c7a5cc32da02a9cbeaa954258148683f60a6d1b', slot: "fixed", class: {
699
+ return (h(Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
683
700
  [mode]: true,
684
701
  // Used internally for styling
685
702
  [`refresher-${mode}`]: true,
@@ -40,10 +40,6 @@ 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;
47
43
  /**
48
44
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
49
45
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -190,31 +186,10 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
190
186
  this.el.click();
191
187
  }
192
188
  }
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
- }
199
189
  connectedCallback() {
200
190
  const { el } = this;
201
191
  this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
202
192
  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
- }
218
193
  this.debounceChanged();
219
194
  if (Build.isBrowser) {
220
195
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -236,11 +211,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
236
211
  this.notchController.destroy();
237
212
  this.notchController = undefined;
238
213
  }
239
- // Clean up validation observer to prevent memory leaks
240
- if (this.validationObserver) {
241
- this.validationObserver.disconnect();
242
- this.validationObserver = undefined;
243
- }
244
214
  }
245
215
  componentWillLoad() {
246
216
  this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
@@ -411,15 +381,15 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
411
381
  * Renders the helper text or error text values
412
382
  */
413
383
  renderHintText() {
414
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
384
+ const { helperText, errorText, helperTextId, errorTextId } = this;
415
385
  return [
416
386
  h("div", { id: helperTextId, class: "helper-text" }, helperText),
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 : ''),
387
+ h("div", { id: errorTextId, class: "error-text" }, errorText),
418
388
  ];
419
389
  }
420
390
  getHintTextID() {
421
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
422
- if (isInvalid && errorText) {
391
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
392
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
423
393
  return errorTextId;
424
394
  }
425
395
  if (helperText) {
@@ -478,7 +448,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
478
448
  * TODO(FW-5592): Remove hasStartEndSlots condition
479
449
  */
480
450
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
481
- return (h(Host, { key: 'b67193cbdbd70844901e7f58346cb6021ea8ff9b', class: createColorClasses(this.color, {
451
+ return (h(Host, { key: 'd9f2ede0107987fc42c99e310cd2336bad5a5755', class: createColorClasses(this.color, {
482
452
  [mode]: true,
483
453
  'has-value': hasValue,
484
454
  'has-focus': hasFocus,
@@ -487,7 +457,7 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
487
457
  [`textarea-shape-${shape}`]: shape !== undefined,
488
458
  [`textarea-label-placement-${labelPlacement}`]: true,
489
459
  'textarea-disabled': disabled,
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()));
460
+ }) }, h("label", { key: '9de598b95237462bb3bccffaefe83afbb43554b8', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'e33c426c6541d723ccc246bb404c03687726ff83', class: "textarea-wrapper-inner" }, h("div", { key: '521e11af9d54d281b0a2b1c25bcfc6f742c18296', class: "start-slot-wrapper" }, h("slot", { key: '515523f6ca3ce0e5dd08f3275c21a190fb1ca177', name: "start" })), h("div", { key: '916e01e00de8400ae00ef06bc1fb62d8be2eee08', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: '810271e6532d90e27dab1fcb26546113c1ce9cb0', 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: '80aca9ea9546dca9d38efd291a6b0be384bb6978', class: "end-slot-wrapper" }, h("slot", { key: '407fab16c66a9f4a542369bfecc0d9afa0065977', name: "end" }))), shouldRenderHighlight && h("div", { key: 'f00523a6698fac8a1996e04303487bef01d10f25', class: "textarea-highlight" })), this.renderBottomContent()));
491
461
  }
492
462
  get el() { return this; }
493
463
  static get watchers() { return {
@@ -529,7 +499,6 @@ const Textarea = /*@__PURE__*/ proxyCustomElement(class Textarea extends HTMLEle
529
499
  "labelPlacement": [1, "label-placement"],
530
500
  "shape": [1],
531
501
  "hasFocus": [32],
532
- "isInvalid": [32],
533
502
  "setFocus": [64],
534
503
  "getInputElement": [64]
535
504
  }, [[2, "click", "onClickCapture"]], {
@@ -44,10 +44,6 @@ 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;
51
47
  /**
52
48
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
53
49
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -232,31 +228,10 @@ const Input = class {
232
228
  componentWillLoad() {
233
229
  this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
234
230
  }
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
- }
241
231
  connectedCallback() {
242
232
  const { el } = this;
243
233
  this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
244
234
  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
- }
260
235
  this.debounceChanged();
261
236
  {
262
237
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -293,11 +268,6 @@ const Input = class {
293
268
  this.notchController.destroy();
294
269
  this.notchController = undefined;
295
270
  }
296
- // Clean up validation observer to prevent memory leaks
297
- if (this.validationObserver) {
298
- this.validationObserver.disconnect();
299
- this.validationObserver = undefined;
300
- }
301
271
  }
302
272
  /**
303
273
  * Sets focus on the native `input` in `ion-input`. Use this method instead of the global
@@ -399,15 +369,15 @@ const Input = class {
399
369
  * Renders the helper text or error text values
400
370
  */
401
371
  renderHintText() {
402
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
372
+ const { helperText, errorText, helperTextId, errorTextId } = this;
403
373
  return [
404
374
  index.h("div", { id: helperTextId, class: "helper-text" }, helperText),
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 : ''),
375
+ index.h("div", { id: errorTextId, class: "error-text" }, errorText),
406
376
  ];
407
377
  }
408
378
  getHintTextID() {
409
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
410
- if (isInvalid && errorText) {
379
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
380
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
411
381
  return errorTextId;
412
382
  }
413
383
  if (helperText) {
@@ -520,7 +490,7 @@ const Input = class {
520
490
  * TODO(FW-5592): Remove hasStartEndSlots condition
521
491
  */
522
492
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
523
- return (index.h(index.Host, { key: 'fcb5d96c872ce63cb6252b6d7d302ab30e6ea83b', class: theme.createColorClasses(this.color, {
493
+ return (index.h(index.Host, { key: '41b2526627e7d2773a80f011b123284203a71ca0', class: theme.createColorClasses(this.color, {
524
494
  [mode]: true,
525
495
  'has-value': hasValue,
526
496
  'has-focus': hasFocus,
@@ -531,14 +501,14 @@ const Input = class {
531
501
  'in-item': inItem,
532
502
  'in-item-color': theme.hostContext('ion-item.ion-color', this.el),
533
503
  'input-disabled': disabled,
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) => {
504
+ }) }, index.h("label", { key: '9ab078363e32528102b441ad1791d83f86fdcbdc', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'e34b594980ec62e4c618e827fadf7669a39ad0d8', class: "native-wrapper", onClick: this.onLabelClick }, index.h("slot", { key: '12dc04ead5502e9e5736240e918bf9331bf7b5d9', name: "start" }), index.h("input", Object.assign({ key: 'df356eb4ced23109b2c0242f36dc043aba8782d6', 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: 'f79f68cabcd4ea99419331174a377827db0c0741', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
535
505
  /**
536
506
  * This prevents mobile browsers from
537
507
  * blurring the input when the clear
538
508
  * button is activated.
539
509
  */
540
510
  ev.preventDefault();
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()));
511
+ }, onClick: this.clearTextInput }, index.h("ion-icon", { key: '237ec07ec2e10f08818a332bb596578c2c49f770', "aria-hidden": "true", icon: clearIconData }))), index.h("slot", { key: '1f0a3624aa3e8dc3c307a6762230ab698768a5e5', name: "end" })), shouldRenderHighlight && index.h("div", { key: '8a8cbb82695a722a0010b53dd0b1f1f97534a20b', class: "input-highlight" })), this.renderBottomContent()));
542
512
  }
543
513
  get el() { return index.getElement(this); }
544
514
  static get watchers() { return {
@@ -375,6 +375,14 @@ const Refresher = class {
375
375
  this.beginRefresh();
376
376
  this.didRefresh = true;
377
377
  haptic.hapticImpact({ style: haptic.ImpactStyle.Light });
378
+ /**
379
+ * Clear focus from any active element to prevent scroll jumps
380
+ * when the refresh completes
381
+ */
382
+ const activeElement = document.activeElement;
383
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
384
+ activeElement.blur();
385
+ }
378
386
  /**
379
387
  * Translate the content element otherwise when pointer is removed
380
388
  * from screen the scroll content will bounce back over the refresher
@@ -790,6 +798,15 @@ const Refresher = class {
790
798
  this.state = 8 /* RefresherState.Refreshing */;
791
799
  // place the content in a hangout position while it thinks
792
800
  this.setCss(this.pullMin, this.snapbackDuration, true, '');
801
+ /**
802
+ * Clear focus from any active element to prevent the browser
803
+ * from restoring focus and causing scroll jumps after refresh.
804
+ * This ensures the view stays at the top after refresh completes.
805
+ */
806
+ const activeElement = document.activeElement;
807
+ if ((activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur) !== undefined) {
808
+ activeElement.blur();
809
+ }
793
810
  // emit "refresh" because it was pulled down far enough
794
811
  // and they let go to begin refreshing
795
812
  this.ionRefresh.emit({
@@ -872,7 +889,7 @@ const Refresher = class {
872
889
  }
873
890
  render() {
874
891
  const mode = ionicGlobal.getIonMode(this);
875
- return (index.h(index.Host, { key: '8c7a5cc32da02a9cbeaa954258148683f60a6d1b', slot: "fixed", class: {
892
+ return (index.h(index.Host, { key: '2d1bd880877b698604542ab2d602d38b9504d975', slot: "fixed", class: {
876
893
  [mode]: true,
877
894
  // Used internally for styling
878
895
  [`refresher-${mode}`]: true,
@@ -42,10 +42,6 @@ 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;
49
45
  /**
50
46
  * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
51
47
  * Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
@@ -192,31 +188,10 @@ const Textarea = class {
192
188
  this.el.click();
193
189
  }
194
190
  }
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
- }
201
191
  connectedCallback() {
202
192
  const { el } = this;
203
193
  this.slotMutationController = input_utils.createSlotMutationController(el, ['label', 'start', 'end'], () => index.forceUpdate(this));
204
194
  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
- }
220
195
  this.debounceChanged();
221
196
  {
222
197
  document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
@@ -238,11 +213,6 @@ const Textarea = class {
238
213
  this.notchController.destroy();
239
214
  this.notchController = undefined;
240
215
  }
241
- // Clean up validation observer to prevent memory leaks
242
- if (this.validationObserver) {
243
- this.validationObserver.disconnect();
244
- this.validationObserver = undefined;
245
- }
246
216
  }
247
217
  componentWillLoad() {
248
218
  this.inheritedAttributes = Object.assign(Object.assign({}, helpers.inheritAriaAttributes(this.el)), helpers.inheritAttributes(this.el, ['data-form-type', 'title', 'tabindex', 'dir']));
@@ -413,15 +383,15 @@ const Textarea = class {
413
383
  * Renders the helper text or error text values
414
384
  */
415
385
  renderHintText() {
416
- const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
386
+ const { helperText, errorText, helperTextId, errorTextId } = this;
417
387
  return [
418
388
  index.h("div", { id: helperTextId, class: "helper-text" }, helperText),
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 : ''),
389
+ index.h("div", { id: errorTextId, class: "error-text" }, errorText),
420
390
  ];
421
391
  }
422
392
  getHintTextID() {
423
- const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
424
- if (isInvalid && errorText) {
393
+ const { el, helperText, errorText, helperTextId, errorTextId } = this;
394
+ if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
425
395
  return errorTextId;
426
396
  }
427
397
  if (helperText) {
@@ -480,7 +450,7 @@ const Textarea = class {
480
450
  * TODO(FW-5592): Remove hasStartEndSlots condition
481
451
  */
482
452
  const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
483
- return (index.h(index.Host, { key: 'b67193cbdbd70844901e7f58346cb6021ea8ff9b', class: theme.createColorClasses(this.color, {
453
+ return (index.h(index.Host, { key: 'd9f2ede0107987fc42c99e310cd2336bad5a5755', class: theme.createColorClasses(this.color, {
484
454
  [mode]: true,
485
455
  'has-value': hasValue,
486
456
  'has-focus': hasFocus,
@@ -489,7 +459,7 @@ const Textarea = class {
489
459
  [`textarea-shape-${shape}`]: shape !== undefined,
490
460
  [`textarea-label-placement-${labelPlacement}`]: true,
491
461
  'textarea-disabled': disabled,
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()));
462
+ }) }, index.h("label", { key: '9de598b95237462bb3bccffaefe83afbb43554b8', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), index.h("div", { key: 'e33c426c6541d723ccc246bb404c03687726ff83', class: "textarea-wrapper-inner" }, index.h("div", { key: '521e11af9d54d281b0a2b1c25bcfc6f742c18296', class: "start-slot-wrapper" }, index.h("slot", { key: '515523f6ca3ce0e5dd08f3275c21a190fb1ca177', name: "start" })), index.h("div", { key: '916e01e00de8400ae00ef06bc1fb62d8be2eee08', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, index.h("textarea", Object.assign({ key: '810271e6532d90e27dab1fcb26546113c1ce9cb0', 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: '80aca9ea9546dca9d38efd291a6b0be384bb6978', class: "end-slot-wrapper" }, index.h("slot", { key: '407fab16c66a9f4a542369bfecc0d9afa0065977', name: "end" }))), shouldRenderHighlight && index.h("div", { key: 'f00523a6698fac8a1996e04303487bef01d10f25', class: "textarea-highlight" })), this.renderBottomContent()));
493
463
  }
494
464
  get el() { return index.getElement(this); }
495
465
  static get watchers() { return {