voyager-ionic-core 8.7.9 → 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/checkbox.js +63 -9
- package/components/ion-datetime.js +35 -2
- package/components/ion-input.js +2 -1
- package/components/ion-select.js +7 -6
- package/components/ion-textarea.js +2 -1
- package/components/ion-toggle.js +62 -12
- package/components/notch-controller.js +153 -0
- package/components/radio-group.js +60 -7
- package/components/validity.js +1 -150
- package/dist/cjs/ion-checkbox.cjs.entry.js +60 -8
- package/dist/cjs/ion-datetime_3.cjs.entry.js +35 -2
- package/dist/cjs/ion-input.cjs.entry.js +3 -2
- package/dist/cjs/ion-radio_2.cjs.entry.js +57 -6
- package/dist/cjs/ion-select_3.cjs.entry.js +7 -6
- package/dist/cjs/ion-textarea.cjs.entry.js +3 -2
- package/dist/cjs/ion-toggle.cjs.entry.js +58 -10
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{validity-C8QoAYT2.js → notch-controller-Bzqhjm4f.js} +0 -14
- package/dist/cjs/validity-BpS37YFM.js +19 -0
- package/dist/collection/components/checkbox/checkbox.js +67 -9
- package/dist/collection/components/datetime/datetime.js +35 -2
- package/dist/collection/components/radio-group/radio-group.js +64 -7
- package/dist/collection/components/select/select.js +5 -5
- package/dist/collection/components/toggle/toggle.js +62 -12
- package/dist/collection/utils/test/playwright/page/utils/set-content.js +7 -0
- package/dist/docs.json +1 -1
- package/dist/esm/ion-checkbox.entry.js +60 -8
- package/dist/esm/ion-datetime_3.entry.js +35 -2
- package/dist/esm/ion-input.entry.js +2 -1
- package/dist/esm/ion-radio_2.entry.js +57 -6
- package/dist/esm/ion-select_3.entry.js +6 -5
- package/dist/esm/ion-textarea.entry.js +2 -1
- package/dist/esm/ion-toggle.entry.js +58 -10
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{validity-B8oWougr.js → notch-controller-BwelN_JM.js} +1 -14
- package/dist/esm/validity-DJztqcrH.js +17 -0
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-40c261a3.entry.js +4 -0
- package/dist/ionic/p-4e41ea20.entry.js +4 -0
- package/dist/ionic/p-7380261c.entry.js +4 -0
- package/dist/ionic/{p-DieJyvMP.js → p-DCv9sLH2.js} +1 -1
- package/dist/ionic/p-DJztqcrH.js +4 -0
- package/dist/ionic/p-c19f63d0.entry.js +4 -0
- package/dist/ionic/p-cb93126d.entry.js +4 -0
- package/dist/ionic/p-d1f54e28.entry.js +4 -0
- package/dist/ionic/p-d3014190.entry.js +4 -0
- package/dist/types/components/checkbox/checkbox.d.ts +9 -1
- package/dist/types/components/datetime/datetime.d.ts +10 -0
- package/dist/types/components/radio-group/radio-group.d.ts +9 -1
- package/dist/types/components/select/select.d.ts +2 -2
- package/dist/types/components/toggle/toggle.d.ts +7 -1
- package/dist/types/utils/forms/validity.d.ts +1 -1
- package/hydrate/index.js +312 -227
- package/hydrate/index.mjs +312 -227
- package/package.json +2 -2
- package/dist/ionic/p-4cc26913.entry.js +0 -4
- package/dist/ionic/p-4efea47a.entry.js +0 -4
- package/dist/ionic/p-7bcfc421.entry.js +0 -4
- package/dist/ionic/p-8bdfc8f6.entry.js +0 -4
- package/dist/ionic/p-dc2e126d.entry.js +0 -4
- package/dist/ionic/p-f65f9308.entry.js +0 -4
- package/dist/ionic/p-fc278823.entry.js +0 -4
|
@@ -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 { inheritAriaAttributes, renderHiddenInput } from "../../utils/helpers";
|
|
6
7
|
import { createColorClasses, hostContext } from "../../utils/theme";
|
|
7
8
|
import { getIonMode } from "../../global/ionic-global";
|
|
@@ -62,6 +63,10 @@ export class Checkbox {
|
|
|
62
63
|
* submitting if the value is invalid.
|
|
63
64
|
*/
|
|
64
65
|
this.required = false;
|
|
66
|
+
/**
|
|
67
|
+
* Track validation state for proper aria-live announcements.
|
|
68
|
+
*/
|
|
69
|
+
this.isInvalid = false;
|
|
65
70
|
/**
|
|
66
71
|
* Sets the checked property and emits
|
|
67
72
|
* the ionChange event. Use this to update the
|
|
@@ -108,16 +113,63 @@ export class Checkbox {
|
|
|
108
113
|
ev.stopPropagation();
|
|
109
114
|
};
|
|
110
115
|
}
|
|
116
|
+
connectedCallback() {
|
|
117
|
+
const { el } = this;
|
|
118
|
+
// Watch for class changes to update validation state.
|
|
119
|
+
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
120
|
+
this.validationObserver = new MutationObserver(() => {
|
|
121
|
+
const newIsInvalid = checkInvalidState(el);
|
|
122
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
123
|
+
this.isInvalid = newIsInvalid;
|
|
124
|
+
/**
|
|
125
|
+
* Screen readers tend to announce changes
|
|
126
|
+
* to `aria-describedby` when the attribute
|
|
127
|
+
* is changed during a blur event for a
|
|
128
|
+
* native form control.
|
|
129
|
+
* However, the announcement can be spotty
|
|
130
|
+
* when using a non-native form control
|
|
131
|
+
* and `forceUpdate()`.
|
|
132
|
+
* This is due to `forceUpdate()` internally
|
|
133
|
+
* rescheduling the DOM update to a lower
|
|
134
|
+
* priority queue regardless if it's called
|
|
135
|
+
* inside a Promise or not, thus causing
|
|
136
|
+
* the screen reader to potentially miss the
|
|
137
|
+
* change.
|
|
138
|
+
* By using a State variable inside a Promise,
|
|
139
|
+
* it guarantees a re-render immediately at
|
|
140
|
+
* a higher priority.
|
|
141
|
+
*/
|
|
142
|
+
Promise.resolve().then(() => {
|
|
143
|
+
this.hintTextId = this.getHintTextId();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
this.validationObserver.observe(el, {
|
|
148
|
+
attributes: true,
|
|
149
|
+
attributeFilter: ['class'],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Always set initial state
|
|
153
|
+
this.isInvalid = checkInvalidState(el);
|
|
154
|
+
}
|
|
111
155
|
componentWillLoad() {
|
|
112
156
|
this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
|
|
157
|
+
this.hintTextId = this.getHintTextId();
|
|
158
|
+
}
|
|
159
|
+
disconnectedCallback() {
|
|
160
|
+
// Clean up validation observer to prevent memory leaks.
|
|
161
|
+
if (this.validationObserver) {
|
|
162
|
+
this.validationObserver.disconnect();
|
|
163
|
+
this.validationObserver = undefined;
|
|
164
|
+
}
|
|
113
165
|
}
|
|
114
166
|
/** @internal */
|
|
115
167
|
async setFocus() {
|
|
116
168
|
this.el.focus();
|
|
117
169
|
}
|
|
118
|
-
|
|
119
|
-
const {
|
|
120
|
-
if (
|
|
170
|
+
getHintTextId() {
|
|
171
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
172
|
+
if (isInvalid && errorText) {
|
|
121
173
|
return errorTextId;
|
|
122
174
|
}
|
|
123
175
|
if (helperText) {
|
|
@@ -130,7 +182,7 @@ export class Checkbox {
|
|
|
130
182
|
* This element should only be rendered if hint text is set.
|
|
131
183
|
*/
|
|
132
184
|
renderHintText() {
|
|
133
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
185
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
134
186
|
/**
|
|
135
187
|
* undefined and empty string values should
|
|
136
188
|
* be treated as not having helper/error text.
|
|
@@ -139,7 +191,7 @@ export class Checkbox {
|
|
|
139
191
|
if (!hasHintText) {
|
|
140
192
|
return;
|
|
141
193
|
}
|
|
142
|
-
return (h("div", { class: "checkbox-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)));
|
|
194
|
+
return (h("div", { class: "checkbox-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)));
|
|
143
195
|
}
|
|
144
196
|
render() {
|
|
145
197
|
const { color, checked, disabled, el, getSVGPath, indeterminate, inheritedAttributes, inputId, justify, labelPlacement, name, value, alignment, required, } = this;
|
|
@@ -149,7 +201,7 @@ export class Checkbox {
|
|
|
149
201
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
150
202
|
// The host element must have a checkbox role to ensure proper VoiceOver
|
|
151
203
|
// support in Safari for accessibility.
|
|
152
|
-
return (h(Host, { key: '
|
|
204
|
+
return (h(Host, { key: 'ae0fbd4b21accbac132e6b85c513512ad9179394', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": hasLabelContent ? this.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, onClick: this.onClick, class: createColorClasses(color, {
|
|
153
205
|
[mode]: true,
|
|
154
206
|
'in-item': hostContext('ion-item', el),
|
|
155
207
|
'checkbox-checked': checked,
|
|
@@ -159,10 +211,10 @@ export class Checkbox {
|
|
|
159
211
|
[`checkbox-justify-${justify}`]: justify !== undefined,
|
|
160
212
|
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
|
|
161
213
|
[`checkbox-label-placement-${labelPlacement}`]: true,
|
|
162
|
-
}) }, h("label", { key: '
|
|
214
|
+
}) }, h("label", { key: '7a3d7f3c27dde514f2dbf2e34f4629fad33ec3bf', class: "checkbox-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: '4130d77ddf034271fecccda14e101a5a809921b6', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), h("div", { key: '5daa74f4e62b0947e37764762524001ee42609d9', class: {
|
|
163
215
|
'label-text-wrapper': true,
|
|
164
216
|
'label-text-wrapper-hidden': !hasLabelContent,
|
|
165
|
-
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '
|
|
217
|
+
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '23ff66138f8c3a2f56f39113fc842d54b2f7952a' }), this.renderHintText()), h("div", { key: 'ab914d9623c19fc46821d5e62db92f1192ebbe7e', class: "native-wrapper" }, h("svg", { key: '66e3f4f5dcaa9756fb0e9452299954f9ed3dcb7b', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
|
|
166
218
|
}
|
|
167
219
|
getSVGPath(mode, indeterminate) {
|
|
168
220
|
let path = indeterminate ? (h("path", { d: "M6 12L18 12", part: "mark" })) : (h("path", { d: "M5.9,12.5l3.8,3.8l8.8-8.8", part: "mark" }));
|
|
@@ -430,6 +482,12 @@ export class Checkbox {
|
|
|
430
482
|
}
|
|
431
483
|
};
|
|
432
484
|
}
|
|
485
|
+
static get states() {
|
|
486
|
+
return {
|
|
487
|
+
"isInvalid": {},
|
|
488
|
+
"hintTextId": {}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
433
491
|
static get events() {
|
|
434
492
|
return [{
|
|
435
493
|
"method": "ionChange",
|
|
@@ -585,6 +585,28 @@ export class Datetime {
|
|
|
585
585
|
destroyKeyboardMO();
|
|
586
586
|
}
|
|
587
587
|
};
|
|
588
|
+
/**
|
|
589
|
+
* TODO(FW-6931): Remove this fallback upon solving the root cause
|
|
590
|
+
* Fallback to ensure the datetime becomes ready even if
|
|
591
|
+
* IntersectionObserver never reports it as intersecting.
|
|
592
|
+
*
|
|
593
|
+
* This is primarily used in environments where the observer
|
|
594
|
+
* might not fire as expected, such as when running under
|
|
595
|
+
* synthetic tests that stub IntersectionObserver.
|
|
596
|
+
*/
|
|
597
|
+
this.ensureReadyIfVisible = () => {
|
|
598
|
+
if (this.el.classList.contains('datetime-ready')) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const rect = this.el.getBoundingClientRect();
|
|
602
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
this.initializeListeners();
|
|
606
|
+
writeTask(() => {
|
|
607
|
+
this.el.classList.add('datetime-ready');
|
|
608
|
+
});
|
|
609
|
+
};
|
|
588
610
|
this.processValue = (value) => {
|
|
589
611
|
const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
|
|
590
612
|
const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
|
|
@@ -902,6 +924,17 @@ export class Datetime {
|
|
|
902
924
|
* triggering the `hiddenIO` observer below.
|
|
903
925
|
*/
|
|
904
926
|
raf(() => visibleIO === null || visibleIO === void 0 ? void 0 : visibleIO.observe(intersectionTrackerRef));
|
|
927
|
+
/**
|
|
928
|
+
* TODO(FW-6931): Remove this fallback upon solving the root cause
|
|
929
|
+
* Fallback: If IntersectionObserver never reports that the
|
|
930
|
+
* datetime is visible but the host clearly has layout, ensure
|
|
931
|
+
* we still initialize listeners and mark the component as ready.
|
|
932
|
+
*
|
|
933
|
+
* We schedule this after everything has had a chance to run.
|
|
934
|
+
*/
|
|
935
|
+
setTimeout(() => {
|
|
936
|
+
this.ensureReadyIfVisible();
|
|
937
|
+
}, 100);
|
|
905
938
|
/**
|
|
906
939
|
* We need to clean up listeners when the datetime is hidden
|
|
907
940
|
* in a popover/modal so that we can properly scroll containers
|
|
@@ -1657,7 +1690,7 @@ export class Datetime {
|
|
|
1657
1690
|
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
|
1658
1691
|
const hasWheelVariant = hasDatePresentation && preferWheel;
|
|
1659
1692
|
renderHiddenInput(true, el, name, formatValue(value), disabled);
|
|
1660
|
-
return (h(Host, { key: '
|
|
1693
|
+
return (h(Host, { key: 'efdbc0922670a841bc667ceac392cdc1dedffd01', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
|
|
1661
1694
|
[mode]: true,
|
|
1662
1695
|
['datetime-readonly']: readonly,
|
|
1663
1696
|
['datetime-disabled']: disabled,
|
|
@@ -1667,7 +1700,7 @@ export class Datetime {
|
|
|
1667
1700
|
[`datetime-size-${size}`]: true,
|
|
1668
1701
|
[`datetime-prefer-wheel`]: hasWheelVariant,
|
|
1669
1702
|
[`datetime-grid`]: isGridStyle,
|
|
1670
|
-
})) }, h("div", { key: '
|
|
1703
|
+
})) }, h("div", { key: '3f8bb75fcb0baff55182ef3aa1b535eacc58d81f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
|
|
1671
1704
|
}
|
|
1672
1705
|
static get is() { return "ion-datetime"; }
|
|
1673
1706
|
static get encapsulation() { return "shadow"; }
|
|
@@ -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",
|
|
@@ -202,7 +202,7 @@ export class Select {
|
|
|
202
202
|
* a higher priority.
|
|
203
203
|
*/
|
|
204
204
|
Promise.resolve().then(() => {
|
|
205
|
-
this.
|
|
205
|
+
this.hintTextId = this.getHintTextId();
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
});
|
|
@@ -216,7 +216,7 @@ export class Select {
|
|
|
216
216
|
}
|
|
217
217
|
componentWillLoad() {
|
|
218
218
|
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
|
219
|
-
this.
|
|
219
|
+
this.hintTextId = this.getHintTextId();
|
|
220
220
|
}
|
|
221
221
|
componentDidLoad() {
|
|
222
222
|
/**
|
|
@@ -761,9 +761,9 @@ export class Select {
|
|
|
761
761
|
}
|
|
762
762
|
renderListbox() {
|
|
763
763
|
const { disabled, inputId, isExpanded, required } = this;
|
|
764
|
-
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) }));
|
|
765
765
|
}
|
|
766
|
-
|
|
766
|
+
getHintTextId() {
|
|
767
767
|
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
768
768
|
if (isInvalid && errorText) {
|
|
769
769
|
return errorTextId;
|
|
@@ -1316,7 +1316,7 @@ export class Select {
|
|
|
1316
1316
|
"isExpanded": {},
|
|
1317
1317
|
"hasFocus": {},
|
|
1318
1318
|
"isInvalid": {},
|
|
1319
|
-
"
|
|
1319
|
+
"hintTextId": {}
|
|
1320
1320
|
};
|
|
1321
1321
|
}
|
|
1322
1322
|
static get events() {
|
|
@@ -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
|
*/
|
|
@@ -145,15 +150,52 @@ export class Toggle {
|
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
152
|
async connectedCallback() {
|
|
153
|
+
const { didLoad, el } = this;
|
|
148
154
|
/**
|
|
149
155
|
* If we have not yet rendered
|
|
150
156
|
* ion-toggle, then toggleTrack is not defined.
|
|
151
157
|
* But if we are moving ion-toggle via appendChild,
|
|
152
158
|
* then toggleTrack will be defined.
|
|
153
159
|
*/
|
|
154
|
-
if (
|
|
160
|
+
if (didLoad) {
|
|
155
161
|
this.setupGesture();
|
|
156
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);
|
|
157
199
|
}
|
|
158
200
|
componentDidLoad() {
|
|
159
201
|
this.setupGesture();
|
|
@@ -164,9 +206,15 @@ export class Toggle {
|
|
|
164
206
|
this.gesture.destroy();
|
|
165
207
|
this.gesture = undefined;
|
|
166
208
|
}
|
|
209
|
+
// Clean up validation observer to prevent memory leaks.
|
|
210
|
+
if (this.validationObserver) {
|
|
211
|
+
this.validationObserver.disconnect();
|
|
212
|
+
this.validationObserver = undefined;
|
|
213
|
+
}
|
|
167
214
|
}
|
|
168
215
|
componentWillLoad() {
|
|
169
216
|
this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
|
|
217
|
+
this.hintTextId = this.getHintTextId();
|
|
170
218
|
}
|
|
171
219
|
onStart() {
|
|
172
220
|
this.activated = true;
|
|
@@ -207,9 +255,9 @@ export class Toggle {
|
|
|
207
255
|
get hasLabel() {
|
|
208
256
|
return this.el.textContent !== '';
|
|
209
257
|
}
|
|
210
|
-
|
|
211
|
-
const {
|
|
212
|
-
if (
|
|
258
|
+
getHintTextId() {
|
|
259
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
260
|
+
if (isInvalid && errorText) {
|
|
213
261
|
return errorTextId;
|
|
214
262
|
}
|
|
215
263
|
if (helperText) {
|
|
@@ -222,7 +270,7 @@ export class Toggle {
|
|
|
222
270
|
* This element should only be rendered if hint text is set.
|
|
223
271
|
*/
|
|
224
272
|
renderHintText() {
|
|
225
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
273
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
226
274
|
/**
|
|
227
275
|
* undefined and empty string values should
|
|
228
276
|
* be treated as not having helper/error text.
|
|
@@ -231,15 +279,15 @@ export class Toggle {
|
|
|
231
279
|
if (!hasHintText) {
|
|
232
280
|
return;
|
|
233
281
|
}
|
|
234
|
-
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)));
|
|
235
283
|
}
|
|
236
284
|
render() {
|
|
237
|
-
const { activated, alignment, checked, color, disabled, el,
|
|
285
|
+
const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
|
|
238
286
|
const mode = getIonMode(this);
|
|
239
287
|
const value = this.getValue();
|
|
240
288
|
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
|
241
289
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
242
|
-
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, {
|
|
243
291
|
[mode]: true,
|
|
244
292
|
'in-item': hostContext('ion-item', el),
|
|
245
293
|
'toggle-activated': activated,
|
|
@@ -249,10 +297,10 @@ export class Toggle {
|
|
|
249
297
|
[`toggle-alignment-${alignment}`]: alignment !== undefined,
|
|
250
298
|
[`toggle-label-placement-${labelPlacement}`]: true,
|
|
251
299
|
[`toggle-${rtl}`]: true,
|
|
252
|
-
}) }, 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: {
|
|
253
301
|
'label-text-wrapper': true,
|
|
254
302
|
'label-text-wrapper-hidden': !hasLabel,
|
|
255
|
-
}, 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()))));
|
|
256
304
|
}
|
|
257
305
|
static get is() { return "ion-toggle"; }
|
|
258
306
|
static get encapsulation() { return "shadow"; }
|
|
@@ -515,7 +563,9 @@ export class Toggle {
|
|
|
515
563
|
}
|
|
516
564
|
static get states() {
|
|
517
565
|
return {
|
|
518
|
-
"activated": {}
|
|
566
|
+
"activated": {},
|
|
567
|
+
"isInvalid": {},
|
|
568
|
+
"hintTextId": {}
|
|
519
569
|
};
|
|
520
570
|
}
|
|
521
571
|
static get events() {
|
|
@@ -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
|
};
|
package/dist/docs.json
CHANGED