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
package/components/checkbox.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
|
|
4
|
+
import { proxyCustomElement, HTMLElement, createEvent, Build, h, Host } from '@stencil/core/internal/client';
|
|
5
5
|
import { i as inheritAriaAttributes, e as renderHiddenInput } from './helpers.js';
|
|
6
|
+
import { c as checkInvalidState } from './validity.js';
|
|
6
7
|
import { c as createColorClasses, h as hostContext } from './theme.js';
|
|
7
8
|
import { b as getIonMode } from './ionic-global.js';
|
|
8
9
|
|
|
@@ -63,6 +64,10 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
63
64
|
* submitting if the value is invalid.
|
|
64
65
|
*/
|
|
65
66
|
this.required = false;
|
|
67
|
+
/**
|
|
68
|
+
* Track validation state for proper aria-live announcements.
|
|
69
|
+
*/
|
|
70
|
+
this.isInvalid = false;
|
|
66
71
|
/**
|
|
67
72
|
* Sets the checked property and emits
|
|
68
73
|
* the ionChange event. Use this to update the
|
|
@@ -109,16 +114,63 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
109
114
|
ev.stopPropagation();
|
|
110
115
|
};
|
|
111
116
|
}
|
|
117
|
+
connectedCallback() {
|
|
118
|
+
const { el } = this;
|
|
119
|
+
// Watch for class changes to update validation state.
|
|
120
|
+
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
121
|
+
this.validationObserver = new MutationObserver(() => {
|
|
122
|
+
const newIsInvalid = checkInvalidState(el);
|
|
123
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
124
|
+
this.isInvalid = newIsInvalid;
|
|
125
|
+
/**
|
|
126
|
+
* Screen readers tend to announce changes
|
|
127
|
+
* to `aria-describedby` when the attribute
|
|
128
|
+
* is changed during a blur event for a
|
|
129
|
+
* native form control.
|
|
130
|
+
* However, the announcement can be spotty
|
|
131
|
+
* when using a non-native form control
|
|
132
|
+
* and `forceUpdate()`.
|
|
133
|
+
* This is due to `forceUpdate()` internally
|
|
134
|
+
* rescheduling the DOM update to a lower
|
|
135
|
+
* priority queue regardless if it's called
|
|
136
|
+
* inside a Promise or not, thus causing
|
|
137
|
+
* the screen reader to potentially miss the
|
|
138
|
+
* change.
|
|
139
|
+
* By using a State variable inside a Promise,
|
|
140
|
+
* it guarantees a re-render immediately at
|
|
141
|
+
* a higher priority.
|
|
142
|
+
*/
|
|
143
|
+
Promise.resolve().then(() => {
|
|
144
|
+
this.hintTextId = this.getHintTextId();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
this.validationObserver.observe(el, {
|
|
149
|
+
attributes: true,
|
|
150
|
+
attributeFilter: ['class'],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Always set initial state
|
|
154
|
+
this.isInvalid = checkInvalidState(el);
|
|
155
|
+
}
|
|
112
156
|
componentWillLoad() {
|
|
113
157
|
this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
|
|
158
|
+
this.hintTextId = this.getHintTextId();
|
|
159
|
+
}
|
|
160
|
+
disconnectedCallback() {
|
|
161
|
+
// Clean up validation observer to prevent memory leaks.
|
|
162
|
+
if (this.validationObserver) {
|
|
163
|
+
this.validationObserver.disconnect();
|
|
164
|
+
this.validationObserver = undefined;
|
|
165
|
+
}
|
|
114
166
|
}
|
|
115
167
|
/** @internal */
|
|
116
168
|
async setFocus() {
|
|
117
169
|
this.el.focus();
|
|
118
170
|
}
|
|
119
|
-
|
|
120
|
-
const {
|
|
121
|
-
if (
|
|
171
|
+
getHintTextId() {
|
|
172
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
173
|
+
if (isInvalid && errorText) {
|
|
122
174
|
return errorTextId;
|
|
123
175
|
}
|
|
124
176
|
if (helperText) {
|
|
@@ -131,7 +183,7 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
131
183
|
* This element should only be rendered if hint text is set.
|
|
132
184
|
*/
|
|
133
185
|
renderHintText() {
|
|
134
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
186
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
135
187
|
/**
|
|
136
188
|
* undefined and empty string values should
|
|
137
189
|
* be treated as not having helper/error text.
|
|
@@ -140,7 +192,7 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
140
192
|
if (!hasHintText) {
|
|
141
193
|
return;
|
|
142
194
|
}
|
|
143
|
-
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)));
|
|
195
|
+
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)));
|
|
144
196
|
}
|
|
145
197
|
render() {
|
|
146
198
|
const { color, checked, disabled, el, getSVGPath, indeterminate, inheritedAttributes, inputId, justify, labelPlacement, name, value, alignment, required, } = this;
|
|
@@ -150,7 +202,7 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
150
202
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
151
203
|
// The host element must have a checkbox role to ensure proper VoiceOver
|
|
152
204
|
// support in Safari for accessibility.
|
|
153
|
-
return (h(Host, { key: '
|
|
205
|
+
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, {
|
|
154
206
|
[mode]: true,
|
|
155
207
|
'in-item': hostContext('ion-item', el),
|
|
156
208
|
'checkbox-checked': checked,
|
|
@@ -160,10 +212,10 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
160
212
|
[`checkbox-justify-${justify}`]: justify !== undefined,
|
|
161
213
|
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
|
|
162
214
|
[`checkbox-label-placement-${labelPlacement}`]: true,
|
|
163
|
-
}) }, h("label", { key: '
|
|
215
|
+
}) }, 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: {
|
|
164
216
|
'label-text-wrapper': true,
|
|
165
217
|
'label-text-wrapper-hidden': !hasLabelContent,
|
|
166
|
-
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '
|
|
218
|
+
}, 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)))));
|
|
167
219
|
}
|
|
168
220
|
getSVGPath(mode, indeterminate) {
|
|
169
221
|
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" }));
|
|
@@ -190,6 +242,8 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
190
242
|
"justify": [1],
|
|
191
243
|
"alignment": [1],
|
|
192
244
|
"required": [4],
|
|
245
|
+
"isInvalid": [32],
|
|
246
|
+
"hintTextId": [32],
|
|
193
247
|
"setFocus": [64]
|
|
194
248
|
}]);
|
|
195
249
|
let checkboxIds = 0;
|
|
@@ -790,6 +790,28 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
|
|
|
790
790
|
destroyKeyboardMO();
|
|
791
791
|
}
|
|
792
792
|
};
|
|
793
|
+
/**
|
|
794
|
+
* TODO(FW-6931): Remove this fallback upon solving the root cause
|
|
795
|
+
* Fallback to ensure the datetime becomes ready even if
|
|
796
|
+
* IntersectionObserver never reports it as intersecting.
|
|
797
|
+
*
|
|
798
|
+
* This is primarily used in environments where the observer
|
|
799
|
+
* might not fire as expected, such as when running under
|
|
800
|
+
* synthetic tests that stub IntersectionObserver.
|
|
801
|
+
*/
|
|
802
|
+
this.ensureReadyIfVisible = () => {
|
|
803
|
+
if (this.el.classList.contains('datetime-ready')) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const rect = this.el.getBoundingClientRect();
|
|
807
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
this.initializeListeners();
|
|
811
|
+
writeTask(() => {
|
|
812
|
+
this.el.classList.add('datetime-ready');
|
|
813
|
+
});
|
|
814
|
+
};
|
|
793
815
|
this.processValue = (value) => {
|
|
794
816
|
const hasValue = value !== null && value !== undefined && value !== '' && (!Array.isArray(value) || value.length > 0);
|
|
795
817
|
const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
|
|
@@ -1107,6 +1129,17 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
|
|
|
1107
1129
|
* triggering the `hiddenIO` observer below.
|
|
1108
1130
|
*/
|
|
1109
1131
|
raf(() => visibleIO === null || visibleIO === void 0 ? void 0 : visibleIO.observe(intersectionTrackerRef));
|
|
1132
|
+
/**
|
|
1133
|
+
* TODO(FW-6931): Remove this fallback upon solving the root cause
|
|
1134
|
+
* Fallback: If IntersectionObserver never reports that the
|
|
1135
|
+
* datetime is visible but the host clearly has layout, ensure
|
|
1136
|
+
* we still initialize listeners and mark the component as ready.
|
|
1137
|
+
*
|
|
1138
|
+
* We schedule this after everything has had a chance to run.
|
|
1139
|
+
*/
|
|
1140
|
+
setTimeout(() => {
|
|
1141
|
+
this.ensureReadyIfVisible();
|
|
1142
|
+
}, 100);
|
|
1110
1143
|
/**
|
|
1111
1144
|
* We need to clean up listeners when the datetime is hidden
|
|
1112
1145
|
* in a popover/modal so that we can properly scroll containers
|
|
@@ -1862,7 +1895,7 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
|
|
|
1862
1895
|
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
|
1863
1896
|
const hasWheelVariant = hasDatePresentation && preferWheel;
|
|
1864
1897
|
renderHiddenInput(true, el, name, formatValue(value), disabled);
|
|
1865
|
-
return (h(Host, { key: '
|
|
1898
|
+
return (h(Host, { key: 'efdbc0922670a841bc667ceac392cdc1dedffd01', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
|
|
1866
1899
|
[mode]: true,
|
|
1867
1900
|
['datetime-readonly']: readonly,
|
|
1868
1901
|
['datetime-disabled']: disabled,
|
|
@@ -1872,7 +1905,7 @@ const Datetime = /*@__PURE__*/ proxyCustomElement(class Datetime extends HTMLEle
|
|
|
1872
1905
|
[`datetime-size-${size}`]: true,
|
|
1873
1906
|
[`datetime-prefer-wheel`]: hasWheelVariant,
|
|
1874
1907
|
[`datetime-grid`]: isGridStyle,
|
|
1875
|
-
})) }, h("div", { key: '
|
|
1908
|
+
})) }, h("div", { key: '3f8bb75fcb0baff55182ef3aa1b535eacc58d81f', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
|
|
1876
1909
|
}
|
|
1877
1910
|
get el() { return this; }
|
|
1878
1911
|
static get watchers() { return {
|
package/components/ion-input.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { proxyCustomElement, HTMLElement, createEvent, forceUpdate, Build, h, Host } from '@stencil/core/internal/client';
|
|
5
|
-
import { c as createNotchController
|
|
5
|
+
import { c as createNotchController } from './notch-controller.js';
|
|
6
|
+
import { c as checkInvalidState } from './validity.js';
|
|
6
7
|
import { l as debounceEvent, i as inheritAriaAttributes, d as inheritAttributes, c as componentOnReady } from './helpers.js';
|
|
7
8
|
import { c as createSlotMutationController, g as getCounterText } from './input.utils.js';
|
|
8
9
|
import { h as hostContext, c as createColorClasses } from './theme.js';
|
package/components/ion-select.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { proxyCustomElement, HTMLElement, createEvent, Build, h, Host, forceUpdate } from '@stencil/core/internal/client';
|
|
5
|
-
import { c as createNotchController
|
|
5
|
+
import { c as createNotchController } from './notch-controller.js';
|
|
6
6
|
import { i as isOptionSelected, d as defineCustomElement$8, c as compareOptions } from './radio.js';
|
|
7
|
+
import { c as checkInvalidState } from './validity.js';
|
|
7
8
|
import { d as inheritAttributes, e as renderHiddenInput, h as focusVisibleElement } from './helpers.js';
|
|
8
9
|
import { p as printIonWarning } from './index4.js';
|
|
9
10
|
import { c as popoverController, b as actionSheetController, a as alertController, m as modalController } from './overlays.js';
|
|
@@ -223,7 +224,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
|
|
|
223
224
|
* a higher priority.
|
|
224
225
|
*/
|
|
225
226
|
Promise.resolve().then(() => {
|
|
226
|
-
this.
|
|
227
|
+
this.hintTextId = this.getHintTextId();
|
|
227
228
|
});
|
|
228
229
|
}
|
|
229
230
|
});
|
|
@@ -237,7 +238,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
|
|
|
237
238
|
}
|
|
238
239
|
componentWillLoad() {
|
|
239
240
|
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
|
240
|
-
this.
|
|
241
|
+
this.hintTextId = this.getHintTextId();
|
|
241
242
|
}
|
|
242
243
|
componentDidLoad() {
|
|
243
244
|
/**
|
|
@@ -736,9 +737,9 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
|
|
|
736
737
|
}
|
|
737
738
|
renderListbox() {
|
|
738
739
|
const { disabled, inputId, isExpanded, required } = this;
|
|
739
|
-
return (h("button", { disabled: disabled, id: inputId, "aria-label": this.ariaLabel, "aria-haspopup": "dialog", "aria-expanded": `${isExpanded}`, "aria-describedby": this.
|
|
740
|
+
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) }));
|
|
740
741
|
}
|
|
741
|
-
|
|
742
|
+
getHintTextId() {
|
|
742
743
|
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
743
744
|
if (isInvalid && errorText) {
|
|
744
745
|
return errorTextId;
|
|
@@ -860,7 +861,7 @@ const Select = /*@__PURE__*/ proxyCustomElement(class Select extends HTMLElement
|
|
|
860
861
|
"isExpanded": [32],
|
|
861
862
|
"hasFocus": [32],
|
|
862
863
|
"isInvalid": [32],
|
|
863
|
-
"
|
|
864
|
+
"hintTextId": [32],
|
|
864
865
|
"open": [64]
|
|
865
866
|
}, undefined, {
|
|
866
867
|
"disabled": ["styleChanged"],
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { proxyCustomElement, HTMLElement, createEvent, forceUpdate, Build, writeTask, h, Host } from '@stencil/core/internal/client';
|
|
5
|
-
import { c as createNotchController
|
|
5
|
+
import { c as createNotchController } from './notch-controller.js';
|
|
6
|
+
import { c as checkInvalidState } from './validity.js';
|
|
6
7
|
import { l as debounceEvent, i as inheritAriaAttributes, d as inheritAttributes, c as componentOnReady } from './helpers.js';
|
|
7
8
|
import { c as createSlotMutationController, g as getCounterText } from './input.utils.js';
|
|
8
9
|
import { h as hostContext, c as createColorClasses } from './theme.js';
|
package/components/ion-toggle.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
|
|
4
|
+
import { proxyCustomElement, HTMLElement, createEvent, Build, h, Host } from '@stencil/core/internal/client';
|
|
5
5
|
import { i as inheritAriaAttributes, e as renderHiddenInput } from './helpers.js';
|
|
6
|
+
import { c as checkInvalidState } from './validity.js';
|
|
6
7
|
import { d as hapticSelection } from './haptic.js';
|
|
7
8
|
import { a as isPlatform, b as getIonMode } from './ionic-global.js';
|
|
8
9
|
import { i as isRTL } from './dir.js';
|
|
@@ -33,6 +34,10 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
33
34
|
this.inheritedAttributes = {};
|
|
34
35
|
this.didLoad = false;
|
|
35
36
|
this.activated = false;
|
|
37
|
+
/**
|
|
38
|
+
* Track validation state for proper aria-live announcements.
|
|
39
|
+
*/
|
|
40
|
+
this.isInvalid = false;
|
|
36
41
|
/**
|
|
37
42
|
* The name of the control, which is submitted with the form data.
|
|
38
43
|
*/
|
|
@@ -146,15 +151,52 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
146
151
|
});
|
|
147
152
|
}
|
|
148
153
|
async connectedCallback() {
|
|
154
|
+
const { didLoad, el } = this;
|
|
149
155
|
/**
|
|
150
156
|
* If we have not yet rendered
|
|
151
157
|
* ion-toggle, then toggleTrack is not defined.
|
|
152
158
|
* But if we are moving ion-toggle via appendChild,
|
|
153
159
|
* then toggleTrack will be defined.
|
|
154
160
|
*/
|
|
155
|
-
if (
|
|
161
|
+
if (didLoad) {
|
|
156
162
|
this.setupGesture();
|
|
157
163
|
}
|
|
164
|
+
// Watch for class changes to update validation state.
|
|
165
|
+
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
166
|
+
this.validationObserver = new MutationObserver(() => {
|
|
167
|
+
const newIsInvalid = checkInvalidState(el);
|
|
168
|
+
if (this.isInvalid !== newIsInvalid) {
|
|
169
|
+
this.isInvalid = newIsInvalid;
|
|
170
|
+
/**
|
|
171
|
+
* Screen readers tend to announce changes
|
|
172
|
+
* to `aria-describedby` when the attribute
|
|
173
|
+
* is changed during a blur event for a
|
|
174
|
+
* native form control.
|
|
175
|
+
* However, the announcement can be spotty
|
|
176
|
+
* when using a non-native form control
|
|
177
|
+
* and `forceUpdate()`.
|
|
178
|
+
* This is due to `forceUpdate()` internally
|
|
179
|
+
* rescheduling the DOM update to a lower
|
|
180
|
+
* priority queue regardless if it's called
|
|
181
|
+
* inside a Promise or not, thus causing
|
|
182
|
+
* the screen reader to potentially miss the
|
|
183
|
+
* change.
|
|
184
|
+
* By using a State variable inside a Promise,
|
|
185
|
+
* it guarantees a re-render immediately at
|
|
186
|
+
* a higher priority.
|
|
187
|
+
*/
|
|
188
|
+
Promise.resolve().then(() => {
|
|
189
|
+
this.hintTextId = this.getHintTextId();
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
this.validationObserver.observe(el, {
|
|
194
|
+
attributes: true,
|
|
195
|
+
attributeFilter: ['class'],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// Always set initial state
|
|
199
|
+
this.isInvalid = checkInvalidState(el);
|
|
158
200
|
}
|
|
159
201
|
componentDidLoad() {
|
|
160
202
|
this.setupGesture();
|
|
@@ -165,9 +207,15 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
165
207
|
this.gesture.destroy();
|
|
166
208
|
this.gesture = undefined;
|
|
167
209
|
}
|
|
210
|
+
// Clean up validation observer to prevent memory leaks.
|
|
211
|
+
if (this.validationObserver) {
|
|
212
|
+
this.validationObserver.disconnect();
|
|
213
|
+
this.validationObserver = undefined;
|
|
214
|
+
}
|
|
168
215
|
}
|
|
169
216
|
componentWillLoad() {
|
|
170
217
|
this.inheritedAttributes = Object.assign({}, inheritAriaAttributes(this.el));
|
|
218
|
+
this.hintTextId = this.getHintTextId();
|
|
171
219
|
}
|
|
172
220
|
onStart() {
|
|
173
221
|
this.activated = true;
|
|
@@ -208,9 +256,9 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
208
256
|
get hasLabel() {
|
|
209
257
|
return this.el.textContent !== '';
|
|
210
258
|
}
|
|
211
|
-
|
|
212
|
-
const {
|
|
213
|
-
if (
|
|
259
|
+
getHintTextId() {
|
|
260
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
261
|
+
if (isInvalid && errorText) {
|
|
214
262
|
return errorTextId;
|
|
215
263
|
}
|
|
216
264
|
if (helperText) {
|
|
@@ -223,7 +271,7 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
223
271
|
* This element should only be rendered if hint text is set.
|
|
224
272
|
*/
|
|
225
273
|
renderHintText() {
|
|
226
|
-
const { helperText, errorText, helperTextId, errorTextId } = this;
|
|
274
|
+
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
|
227
275
|
/**
|
|
228
276
|
* undefined and empty string values should
|
|
229
277
|
* be treated as not having helper/error text.
|
|
@@ -232,15 +280,15 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
232
280
|
if (!hasHintText) {
|
|
233
281
|
return;
|
|
234
282
|
}
|
|
235
|
-
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)));
|
|
283
|
+
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)));
|
|
236
284
|
}
|
|
237
285
|
render() {
|
|
238
|
-
const { activated, alignment, checked, color, disabled, el,
|
|
286
|
+
const { activated, alignment, checked, color, disabled, el, hasLabel, inheritedAttributes, inputId, inputLabelId, justify, labelPlacement, name, required, } = this;
|
|
239
287
|
const mode = getIonMode(this);
|
|
240
288
|
const value = this.getValue();
|
|
241
289
|
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
|
242
290
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
243
|
-
return (h(Host, { key: '
|
|
291
|
+
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, {
|
|
244
292
|
[mode]: true,
|
|
245
293
|
'in-item': hostContext('ion-item', el),
|
|
246
294
|
'toggle-activated': activated,
|
|
@@ -250,10 +298,10 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
250
298
|
[`toggle-alignment-${alignment}`]: alignment !== undefined,
|
|
251
299
|
[`toggle-label-placement-${labelPlacement}`]: true,
|
|
252
300
|
[`toggle-${rtl}`]: true,
|
|
253
|
-
}) }, h("label", { key: '
|
|
301
|
+
}) }, 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: {
|
|
254
302
|
'label-text-wrapper': true,
|
|
255
303
|
'label-text-wrapper-hidden': !hasLabel,
|
|
256
|
-
}, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '
|
|
304
|
+
}, part: "label", id: inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '7b162b7dd27199cca2a4c995276a18b9f8e44aaf' }), this.renderHintText()), h("div", { key: 'd13c34bd42fca01cc73ddb4ea7e471b33a282a3e', class: "native-wrapper" }, this.renderToggleControl()))));
|
|
257
305
|
}
|
|
258
306
|
get el() { return this; }
|
|
259
307
|
static get watchers() { return {
|
|
@@ -276,7 +324,9 @@ const Toggle = /*@__PURE__*/ proxyCustomElement(class Toggle extends HTMLElement
|
|
|
276
324
|
"justify": [1],
|
|
277
325
|
"alignment": [1],
|
|
278
326
|
"required": [4],
|
|
279
|
-
"activated": [32]
|
|
327
|
+
"activated": [32],
|
|
328
|
+
"isInvalid": [32],
|
|
329
|
+
"hintTextId": [32]
|
|
280
330
|
}, undefined, {
|
|
281
331
|
"disabled": ["disabledChanged"]
|
|
282
332
|
}]);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
import { w as win } from './index9.js';
|
|
5
|
+
import { r as raf } from './helpers.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A utility to calculate the size of an outline notch
|
|
9
|
+
* width relative to the content passed. This is used in
|
|
10
|
+
* components such as `ion-select` with `fill="outline"`
|
|
11
|
+
* where we need to pass slotted HTML content. This is not
|
|
12
|
+
* needed when rendering plaintext content because we can
|
|
13
|
+
* render the plaintext again hidden with `opacity: 0` inside
|
|
14
|
+
* of the notch. As a result we can rely on the intrinsic size
|
|
15
|
+
* of the element to correctly compute the notch width. We
|
|
16
|
+
* cannot do this with slotted content because we cannot project
|
|
17
|
+
* it into 2 places at once.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
* @param el: The host element
|
|
21
|
+
* @param getNotchSpacerEl: A function that returns a reference to the notch spacer element inside of the component template.
|
|
22
|
+
* @param getLabelSlot: A function that returns a reference to the slotted content.
|
|
23
|
+
*/
|
|
24
|
+
const createNotchController = (el, getNotchSpacerEl, getLabelSlot) => {
|
|
25
|
+
let notchVisibilityIO;
|
|
26
|
+
const needsExplicitNotchWidth = () => {
|
|
27
|
+
const notchSpacerEl = getNotchSpacerEl();
|
|
28
|
+
if (
|
|
29
|
+
/**
|
|
30
|
+
* If the notch is not being used
|
|
31
|
+
* then we do not need to set the notch width.
|
|
32
|
+
*/
|
|
33
|
+
notchSpacerEl === undefined ||
|
|
34
|
+
/**
|
|
35
|
+
* If either the label property is being
|
|
36
|
+
* used or the label slot is not defined,
|
|
37
|
+
* then we do not need to estimate the notch width.
|
|
38
|
+
*/
|
|
39
|
+
el.label !== undefined ||
|
|
40
|
+
getLabelSlot() === null) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
const calculateNotchWidth = () => {
|
|
46
|
+
if (needsExplicitNotchWidth()) {
|
|
47
|
+
/**
|
|
48
|
+
* Run this the frame after
|
|
49
|
+
* the browser has re-painted the host element.
|
|
50
|
+
* Otherwise, the label element may have a width
|
|
51
|
+
* of 0 and the IntersectionObserver will be used.
|
|
52
|
+
*/
|
|
53
|
+
raf(() => {
|
|
54
|
+
setNotchWidth();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* When using a label prop we can render
|
|
60
|
+
* the label value inside of the notch and
|
|
61
|
+
* let the browser calculate the size of the notch.
|
|
62
|
+
* However, we cannot render the label slot in multiple
|
|
63
|
+
* places so we need to manually calculate the notch dimension
|
|
64
|
+
* based on the size of the slotted content.
|
|
65
|
+
*
|
|
66
|
+
* This function should only be used to set the notch width
|
|
67
|
+
* on slotted label content. The notch width for label prop
|
|
68
|
+
* content is automatically calculated based on the
|
|
69
|
+
* intrinsic size of the label text.
|
|
70
|
+
*/
|
|
71
|
+
const setNotchWidth = () => {
|
|
72
|
+
const notchSpacerEl = getNotchSpacerEl();
|
|
73
|
+
if (notchSpacerEl === undefined) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!needsExplicitNotchWidth()) {
|
|
77
|
+
notchSpacerEl.style.removeProperty('width');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const width = getLabelSlot().scrollWidth;
|
|
81
|
+
if (
|
|
82
|
+
/**
|
|
83
|
+
* If the computed width of the label is 0
|
|
84
|
+
* and notchSpacerEl's offsetParent is null
|
|
85
|
+
* then that means the element is hidden.
|
|
86
|
+
* As a result, we need to wait for the element
|
|
87
|
+
* to become visible before setting the notch width.
|
|
88
|
+
*
|
|
89
|
+
* We do not check el.offsetParent because
|
|
90
|
+
* that can be null if the host element has
|
|
91
|
+
* position: fixed applied to it.
|
|
92
|
+
* notchSpacerEl does not have position: fixed.
|
|
93
|
+
*/
|
|
94
|
+
width === 0 &&
|
|
95
|
+
notchSpacerEl.offsetParent === null &&
|
|
96
|
+
win !== undefined &&
|
|
97
|
+
'IntersectionObserver' in win) {
|
|
98
|
+
/**
|
|
99
|
+
* If there is an IO already attached
|
|
100
|
+
* then that will update the notch
|
|
101
|
+
* once the element becomes visible.
|
|
102
|
+
* As a result, there is no need to create
|
|
103
|
+
* another one.
|
|
104
|
+
*/
|
|
105
|
+
if (notchVisibilityIO !== undefined) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const io = (notchVisibilityIO = new IntersectionObserver((ev) => {
|
|
109
|
+
/**
|
|
110
|
+
* If the element is visible then we
|
|
111
|
+
* can try setting the notch width again.
|
|
112
|
+
*/
|
|
113
|
+
if (ev[0].intersectionRatio === 1) {
|
|
114
|
+
setNotchWidth();
|
|
115
|
+
io.disconnect();
|
|
116
|
+
notchVisibilityIO = undefined;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* Set the root to be the host element
|
|
121
|
+
* This causes the IO callback
|
|
122
|
+
* to be fired in WebKit as soon as the element
|
|
123
|
+
* is visible. If we used the default root value
|
|
124
|
+
* then WebKit would only fire the IO callback
|
|
125
|
+
* after any animations (such as a modal transition)
|
|
126
|
+
* finished, and there would potentially be a flicker.
|
|
127
|
+
*/
|
|
128
|
+
{ threshold: 0.01, root: el }));
|
|
129
|
+
io.observe(notchSpacerEl);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* If the element is visible then we can set the notch width.
|
|
134
|
+
* The notch is only visible when the label is scaled,
|
|
135
|
+
* which is why we multiply the width by 0.75 as this is
|
|
136
|
+
* the same amount the label element is scaled by in the host CSS.
|
|
137
|
+
* (See $form-control-label-stacked-scale in ionic.globals.scss).
|
|
138
|
+
*/
|
|
139
|
+
notchSpacerEl.style.setProperty('width', `${width * 0.75}px`);
|
|
140
|
+
};
|
|
141
|
+
const destroy = () => {
|
|
142
|
+
if (notchVisibilityIO) {
|
|
143
|
+
notchVisibilityIO.disconnect();
|
|
144
|
+
notchVisibilityIO = undefined;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
calculateNotchWidth,
|
|
149
|
+
destroy,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export { createNotchController as c };
|