voyager-ionic-core 8.7.6 → 8.7.11
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/button.js +3 -7
- package/components/checkbox.js +64 -13
- package/components/header.js +42 -4
- package/components/index2.js +74 -3
- package/components/ion-accordion.js +93 -14
- package/components/ion-datetime.js +35 -2
- package/components/ion-input.js +6 -13
- package/components/ion-select.js +59 -10
- package/components/ion-textarea.js +5 -12
- package/components/ion-toggle.js +63 -16
- package/components/radio-group.js +60 -7
- package/components/validity.js +17 -0
- package/dist/cjs/{index-CD5Rjp23.js → index-094mMFB-.js} +76 -5
- package/dist/cjs/index.cjs.js +3 -3
- package/dist/cjs/ion-accordion_2.cjs.entry.js +91 -13
- package/dist/cjs/ion-app_8.cjs.entry.js +43 -5
- package/dist/cjs/ion-button_2.cjs.entry.js +3 -7
- package/dist/cjs/ion-checkbox.cjs.entry.js +61 -12
- package/dist/cjs/ion-datetime_3.cjs.entry.js +35 -2
- package/dist/cjs/ion-input.cjs.entry.js +6 -13
- package/dist/cjs/ion-modal.cjs.entry.js +1 -1
- package/dist/cjs/ion-nav_2.cjs.entry.js +1 -1
- package/dist/cjs/ion-popover.cjs.entry.js +1 -1
- package/dist/cjs/ion-radio_2.cjs.entry.js +57 -6
- package/dist/cjs/ion-select_3.cjs.entry.js +56 -9
- package/dist/cjs/ion-textarea.cjs.entry.js +5 -12
- package/dist/cjs/ion-toggle.cjs.entry.js +59 -14
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/{ios.transition-j9CclgEW.js → ios.transition-BOt_uW73.js} +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{md.transition-CwFyRSfv.js → md.transition-Dt968VXB.js} +1 -1
- package/dist/cjs/validity-BpS37YFM.js +19 -0
- package/dist/collection/components/accordion/accordion.js +93 -14
- package/dist/collection/components/button/button.js +3 -7
- package/dist/collection/components/checkbox/checkbox.js +68 -13
- package/dist/collection/components/datetime/datetime.js +35 -2
- package/dist/collection/components/header/header.ios.css +27 -1
- package/dist/collection/components/header/header.js +5 -4
- package/dist/collection/components/header/header.utils.js +37 -0
- package/dist/collection/components/input/input.js +6 -14
- package/dist/collection/components/radio-group/radio-group.js +64 -7
- package/dist/collection/components/select/select.js +60 -12
- package/dist/collection/components/textarea/textarea.js +5 -13
- package/dist/collection/components/toggle/toggle.js +63 -16
- package/dist/collection/utils/forms/index.js +1 -0
- package/dist/collection/utils/forms/validity.js +15 -0
- package/dist/collection/utils/test/playwright/page/utils/set-content.js +7 -0
- package/dist/collection/utils/test/playwright/page/utils/spy-on-event.js +32 -0
- package/dist/collection/utils/transition/index.js +74 -3
- package/dist/docs.json +1 -1
- package/dist/esm/{index-D6G2seR8.js → index-r2D9DEro.js} +76 -5
- package/dist/esm/index.js +3 -3
- package/dist/esm/ion-accordion_2.entry.js +91 -13
- package/dist/esm/ion-app_8.entry.js +43 -5
- package/dist/esm/ion-button_2.entry.js +3 -7
- package/dist/esm/ion-checkbox.entry.js +61 -12
- package/dist/esm/ion-datetime_3.entry.js +35 -2
- package/dist/esm/ion-input.entry.js +6 -13
- package/dist/esm/ion-modal.entry.js +1 -1
- package/dist/esm/ion-nav_2.entry.js +1 -1
- package/dist/esm/ion-popover.entry.js +1 -1
- package/dist/esm/ion-radio_2.entry.js +57 -6
- package/dist/esm/ion-select_3.entry.js +56 -9
- package/dist/esm/ion-textarea.entry.js +5 -12
- package/dist/esm/ion-toggle.entry.js +59 -14
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/{ios.transition-Bpq9ixwv.js → ios.transition-BDzw0_Hm.js} +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{md.transition-zOA0oanq.js → md.transition-BzDYi3qq.js} +1 -1
- package/dist/esm/validity-DJztqcrH.js +17 -0
- package/dist/ionic/index.esm.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-40c261a3.entry.js +4 -0
- package/dist/ionic/p-43ed1ef5.entry.js +4 -0
- package/dist/ionic/p-4e41ea20.entry.js +4 -0
- package/dist/ionic/{p-323421af.entry.js → p-5a39a99a.entry.js} +1 -1
- package/dist/ionic/p-5fb517e4.entry.js +4 -0
- package/dist/ionic/p-7380261c.entry.js +4 -0
- package/dist/ionic/{p-9a36e2e7.entry.js → p-95bddd49.entry.js} +1 -1
- package/dist/ionic/{p-DPhQmGJN.js → p-C7hRNDhM.js} +1 -1
- package/dist/ionic/p-DJztqcrH.js +4 -0
- package/dist/ionic/p-DUt5fQmA.js +4 -0
- package/dist/ionic/{p-9R1XyICs.js → p-DZRJwG4S.js} +1 -1
- package/dist/ionic/p-c19f63d0.entry.js +4 -0
- package/dist/ionic/p-cb93126d.entry.js +4 -0
- package/dist/ionic/p-d0a2a1ab.entry.js +4 -0
- package/dist/ionic/p-d1f54e28.entry.js +4 -0
- package/dist/ionic/p-d3014190.entry.js +4 -0
- package/dist/ionic/{p-de7b5fa3.entry.js → p-e16b69e1.entry.js} +1 -1
- package/dist/ionic/svg/checkbox-outline.svg +1 -0
- package/dist/ionic/svg/checkbox-sharp.svg +1 -0
- package/dist/ionic/svg/checkbox.svg +1 -0
- package/dist/ionic/svg/checkmark-circle-outline.svg +1 -0
- package/dist/ionic/svg/checkmark-circle-sharp.svg +1 -0
- package/dist/ionic/svg/checkmark-circle.svg +1 -0
- package/dist/ionic/svg/checkmark-done-circle-outline.svg +1 -0
- package/dist/ionic/svg/checkmark-done-circle-sharp.svg +1 -0
- package/dist/ionic/svg/checkmark-done-circle.svg +1 -0
- package/dist/ionic/svg/checkmark-done-outline.svg +1 -0
- package/dist/ionic/svg/checkmark-done-sharp.svg +1 -0
- package/dist/ionic/svg/checkmark-done.svg +1 -0
- package/dist/ionic/svg/checkmark-outline.svg +1 -0
- package/dist/ionic/svg/checkmark-sharp.svg +1 -0
- package/dist/ionic/svg/checkmark.svg +1 -0
- package/dist/ionic/svg/chevron-back-circle-outline.svg +1 -0
- package/dist/ionic/svg/chevron-back-circle-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-back-circle.svg +1 -0
- package/dist/ionic/svg/chevron-back-outline.svg +1 -0
- package/dist/ionic/svg/chevron-back-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-back.svg +1 -0
- package/dist/ionic/svg/chevron-collapse-outline.svg +1 -0
- package/dist/ionic/svg/chevron-collapse-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-collapse.svg +1 -0
- package/dist/ionic/svg/chevron-down-circle-outline.svg +1 -0
- package/dist/ionic/svg/chevron-down-circle-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-down-circle.svg +1 -0
- package/dist/ionic/svg/chevron-down-outline.svg +1 -0
- package/dist/ionic/svg/chevron-down-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-down.svg +1 -0
- package/dist/ionic/svg/chevron-expand-outline.svg +1 -0
- package/dist/ionic/svg/chevron-expand-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-expand.svg +1 -0
- package/dist/ionic/svg/chevron-forward-circle-outline.svg +1 -0
- package/dist/ionic/svg/chevron-forward-circle-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-forward-circle.svg +1 -0
- package/dist/ionic/svg/chevron-forward-outline.svg +1 -0
- package/dist/ionic/svg/chevron-forward-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-forward.svg +1 -0
- package/dist/ionic/svg/chevron-up-circle-outline.svg +1 -0
- package/dist/ionic/svg/chevron-up-circle-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-up-circle.svg +1 -0
- package/dist/ionic/svg/chevron-up-outline.svg +1 -0
- package/dist/ionic/svg/chevron-up-sharp.svg +1 -0
- package/dist/ionic/svg/chevron-up.svg +1 -0
- package/dist/ionic/svg/clipboard-outline.svg +1 -0
- package/dist/ionic/svg/clipboard-sharp.svg +1 -0
- package/dist/ionic/svg/clipboard.svg +1 -0
- package/dist/ionic/svg/close-circle-outline.svg +1 -0
- package/dist/ionic/svg/close-circle-sharp.svg +1 -0
- package/dist/ionic/svg/close-circle.svg +1 -0
- package/dist/ionic/svg/close-outline.svg +1 -0
- package/dist/ionic/svg/close-sharp.svg +1 -0
- package/dist/ionic/svg/close.svg +1 -0
- package/dist/ionic/svg/cloud-circle-outline.svg +1 -0
- package/dist/ionic/svg/cloud-circle-sharp.svg +1 -0
- package/dist/ionic/svg/cloud-circle.svg +1 -0
- package/dist/ionic/svg/cloud-done-outline.svg +1 -0
- package/dist/ionic/svg/cloud-done-sharp.svg +1 -0
- package/dist/ionic/svg/cloud-done.svg +1 -0
- package/dist/ionic/svg/cloud-download-outline.svg +1 -0
- package/dist/ionic/svg/cloud-download-sharp.svg +1 -0
- package/dist/ionic/svg/cloud-download.svg +1 -0
- package/dist/ionic/svg/cloud-offline-outline.svg +1 -0
- package/dist/ionic/svg/cloud-offline-sharp.svg +1 -0
- package/dist/ionic/svg/cloud-offline.svg +1 -0
- package/dist/ionic/svg/cloud-outline.svg +1 -0
- package/dist/ionic/svg/cloud-sharp.svg +1 -0
- package/dist/ionic/svg/cloud-upload-outline.svg +1 -0
- package/dist/ionic/svg/cloud-upload-sharp.svg +1 -0
- package/dist/ionic/svg/cloud-upload.svg +1 -0
- package/dist/ionic/svg/cloud.svg +1 -0
- package/dist/ionic/svg/cloudy-night-outline.svg +1 -0
- package/dist/ionic/svg/cloudy-night-sharp.svg +1 -0
- package/dist/ionic/svg/cloudy-night.svg +1 -0
- package/dist/ionic/svg/cloudy-outline.svg +1 -0
- package/dist/ionic/svg/cloudy-sharp.svg +1 -0
- package/dist/ionic/svg/cloudy.svg +1 -0
- package/dist/ionic/svg/code-download-outline.svg +1 -0
- package/dist/ionic/svg/code-download-sharp.svg +1 -0
- package/dist/ionic/svg/code-download.svg +1 -0
- package/dist/ionic/svg/code-outline.svg +1 -0
- package/dist/ionic/svg/code-sharp.svg +1 -0
- package/dist/ionic/svg/code-slash-outline.svg +1 -0
- package/dist/ionic/svg/code-slash-sharp.svg +1 -0
- package/dist/ionic/svg/code-slash.svg +1 -0
- package/dist/ionic/svg/code-working-outline.svg +1 -0
- package/dist/ionic/svg/code-working-sharp.svg +1 -0
- package/dist/ionic/svg/code-working.svg +1 -0
- package/dist/ionic/svg/code.svg +1 -0
- package/dist/ionic/svg/cog-outline.svg +1 -0
- package/dist/ionic/svg/cog-sharp.svg +1 -0
- package/dist/ionic/svg/cog.svg +1 -0
- package/dist/ionic/svg/color-fill-outline.svg +1 -0
- package/dist/ionic/svg/color-fill-sharp.svg +1 -0
- package/dist/ionic/svg/color-fill.svg +1 -0
- package/dist/ionic/svg/color-filter-outline.svg +1 -0
- package/dist/ionic/svg/color-filter-sharp.svg +1 -0
- package/dist/ionic/svg/color-filter.svg +1 -0
- package/dist/ionic/svg/color-palette-outline.svg +1 -0
- package/dist/ionic/svg/color-palette-sharp.svg +1 -0
- package/dist/ionic/svg/color-palette.svg +1 -0
- package/dist/ionic/svg/color-wand-outline.svg +1 -0
- package/dist/ionic/svg/color-wand-sharp.svg +1 -0
- package/dist/ionic/svg/color-wand.svg +1 -0
- package/dist/ionic/svg/compass-outline.svg +1 -0
- package/dist/ionic/svg/compass-sharp.svg +1 -0
- package/dist/ionic/svg/compass.svg +1 -0
- package/dist/ionic/svg/construct-outline.svg +1 -0
- package/dist/ionic/svg/construct-sharp.svg +1 -0
- package/dist/ionic/svg/construct.svg +1 -0
- package/dist/ionic/svg/contract-outline.svg +1 -0
- package/dist/ionic/svg/contract-sharp.svg +1 -0
- package/dist/ionic/svg/contract.svg +1 -0
- package/dist/ionic/svg/contrast-outline.svg +1 -0
- package/dist/ionic/svg/contrast-sharp.svg +1 -0
- package/dist/ionic/svg/contrast.svg +1 -0
- package/dist/ionic/svg/copy-outline.svg +1 -0
- package/dist/ionic/svg/copy-sharp.svg +1 -0
- package/dist/ionic/svg/copy.svg +1 -0
- package/dist/ionic/svg/create-outline.svg +1 -0
- package/dist/ionic/svg/create-sharp.svg +1 -0
- package/dist/ionic/svg/create.svg +1 -0
- package/dist/ionic/svg/crop-outline.svg +1 -0
- package/dist/ionic/svg/crop-sharp.svg +1 -0
- package/dist/ionic/svg/crop.svg +1 -0
- package/dist/ionic/svg/cube-outline.svg +1 -0
- package/dist/ionic/svg/cube-sharp.svg +1 -0
- package/dist/ionic/svg/cube.svg +1 -0
- package/dist/ionic/svg/cut-outline.svg +1 -0
- package/dist/ionic/svg/cut-sharp.svg +1 -0
- package/dist/ionic/svg/cut.svg +1 -0
- package/dist/ionic/svg/desktop-outline.svg +1 -0
- package/dist/ionic/svg/desktop-sharp.svg +1 -0
- package/dist/ionic/svg/desktop.svg +1 -0
- package/dist/ionic/svg/diamond-outline.svg +1 -0
- package/dist/ionic/svg/diamond-sharp.svg +1 -0
- package/dist/ionic/svg/diamond.svg +1 -0
- package/dist/ionic/svg/dice-outline.svg +1 -0
- package/dist/ionic/svg/dice-sharp.svg +1 -0
- package/dist/ionic/svg/dice.svg +1 -0
- package/dist/ionic/svg/disc-outline.svg +1 -0
- package/dist/ionic/svg/disc-sharp.svg +1 -0
- package/dist/ionic/svg/disc.svg +1 -0
- package/dist/ionic/svg/document-attach-outline.svg +1 -0
- package/dist/ionic/svg/document-attach-sharp.svg +1 -0
- package/dist/ionic/svg/document-attach.svg +1 -0
- package/dist/ionic/svg/document-lock-outline.svg +1 -0
- package/dist/ionic/svg/document-lock-sharp.svg +1 -0
- package/dist/ionic/svg/document-lock.svg +1 -0
- package/dist/ionic/svg/document-outline.svg +1 -0
- package/dist/types/components/accordion/accordion.d.ts +18 -1
- package/dist/types/components/checkbox/checkbox.d.ts +9 -2
- package/dist/types/components/datetime/datetime.d.ts +10 -0
- package/dist/types/components/header/header.utils.d.ts +10 -0
- package/dist/types/components/input/input.d.ts +0 -4
- package/dist/types/components/radio-group/radio-group.d.ts +9 -1
- package/dist/types/components/select/select.d.ts +7 -1
- package/dist/types/components/textarea/textarea.d.ts +0 -4
- package/dist/types/components/toggle/toggle.d.ts +7 -2
- package/dist/types/utils/forms/index.d.ts +1 -0
- package/dist/types/utils/forms/validity.d.ts +10 -0
- package/dist/types/utils/transition/index.d.ts +9 -0
- package/hydrate/index.js +687 -413
- package/hydrate/index.mjs +687 -413
- package/package.json +4 -4
- package/dist/ionic/p-1c8a476d.entry.js +0 -4
- package/dist/ionic/p-3355a2ff.entry.js +0 -4
- package/dist/ionic/p-4efea47a.entry.js +0 -4
- package/dist/ionic/p-62e50f80.entry.js +0 -4
- package/dist/ionic/p-785026d7.entry.js +0 -4
- package/dist/ionic/p-78c74a3e.entry.js +0 -4
- package/dist/ionic/p-7bcfc421.entry.js +0 -4
- package/dist/ionic/p-83fc84e7.entry.js +0 -4
- package/dist/ionic/p-913a7c1e.entry.js +0 -4
- package/dist/ionic/p-CMhMiYSX.js +0 -4
- package/dist/ionic/p-c17c0a01.entry.js +0 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { Build, Host, forceUpdate, h, } from "@stencil/core";
|
|
5
|
-
import { createNotchController } from "../../utils/forms/index";
|
|
5
|
+
import { createNotchController, checkInvalidState } from "../../utils/forms/index";
|
|
6
6
|
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
|
|
7
7
|
import { createSlotMutationController } from "../../utils/slot-mutation-controller";
|
|
8
8
|
import { createColorClasses, hostContext } from "../../utils/theme";
|
|
@@ -227,14 +227,6 @@ export class Input {
|
|
|
227
227
|
componentWillLoad() {
|
|
228
228
|
this.inheritedAttributes = Object.assign(Object.assign({}, inheritAriaAttributes(this.el)), inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']));
|
|
229
229
|
}
|
|
230
|
-
/**
|
|
231
|
-
* Checks if the input is in an invalid state based on Ionic validation classes
|
|
232
|
-
*/
|
|
233
|
-
checkInvalidState() {
|
|
234
|
-
const hasIonTouched = this.el.classList.contains('ion-touched');
|
|
235
|
-
const hasIonInvalid = this.el.classList.contains('ion-invalid');
|
|
236
|
-
return hasIonTouched && hasIonInvalid;
|
|
237
|
-
}
|
|
238
230
|
connectedCallback() {
|
|
239
231
|
const { el } = this;
|
|
240
232
|
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
|
|
@@ -242,7 +234,7 @@ export class Input {
|
|
|
242
234
|
// Watch for class changes to update validation state
|
|
243
235
|
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
244
236
|
this.validationObserver = new MutationObserver(() => {
|
|
245
|
-
const newIsInvalid =
|
|
237
|
+
const newIsInvalid = checkInvalidState(el);
|
|
246
238
|
if (this.isInvalid !== newIsInvalid) {
|
|
247
239
|
this.isInvalid = newIsInvalid;
|
|
248
240
|
// Force a re-render to update aria-describedby immediately
|
|
@@ -255,7 +247,7 @@ export class Input {
|
|
|
255
247
|
});
|
|
256
248
|
}
|
|
257
249
|
// Always set initial state
|
|
258
|
-
this.isInvalid =
|
|
250
|
+
this.isInvalid = checkInvalidState(el);
|
|
259
251
|
this.debounceChanged();
|
|
260
252
|
if (Build.isBrowser) {
|
|
261
253
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -519,7 +511,7 @@ export class Input {
|
|
|
519
511
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
520
512
|
*/
|
|
521
513
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
522
|
-
return (h(Host, { key: '
|
|
514
|
+
return (h(Host, { key: '97b5308021064d9e7434ef2d3d96f27045c1b0c4', class: createColorClasses(this.color, {
|
|
523
515
|
[mode]: true,
|
|
524
516
|
'has-value': hasValue,
|
|
525
517
|
'has-focus': hasFocus,
|
|
@@ -530,14 +522,14 @@ export class Input {
|
|
|
530
522
|
'in-item': inItem,
|
|
531
523
|
'in-item-color': hostContext('ion-item.ion-color', this.el),
|
|
532
524
|
'input-disabled': disabled,
|
|
533
|
-
}) }, h("label", { key: '
|
|
525
|
+
}) }, h("label", { key: '353f68726ce180299bd9adc81e5ff7d26a48f54f', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '2034b4bad04fc157f3298a1805819216b6f439d0', class: "native-wrapper", onClick: this.onLabelClick }, h("slot", { key: '96bb5e30176b2bd76dfb75bfbf6c1c3d4403f4bb', name: "start" }), h("input", Object.assign({ key: '1a1d75b0e414a95c89d5a760757c33548d234aca', 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.isInvalid ? 'true' : undefined }, this.inheritedAttributes)), this.clearInput && !readonly && !disabled && (h("button", { key: '95f3df17b7691d9a2e7dcd4a51f16a94aa3ca36f', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
|
|
534
526
|
/**
|
|
535
527
|
* This prevents mobile browsers from
|
|
536
528
|
* blurring the input when the clear
|
|
537
529
|
* button is activated.
|
|
538
530
|
*/
|
|
539
531
|
ev.preventDefault();
|
|
540
|
-
}, onClick: this.clearTextInput }, h("ion-icon", { key: '
|
|
532
|
+
}, onClick: this.clearTextInput }, h("ion-icon", { key: '16b0af75eed50c8115fb5597f73b5fbf71c2530e', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: 'c48da0f8ddb3764ac43efa705bb4a6bb2d9cc2fd', name: "end" })), shouldRenderHighlight && h("div", { key: 'f15238481fc20de56ca7ecb6e350b3c024cc755e', class: "input-highlight" })), this.renderBottomContent()));
|
|
541
533
|
}
|
|
542
534
|
static get is() { return "ion-input"; }
|
|
543
535
|
static get encapsulation() { return "scoped"; }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { Host, h } from "@stencil/core";
|
|
4
|
+
import { Build, Host, h } from "@stencil/core";
|
|
5
|
+
import { checkInvalidState } from "../../utils/forms/index";
|
|
5
6
|
import { renderHiddenInput } from "../../utils/helpers";
|
|
6
7
|
import { getIonMode } from "../../global/ionic-global";
|
|
7
8
|
export class RadioGroup {
|
|
@@ -10,6 +11,10 @@ export class RadioGroup {
|
|
|
10
11
|
this.helperTextId = `${this.inputId}-helper-text`;
|
|
11
12
|
this.errorTextId = `${this.inputId}-error-text`;
|
|
12
13
|
this.labelId = `${this.inputId}-lbl`;
|
|
14
|
+
/**
|
|
15
|
+
* Track validation state for proper aria-live announcements.
|
|
16
|
+
*/
|
|
17
|
+
this.isInvalid = false;
|
|
13
18
|
/**
|
|
14
19
|
* If `true`, the radios can be deselected.
|
|
15
20
|
*/
|
|
@@ -91,6 +96,52 @@ export class RadioGroup {
|
|
|
91
96
|
this.labelId = label.id = this.name + '-lbl';
|
|
92
97
|
}
|
|
93
98
|
}
|
|
99
|
+
// Watch for class changes to update validation state.
|
|
100
|
+
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
101
|
+
this.validationObserver = new MutationObserver(() => {
|
|
102
|
+
const newIsInvalid = checkInvalidState(this.el);
|
|
103
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
104
|
+
this.isInvalid = newIsInvalid;
|
|
105
|
+
/**
|
|
106
|
+
* Screen readers tend to announce changes
|
|
107
|
+
* to `aria-describedby` when the attribute
|
|
108
|
+
* is changed during a blur event for a
|
|
109
|
+
* native form control.
|
|
110
|
+
* However, the announcement can be spotty
|
|
111
|
+
* when using a non-native form control
|
|
112
|
+
* and `forceUpdate()`.
|
|
113
|
+
* This is due to `forceUpdate()` internally
|
|
114
|
+
* rescheduling the DOM update to a lower
|
|
115
|
+
* priority queue regardless if it's called
|
|
116
|
+
* inside a Promise or not, thus causing
|
|
117
|
+
* the screen reader to potentially miss the
|
|
118
|
+
* change.
|
|
119
|
+
* By using a State variable inside a Promise,
|
|
120
|
+
* it guarantees a re-render immediately at
|
|
121
|
+
* a higher priority.
|
|
122
|
+
*/
|
|
123
|
+
Promise.resolve().then(() => {
|
|
124
|
+
this.hintTextId = this.getHintTextId();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
this.validationObserver.observe(this.el, {
|
|
129
|
+
attributes: true,
|
|
130
|
+
attributeFilter: ['class'],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Always set initial state
|
|
134
|
+
this.isInvalid = checkInvalidState(this.el);
|
|
135
|
+
}
|
|
136
|
+
componentWillLoad() {
|
|
137
|
+
this.hintTextId = this.getHintTextId();
|
|
138
|
+
}
|
|
139
|
+
disconnectedCallback() {
|
|
140
|
+
// Clean up validation observer to prevent memory leaks.
|
|
141
|
+
if (this.validationObserver) {
|
|
142
|
+
this.validationObserver.disconnect();
|
|
143
|
+
this.validationObserver = undefined;
|
|
144
|
+
}
|
|
94
145
|
}
|
|
95
146
|
getRadios() {
|
|
96
147
|
return Array.from(this.el.querySelectorAll('ion-radio'));
|
|
@@ -166,16 +217,16 @@ export class RadioGroup {
|
|
|
166
217
|
* Renders the helper text or error text values
|
|
167
218
|
*/
|
|
168
219
|
renderHintText() {
|
|
169
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
220
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
170
221
|
const hasHintText = !!helperText || !!errorText;
|
|
171
222
|
if (!hasHintText) {
|
|
172
223
|
return;
|
|
173
224
|
}
|
|
174
|
-
return (h("div", { class: "radio-group-top" }, h("div", { id: helperTextId, class: "helper-text" }, helperText), h("div", { id: errorTextId, class: "error-text" }, errorText)));
|
|
225
|
+
return (h("div", { class: "radio-group-top" }, h("div", { id: helperTextId, class: "helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), h("div", { id: errorTextId, class: "error-text", role: "alert" }, isInvalid ? errorText : null)));
|
|
175
226
|
}
|
|
176
|
-
|
|
177
|
-
const {
|
|
178
|
-
if (
|
|
227
|
+
getHintTextId() {
|
|
228
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
229
|
+
if (isInvalid && errorText) {
|
|
179
230
|
return errorTextId;
|
|
180
231
|
}
|
|
181
232
|
if (helperText) {
|
|
@@ -187,7 +238,7 @@ export class RadioGroup {
|
|
|
187
238
|
const { label, labelId, el, name, value } = this;
|
|
188
239
|
const mode = getIonMode(this);
|
|
189
240
|
renderHiddenInput(true, el, name, value, false);
|
|
190
|
-
return (h(Host, { key: '
|
|
241
|
+
return (h(Host, { key: 'db593b3ed511e9395e3c7bfd91b787328692cd6d', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), h("div", { key: '85045b45a0100a45f3b9a35d1c5a25ec63d525c4', class: "radio-group-wrapper" }, h("slot", { key: '53dacb87ce62398e78771fb2efaf839ab922d946' }))));
|
|
191
242
|
}
|
|
192
243
|
static get is() { return "ion-radio-group"; }
|
|
193
244
|
static get originalStyleUrls() {
|
|
@@ -328,6 +379,12 @@ export class RadioGroup {
|
|
|
328
379
|
}
|
|
329
380
|
};
|
|
330
381
|
}
|
|
382
|
+
static get states() {
|
|
383
|
+
return {
|
|
384
|
+
"isInvalid": {},
|
|
385
|
+
"hintTextId": {}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
331
388
|
static get events() {
|
|
332
389
|
return [{
|
|
333
390
|
"method": "ionChange",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { Host, h, forceUpdate } from "@stencil/core";
|
|
5
|
-
import { compareOptions, createNotchController, isOptionSelected } from "../../utils/forms/index";
|
|
4
|
+
import { Build, Host, h, forceUpdate } from "@stencil/core";
|
|
5
|
+
import { compareOptions, createNotchController, isOptionSelected, checkInvalidState } from "../../utils/forms/index";
|
|
6
6
|
import { focusVisibleElement, renderHiddenInput, inheritAttributes } from "../../utils/helpers";
|
|
7
7
|
import { printIonWarning } from "../../utils/logging/index";
|
|
8
8
|
import { actionSheetController, alertController, popoverController, modalController } from "../../utils/overlays";
|
|
@@ -44,6 +44,10 @@ export class Select {
|
|
|
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
|
* The text to display on the cancel button.
|
|
49
53
|
*/
|
|
@@ -173,9 +177,46 @@ export class Select {
|
|
|
173
177
|
*/
|
|
174
178
|
forceUpdate(this);
|
|
175
179
|
});
|
|
180
|
+
// Watch for class changes to update validation state.
|
|
181
|
+
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
182
|
+
this.validationObserver = new MutationObserver(() => {
|
|
183
|
+
const newIsInvalid = checkInvalidState(this.el);
|
|
184
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
185
|
+
this.isInvalid = newIsInvalid;
|
|
186
|
+
/**
|
|
187
|
+
* Screen readers tend to announce changes
|
|
188
|
+
* to `aria-describedby` when the attribute
|
|
189
|
+
* is changed during a blur event for a
|
|
190
|
+
* native form control.
|
|
191
|
+
* However, the announcement can be spotty
|
|
192
|
+
* when using a non-native form control
|
|
193
|
+
* and `forceUpdate()`.
|
|
194
|
+
* This is due to `forceUpdate()` internally
|
|
195
|
+
* rescheduling the DOM update to a lower
|
|
196
|
+
* priority queue regardless if it's called
|
|
197
|
+
* inside a Promise or not, thus causing
|
|
198
|
+
* the screen reader to potentially miss the
|
|
199
|
+
* change.
|
|
200
|
+
* By using a State variable inside a Promise,
|
|
201
|
+
* it guarantees a re-render immediately at
|
|
202
|
+
* a higher priority.
|
|
203
|
+
*/
|
|
204
|
+
Promise.resolve().then(() => {
|
|
205
|
+
this.hintTextId = this.getHintTextId();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
this.validationObserver.observe(el, {
|
|
210
|
+
attributes: true,
|
|
211
|
+
attributeFilter: ['class'],
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Always set initial state
|
|
215
|
+
this.isInvalid = checkInvalidState(this.el);
|
|
176
216
|
}
|
|
177
217
|
componentWillLoad() {
|
|
178
218
|
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
|
219
|
+
this.hintTextId = this.getHintTextId();
|
|
179
220
|
}
|
|
180
221
|
componentDidLoad() {
|
|
181
222
|
/**
|
|
@@ -199,6 +240,11 @@ export class Select {
|
|
|
199
240
|
this.notchController.destroy();
|
|
200
241
|
this.notchController = undefined;
|
|
201
242
|
}
|
|
243
|
+
// Clean up validation observer to prevent memory leaks.
|
|
244
|
+
if (this.validationObserver) {
|
|
245
|
+
this.validationObserver.disconnect();
|
|
246
|
+
this.validationObserver = undefined;
|
|
247
|
+
}
|
|
202
248
|
}
|
|
203
249
|
/**
|
|
204
250
|
* Open the select overlay. The overlay is either an alert, action sheet, or popover,
|
|
@@ -715,11 +761,11 @@ export class Select {
|
|
|
715
761
|
}
|
|
716
762
|
renderListbox() {
|
|
717
763
|
const { disabled, inputId, isExpanded, required } = this;
|
|
718
|
-
return (h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.
|
|
764
|
+
return (h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-required": `${required}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl) => (this.focusEl = focusEl) }));
|
|
719
765
|
}
|
|
720
|
-
|
|
721
|
-
const {
|
|
722
|
-
if (
|
|
766
|
+
getHintTextId() {
|
|
767
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
768
|
+
if (isInvalid && errorText) {
|
|
723
769
|
return errorTextId;
|
|
724
770
|
}
|
|
725
771
|
if (helperText) {
|
|
@@ -731,10 +777,10 @@ export class Select {
|
|
|
731
777
|
* Renders the helper text or error text values
|
|
732
778
|
*/
|
|
733
779
|
renderHintText() {
|
|
734
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
780
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
735
781
|
return [
|
|
736
|
-
h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText),
|
|
737
|
-
h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText),
|
|
782
|
+
h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null),
|
|
783
|
+
h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null),
|
|
738
784
|
];
|
|
739
785
|
}
|
|
740
786
|
/**
|
|
@@ -782,7 +828,7 @@ export class Select {
|
|
|
782
828
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
783
829
|
*/
|
|
784
830
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
|
|
785
|
-
return (h(Host, { key: '
|
|
831
|
+
return (h(Host, { key: '35b5e18e6f79a802ff2d46d1242e80ff755cc0b9', onClick: this.onClick, class: createColorClasses(this.color, {
|
|
786
832
|
[mode]: true,
|
|
787
833
|
'in-item': inItem,
|
|
788
834
|
'in-item-color': hostContext('ion-item.ion-color', el),
|
|
@@ -800,7 +846,7 @@ export class Select {
|
|
|
800
846
|
[`select-justify-${justify}`]: justifyEnabled,
|
|
801
847
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
802
848
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
803
|
-
}) }, h("label", { key: '
|
|
849
|
+
}) }, h("label", { key: '6005b34a0c50bc4d7653a4276bc232ecd02e083c', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'c7e07aa81ae856c057f16275dd058f37c5670a47', class: "select-wrapper-inner" }, h("slot", { key: '7fc2deefe0424404caacdbbd9e08ed43ba55d28a', name: "start" }), h("div", { key: '157d74ee717b1bc30b5f1c233a09b0c8456aa68e', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: 'ea66db304528b82bf9317730b6dce3db2612f235', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '786eb1530b7476f0615d4e7c0bf4e7e4dc66509c', class: "select-highlight" })), this.renderBottomContent()));
|
|
804
850
|
}
|
|
805
851
|
static get is() { return "ion-select"; }
|
|
806
852
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1268,7 +1314,9 @@ export class Select {
|
|
|
1268
1314
|
static get states() {
|
|
1269
1315
|
return {
|
|
1270
1316
|
"isExpanded": {},
|
|
1271
|
-
"hasFocus": {}
|
|
1317
|
+
"hasFocus": {},
|
|
1318
|
+
"isInvalid": {},
|
|
1319
|
+
"hintTextId": {}
|
|
1272
1320
|
};
|
|
1273
1321
|
}
|
|
1274
1322
|
static get events() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { Build, Host, forceUpdate, h, writeTask, } from "@stencil/core";
|
|
5
|
-
import { createNotchController } from "../../utils/forms/index";
|
|
5
|
+
import { createNotchController, checkInvalidState } from "../../utils/forms/index";
|
|
6
6
|
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
|
|
7
7
|
import { createSlotMutationController } from "../../utils/slot-mutation-controller";
|
|
8
8
|
import { createColorClasses, hostContext } from "../../utils/theme";
|
|
@@ -187,14 +187,6 @@ export class Textarea {
|
|
|
187
187
|
this.el.click();
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Checks if the textarea is in an invalid state based on Ionic validation classes
|
|
192
|
-
*/
|
|
193
|
-
checkValidationState() {
|
|
194
|
-
const hasIonTouched = this.el.classList.contains('ion-touched');
|
|
195
|
-
const hasIonInvalid = this.el.classList.contains('ion-invalid');
|
|
196
|
-
return hasIonTouched && hasIonInvalid;
|
|
197
|
-
}
|
|
198
190
|
connectedCallback() {
|
|
199
191
|
const { el } = this;
|
|
200
192
|
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
|
|
@@ -202,7 +194,7 @@ export class Textarea {
|
|
|
202
194
|
// Watch for class changes to update validation state
|
|
203
195
|
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
204
196
|
this.validationObserver = new MutationObserver(() => {
|
|
205
|
-
const newIsInvalid = this.
|
|
197
|
+
const newIsInvalid = checkInvalidState(this.el);
|
|
206
198
|
if (this.isInvalid !== newIsInvalid) {
|
|
207
199
|
this.isInvalid = newIsInvalid;
|
|
208
200
|
// Force a re-render to update aria-describedby immediately
|
|
@@ -215,7 +207,7 @@ export class Textarea {
|
|
|
215
207
|
});
|
|
216
208
|
}
|
|
217
209
|
// Always set initial state
|
|
218
|
-
this.isInvalid = this.
|
|
210
|
+
this.isInvalid = checkInvalidState(this.el);
|
|
219
211
|
this.debounceChanged();
|
|
220
212
|
if (Build.isBrowser) {
|
|
221
213
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -479,7 +471,7 @@ export class Textarea {
|
|
|
479
471
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
480
472
|
*/
|
|
481
473
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
482
|
-
return (h(Host, { key: '
|
|
474
|
+
return (h(Host, { key: 'a70a62d7aae3831a50acd74f60b930925ada1326', class: createColorClasses(this.color, {
|
|
483
475
|
[mode]: true,
|
|
484
476
|
'has-value': hasValue,
|
|
485
477
|
'has-focus': hasFocus,
|
|
@@ -488,7 +480,7 @@ export class Textarea {
|
|
|
488
480
|
[`textarea-shape-${shape}`]: shape !== undefined,
|
|
489
481
|
[`textarea-label-placement-${labelPlacement}`]: true,
|
|
490
482
|
'textarea-disabled': disabled,
|
|
491
|
-
}) }, h("label", { key: '
|
|
483
|
+
}) }, h("label", { key: '8a2dd59a60f7469df84018eb0ede3a9ec3862703', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '1bfc368236e3da7a225a45118c27fbfc1fe5fa46', class: "textarea-wrapper-inner" }, h("div", { key: '215cbb2635ff52e31a8973376989b85e7245d40f', class: "start-slot-wrapper" }, h("slot", { key: '9f6b461cdee9d629deb695d2bea054ece2f32305', name: "start" })), h("div", { key: 'c1af35a2d5bc452bebe0b22a26d15ff52b4e9fc8', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: '69a69b3cf0932baafbe37e6e846f1a571608d3f2', 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.isInvalid ? 'true' : undefined }, this.inheritedAttributes), value)), h("div", { key: 'c053ea8b865d0e29763aed2e4939cc9c9e374c15', class: "end-slot-wrapper" }, h("slot", { key: '930aa641833b0df54b9ea10368fc2f46d5f491f6', name: "end" }))), shouldRenderHighlight && h("div", { key: '8d12597d15f5f429d80e8272ea99e64ed924e482', class: "textarea-highlight" })), this.renderBottomContent()));
|
|
492
484
|
}
|
|
493
485
|
static get is() { return "ion-textarea"; }
|
|
494
486
|
static get encapsulation() { return "scoped"; }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { Host, h } from "@stencil/core";
|
|
4
|
+
import { Build, Host, h } from "@stencil/core";
|
|
5
|
+
import { checkInvalidState } from "../../utils/forms/index";
|
|
5
6
|
import { renderHiddenInput, inheritAriaAttributes } from "../../utils/helpers";
|
|
6
7
|
import { hapticSelection } from "../../utils/native/haptic";
|
|
7
8
|
import { isPlatform } from "../../utils/platform";
|
|
@@ -32,6 +33,10 @@ export class Toggle {
|
|
|
32
33
|
this.inheritedAttributes = {};
|
|
33
34
|
this.didLoad = false;
|
|
34
35
|
this.activated = false;
|
|
36
|
+
/**
|
|
37
|
+
* Track validation state for proper aria-live announcements.
|
|
38
|
+
*/
|
|
39
|
+
this.isInvalid = false;
|
|
35
40
|
/**
|
|
36
41
|
* The name of the control, which is submitted with the form data.
|
|
37
42
|
*/
|
|
@@ -139,22 +144,58 @@ export class Toggle {
|
|
|
139
144
|
const { checked, value } = this;
|
|
140
145
|
const isNowChecked = !checked;
|
|
141
146
|
this.checked = isNowChecked;
|
|
142
|
-
this.setFocus();
|
|
143
147
|
this.ionChange.emit({
|
|
144
148
|
checked: isNowChecked,
|
|
145
149
|
value,
|
|
146
150
|
});
|
|
147
151
|
}
|
|
148
152
|
async connectedCallback() {
|
|
153
|
+
const { didLoad, el } = this;
|
|
149
154
|
/**
|
|
150
155
|
* If we have not yet rendered
|
|
151
156
|
* ion-toggle, then toggleTrack is not defined.
|
|
152
157
|
* But if we are moving ion-toggle via appendChild,
|
|
153
158
|
* then toggleTrack will be defined.
|
|
154
159
|
*/
|
|
155
|
-
if (
|
|
160
|
+
if (didLoad) {
|
|
156
161
|
this.setupGesture();
|
|
157
162
|
}
|
|
163
|
+
// Watch for class changes to update validation state.
|
|
164
|
+
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
165
|
+
this.validationObserver = new MutationObserver(() => {
|
|
166
|
+
const newIsInvalid = checkInvalidState(el);
|
|
167
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
168
|
+
this.isInvalid = newIsInvalid;
|
|
169
|
+
/**
|
|
170
|
+
* Screen readers tend to announce changes
|
|
171
|
+
* to `aria-describedby` when the attribute
|
|
172
|
+
* is changed during a blur event for a
|
|
173
|
+
* native form control.
|
|
174
|
+
* However, the announcement can be spotty
|
|
175
|
+
* when using a non-native form control
|
|
176
|
+
* and `forceUpdate()`.
|
|
177
|
+
* This is due to `forceUpdate()` internally
|
|
178
|
+
* rescheduling the DOM update to a lower
|
|
179
|
+
* priority queue regardless if it's called
|
|
180
|
+
* inside a Promise or not, thus causing
|
|
181
|
+
* the screen reader to potentially miss the
|
|
182
|
+
* change.
|
|
183
|
+
* By using a State variable inside a Promise,
|
|
184
|
+
* it guarantees a re-render immediately at
|
|
185
|
+
* a higher priority.
|
|
186
|
+
*/
|
|
187
|
+
Promise.resolve().then(() => {
|
|
188
|
+
this.hintTextId = this.getHintTextId();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
this.validationObserver.observe(el, {
|
|
193
|
+
attributes: true,
|
|
194
|
+
attributeFilter: ['class'],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// Always set initial state
|
|
198
|
+
this.isInvalid = checkInvalidState(el);
|
|
158
199
|
}
|
|
159
200
|
componentDidLoad() {
|
|
160
201
|
this.setupGesture();
|
|
@@ -165,9 +206,15 @@ export class Toggle {
|
|
|
165
206
|
this.gesture.destroy();
|
|
166
207
|
this.gesture = undefined;
|
|
167
208
|
}
|
|
209
|
+
// Clean up validation observer to prevent memory leaks.
|
|
210
|
+
if (this.validationObserver) {
|
|
211
|
+
this.validationObserver.disconnect();
|
|
212
|
+
this.validationObserver = undefined;
|
|
213
|
+
}
|
|
168
214
|
}
|
|
169
215
|
componentWillLoad() {
|
|
170
216
|
this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
|
|
217
|
+
this.hintTextId = this.getHintTextId();
|
|
171
218
|
}
|
|
172
219
|
onStart() {
|
|
173
220
|
this.activated = true;
|
|
@@ -190,9 +237,7 @@ export class Toggle {
|
|
|
190
237
|
return this.value || '';
|
|
191
238
|
}
|
|
192
239
|
setFocus() {
|
|
193
|
-
|
|
194
|
-
this.focusEl.focus();
|
|
195
|
-
}
|
|
240
|
+
this.el.focus();
|
|
196
241
|
}
|
|
197
242
|
renderOnOffSwitchLabels(mode, checked) {
|
|
198
243
|
const icon = this.getSwitchLabelIcon(mode, checked);
|
|
@@ -210,9 +255,9 @@ export class Toggle {
|
|
|
210
255
|
get hasLabel() {
|
|
211
256
|
return this.el.textContent !== '';
|
|
212
257
|
}
|
|
213
|
-
|
|
214
|
-
const {
|
|
215
|
-
if (
|
|
258
|
+
getHintTextId() {
|
|
259
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
260
|
+
if (isInvalid && errorText) {
|
|
216
261
|
return errorTextId;
|
|
217
262
|
}
|
|
218
263
|
if (helperText) {
|
|
@@ -225,7 +270,7 @@ export class Toggle {
|
|
|
225
270
|
* This element should only be rendered if hint text is set.
|
|
226
271
|
*/
|
|
227
272
|
renderHintText() {
|
|
228
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
273
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
229
274
|
/**
|
|
230
275
|
* undefined and empty string values should
|
|
231
276
|
* be treated as not having helper/error text.
|
|
@@ -234,15 +279,15 @@ export class Toggle {
|
|
|
234
279
|
if (!hasHintText) {
|
|
235
280
|
return;
|
|
236
281
|
}
|
|
237
|
-
return (h("div", { class: "toggle-bottom" }, h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text" }, helperText), h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text" }, errorText)));
|
|
282
|
+
return (h("div", { class: "toggle-bottom" }, h("div", { id: helperTextId, class: "helper-text", part: "supporting-text helper-text", "aria-live": "polite" }, !isInvalid ? helperText : null), h("div", { id: errorTextId, class: "error-text", part: "supporting-text error-text", role: "alert" }, isInvalid ? errorText : null)));
|
|
238
283
|
}
|
|
239
284
|
render() {
|
|
240
|
-
const { activated, alignment, checked, color, disabled, el,
|
|
285
|
+
const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
|
|
241
286
|
const mode = getIonMode(this);
|
|
242
287
|
const value = this.getValue();
|
|
243
288
|
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
|
244
289
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
245
|
-
return (h(Host, { key: '
|
|
290
|
+
return (h(Host, { key: 'f569148edd89ee041a4719ffc4733c16b05229bd', role: "switch", "aria-checked": `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, "aria-labelledby": hasLabel ? inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, "aria-required": required ? 'true' : undefined, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, class: createColorClasses(color, {
|
|
246
291
|
[mode]: true,
|
|
247
292
|
'in-item': hostContext('ion-item', el),
|
|
248
293
|
'toggle-activated': activated,
|
|
@@ -252,10 +297,10 @@ export class Toggle {
|
|
|
252
297
|
[`toggle-alignment-${alignment}`]: alignment !== undefined,
|
|
253
298
|
[`toggle-label-placement-${labelPlacement}`]: true,
|
|
254
299
|
[`toggle-${rtl}`]: true,
|
|
255
|
-
}) }, h("label", { key: '
|
|
300
|
+
}) }, h("label", { key: '3027f2ac4be6de422a14486d847fbee77f615db1', class: "toggle-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: '4b0304c9e879e432b80184b4e5de37d55c11b436', type: "checkbox", role: "switch", "aria-checked": `${checked}`, checked: checked, disabled: disabled, id: inputId, required: required }, inheritedAttributes)), h("div", { key: '8ef265ec942e7f01ff31cbb202ed146c6bf94e02', class: {
|
|
256
301
|
'label-text-wrapper': true,
|
|
257
302
|
'label-text-wrapper-hidden': !hasLabel,
|
|
258
|
-
}, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '
|
|
303
|
+
}, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), h("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
|
|
259
304
|
}
|
|
260
305
|
static get is() { return "ion-toggle"; }
|
|
261
306
|
static get encapsulation() { return "shadow"; }
|
|
@@ -518,7 +563,9 @@ export class Toggle {
|
|
|
518
563
|
}
|
|
519
564
|
static get states() {
|
|
520
565
|
return {
|
|
521
|
-
"activated": {}
|
|
566
|
+
"activated": {},
|
|
567
|
+
"isInvalid": {},
|
|
568
|
+
"hintTextId": {}
|
|
522
569
|
};
|
|
523
570
|
}
|
|
524
571
|
static get events() {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the form element is in an invalid state based on
|
|
6
|
+
* Ionic validation classes.
|
|
7
|
+
*
|
|
8
|
+
* @param el The form element to check.
|
|
9
|
+
* @returns `true` if the element is invalid, `false` otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export const checkInvalidState = (el) => {
|
|
12
|
+
const hasIonTouched = el.classList.contains('ion-touched');
|
|
13
|
+
const hasIonInvalid = el.classList.contains('ion-invalid');
|
|
14
|
+
return hasIonTouched && hasIonInvalid;
|
|
15
|
+
};
|
|
@@ -95,6 +95,13 @@ export const setContent = async (page, html, testInfo, options) => {
|
|
|
95
95
|
route.continue();
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
|
+
/**
|
|
99
|
+
* URL query parameters cause the custom Playwright `page.route`
|
|
100
|
+
* interceptor to fail, which is necessary to inject the test HTML.
|
|
101
|
+
*
|
|
102
|
+
* To avoid this, the final navigation URL is kept simple by using
|
|
103
|
+
* hash params to ensure the route interceptor always works.
|
|
104
|
+
*/
|
|
98
105
|
await page.goto(`${baseUrl}#`, options);
|
|
99
106
|
}
|
|
100
107
|
};
|
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { addE2EListener, EventSpy } from "../event-spy";
|
|
5
5
|
export const spyOnEvent = async (page, eventName) => {
|
|
6
|
+
/**
|
|
7
|
+
* Tabbing out of the page boundary can lead to unreliable `ionBlur events,
|
|
8
|
+
* particularly in Firefox.
|
|
9
|
+
*
|
|
10
|
+
* This occurs because Playwright may incorrectly maintain focus state on the
|
|
11
|
+
* last element, even after a Tab press attempts to shift focus outside the
|
|
12
|
+
* viewport. To reliably trigger the necessary blur event, add a visually
|
|
13
|
+
* hidden, focusable element at the end of the page to receive focus instead of
|
|
14
|
+
* the browser.
|
|
15
|
+
*
|
|
16
|
+
* Playwright issue reference:
|
|
17
|
+
* https://github.com/microsoft/playwright/issues/32269
|
|
18
|
+
*/
|
|
19
|
+
if (eventName === 'ionBlur') {
|
|
20
|
+
const hiddenInput = await page.$('#hidden-input-for-ion-blur');
|
|
21
|
+
if (!hiddenInput) {
|
|
22
|
+
await page.evaluate(() => {
|
|
23
|
+
const input = document.createElement('input');
|
|
24
|
+
input.id = 'hidden-input-for-ion-blur';
|
|
25
|
+
input.style.position = 'absolute';
|
|
26
|
+
input.style.opacity = '0';
|
|
27
|
+
input.style.height = '0';
|
|
28
|
+
input.style.width = '0';
|
|
29
|
+
input.style.pointerEvents = 'none';
|
|
30
|
+
document.body.appendChild(input);
|
|
31
|
+
// Clean up the element when the page is unloaded.
|
|
32
|
+
window.addEventListener('unload', () => {
|
|
33
|
+
input.remove();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
6
38
|
const spy = new EventSpy(eventName);
|
|
7
39
|
const handle = await page.evaluateHandle(() => window);
|
|
8
40
|
await addE2EListener(page, handle, eventName, (ev) => spy.push(ev));
|