voyager-ionic-core 8.7.6 → 8.7.9
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 +4 -7
- package/components/header.js +42 -4
- package/components/index2.js +74 -3
- package/components/ion-accordion.js +93 -14
- package/components/ion-input.js +6 -14
- package/components/ion-select.js +58 -10
- package/components/ion-textarea.js +5 -13
- package/components/ion-toggle.js +4 -7
- package/components/{notch-controller.js → validity.js} +14 -1
- 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 +4 -7
- package/dist/cjs/ion-input.cjs.entry.js +7 -15
- 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-select_3.cjs.entry.js +56 -10
- package/dist/cjs/ion-textarea.cjs.entry.js +6 -14
- package/dist/cjs/ion-toggle.cjs.entry.js +4 -7
- 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/{notch-controller-Bzqhjm4f.js → validity-C8QoAYT2.js} +14 -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 +4 -7
- 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/select/select.js +59 -11
- package/dist/collection/components/textarea/textarea.js +5 -13
- package/dist/collection/components/toggle/toggle.js +4 -7
- 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/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 +4 -7
- package/dist/esm/ion-input.entry.js +6 -14
- 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-select_3.entry.js +55 -9
- package/dist/esm/ion-textarea.entry.js +5 -13
- package/dist/esm/ion-toggle.entry.js +4 -7
- 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/{notch-controller-BwelN_JM.js → validity-B8oWougr.js} +14 -1
- package/dist/ionic/index.esm.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-43ed1ef5.entry.js +4 -0
- package/dist/ionic/p-4cc26913.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-8bdfc8f6.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-DUt5fQmA.js +4 -0
- package/dist/ionic/{p-9R1XyICs.js → p-DZRJwG4S.js} +1 -1
- package/dist/ionic/{p-DCv9sLH2.js → p-DieJyvMP.js} +1 -1
- package/dist/ionic/p-d0a2a1ab.entry.js +4 -0
- package/dist/ionic/p-dc2e126d.entry.js +4 -0
- package/dist/ionic/{p-de7b5fa3.entry.js → p-e16b69e1.entry.js} +1 -1
- package/dist/ionic/p-f65f9308.entry.js +4 -0
- package/dist/ionic/p-fc278823.entry.js +4 -0
- 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 +0 -1
- 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/select/select.d.ts +6 -0
- package/dist/types/components/textarea/textarea.d.ts +0 -4
- package/dist/types/components/toggle/toggle.d.ts +0 -1
- 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 +262 -73
- package/hydrate/index.mjs +262 -73
- package/package.json +3 -3
- package/dist/ionic/p-1c8a476d.entry.js +0 -4
- package/dist/ionic/p-3355a2ff.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-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
|
@@ -21,10 +21,57 @@ import { getIonMode } from "../../global/ionic-global";
|
|
|
21
21
|
*/
|
|
22
22
|
export class Accordion {
|
|
23
23
|
constructor() {
|
|
24
|
-
this.
|
|
24
|
+
this.accordionGroupUpdateHandler = () => {
|
|
25
|
+
/**
|
|
26
|
+
* Determine if this update will cause an actual state change.
|
|
27
|
+
* We only want to mark as "interacted" if the state is changing.
|
|
28
|
+
*/
|
|
29
|
+
const accordionGroup = this.accordionGroupEl;
|
|
30
|
+
if (accordionGroup) {
|
|
31
|
+
const value = accordionGroup.value;
|
|
32
|
+
const accordionValue = this.value;
|
|
33
|
+
const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
|
|
34
|
+
const isExpanded = this.state === 4 /* AccordionState.Expanded */ || this.state === 8 /* AccordionState.Expanding */;
|
|
35
|
+
const stateWillChange = shouldExpand !== isExpanded;
|
|
36
|
+
/**
|
|
37
|
+
* Only mark as interacted if:
|
|
38
|
+
* 1. This is not the first update we've received with a defined value
|
|
39
|
+
* 2. The state is actually changing (prevents redundant updates from enabling animations)
|
|
40
|
+
*/
|
|
41
|
+
if (this.hasReceivedFirstUpdate && stateWillChange) {
|
|
42
|
+
this.hasInteracted = true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Only count this as the first update if the group value is defined.
|
|
46
|
+
* This prevents the initial undefined value from the group's componentDidLoad
|
|
47
|
+
* from being treated as the first real update.
|
|
48
|
+
*/
|
|
49
|
+
if (value !== undefined) {
|
|
50
|
+
this.hasReceivedFirstUpdate = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.updateState();
|
|
54
|
+
};
|
|
25
55
|
this.state = 1 /* AccordionState.Collapsed */;
|
|
26
56
|
this.isNext = false;
|
|
27
57
|
this.isPrevious = false;
|
|
58
|
+
/**
|
|
59
|
+
* Tracks whether a user-initiated interaction has occurred.
|
|
60
|
+
* Animations are disabled until the first interaction happens.
|
|
61
|
+
* This prevents the accordion from animating when it's programmatically
|
|
62
|
+
* set to an expanded or collapsed state on initial load.
|
|
63
|
+
*/
|
|
64
|
+
this.hasInteracted = false;
|
|
65
|
+
/**
|
|
66
|
+
* Tracks if this accordion has ever been expanded.
|
|
67
|
+
* Used to prevent the first expansion from animating.
|
|
68
|
+
*/
|
|
69
|
+
this.hasEverBeenExpanded = false;
|
|
70
|
+
/**
|
|
71
|
+
* Tracks if this accordion has received its first update from the group.
|
|
72
|
+
* Used to distinguish initial programmatic sets from user interactions.
|
|
73
|
+
*/
|
|
74
|
+
this.hasReceivedFirstUpdate = false;
|
|
28
75
|
/**
|
|
29
76
|
* The value of the accordion. Defaults to an autogenerated
|
|
30
77
|
* value.
|
|
@@ -129,10 +176,15 @@ export class Accordion {
|
|
|
129
176
|
iconEl.setAttribute('aria-hidden', 'true');
|
|
130
177
|
ionItem.appendChild(iconEl);
|
|
131
178
|
};
|
|
132
|
-
this.expandAccordion = (
|
|
179
|
+
this.expandAccordion = () => {
|
|
133
180
|
const { contentEl, contentElWrapper } = this;
|
|
134
|
-
|
|
181
|
+
/**
|
|
182
|
+
* If the content elements aren't available yet, just set the state.
|
|
183
|
+
* This happens on initial render before the DOM is ready.
|
|
184
|
+
*/
|
|
185
|
+
if (contentEl === undefined || contentElWrapper === undefined) {
|
|
135
186
|
this.state = 4 /* AccordionState.Expanded */;
|
|
187
|
+
this.hasEverBeenExpanded = true;
|
|
136
188
|
return;
|
|
137
189
|
}
|
|
138
190
|
if (this.state === 4 /* AccordionState.Expanded */) {
|
|
@@ -141,6 +193,11 @@ export class Accordion {
|
|
|
141
193
|
if (this.currentRaf !== undefined) {
|
|
142
194
|
cancelAnimationFrame(this.currentRaf);
|
|
143
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Mark that this accordion has been expanded at least once.
|
|
198
|
+
* This allows subsequent expansions to animate.
|
|
199
|
+
*/
|
|
200
|
+
this.hasEverBeenExpanded = true;
|
|
144
201
|
if (this.shouldAnimate()) {
|
|
145
202
|
raf(() => {
|
|
146
203
|
this.state = 8 /* AccordionState.Expanding */;
|
|
@@ -158,9 +215,13 @@ export class Accordion {
|
|
|
158
215
|
this.state = 4 /* AccordionState.Expanded */;
|
|
159
216
|
}
|
|
160
217
|
};
|
|
161
|
-
this.collapseAccordion = (
|
|
218
|
+
this.collapseAccordion = () => {
|
|
162
219
|
const { contentEl } = this;
|
|
163
|
-
|
|
220
|
+
/**
|
|
221
|
+
* If the content element isn't available yet, just set the state.
|
|
222
|
+
* This happens on initial render before the DOM is ready.
|
|
223
|
+
*/
|
|
224
|
+
if (contentEl === undefined) {
|
|
164
225
|
this.state = 1 /* AccordionState.Collapsed */;
|
|
165
226
|
return;
|
|
166
227
|
}
|
|
@@ -195,6 +256,18 @@ export class Accordion {
|
|
|
195
256
|
* of what is set in the config.
|
|
196
257
|
*/
|
|
197
258
|
this.shouldAnimate = () => {
|
|
259
|
+
/**
|
|
260
|
+
* Don't animate until after the first user interaction.
|
|
261
|
+
* This prevents animations on initial load when accordions
|
|
262
|
+
* start in an expanded or collapsed state programmatically.
|
|
263
|
+
*
|
|
264
|
+
* Additionally, don't animate the very first expansion even if
|
|
265
|
+
* hasInteracted is true. This handles edge cases like React StrictMode
|
|
266
|
+
* where effects run twice and might incorrectly mark as interacted.
|
|
267
|
+
*/
|
|
268
|
+
if (!this.hasInteracted || !this.hasEverBeenExpanded) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
198
271
|
if (typeof window === 'undefined') {
|
|
199
272
|
return false;
|
|
200
273
|
}
|
|
@@ -211,7 +284,7 @@ export class Accordion {
|
|
|
211
284
|
}
|
|
212
285
|
return true;
|
|
213
286
|
};
|
|
214
|
-
this.updateState = async (
|
|
287
|
+
this.updateState = async () => {
|
|
215
288
|
const accordionGroup = this.accordionGroupEl;
|
|
216
289
|
const accordionValue = this.value;
|
|
217
290
|
if (!accordionGroup) {
|
|
@@ -220,11 +293,11 @@ export class Accordion {
|
|
|
220
293
|
const value = accordionGroup.value;
|
|
221
294
|
const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
|
|
222
295
|
if (shouldExpand) {
|
|
223
|
-
this.expandAccordion(
|
|
296
|
+
this.expandAccordion();
|
|
224
297
|
this.isNext = this.isPrevious = false;
|
|
225
298
|
}
|
|
226
299
|
else {
|
|
227
|
-
this.collapseAccordion(
|
|
300
|
+
this.collapseAccordion();
|
|
228
301
|
/**
|
|
229
302
|
* When using popout or inset,
|
|
230
303
|
* the collapsed accordion items
|
|
@@ -272,14 +345,14 @@ export class Accordion {
|
|
|
272
345
|
var _a;
|
|
273
346
|
const accordionGroupEl = (this.accordionGroupEl = (_a = this.el) === null || _a === void 0 ? void 0 : _a.closest('ion-accordion-group'));
|
|
274
347
|
if (accordionGroupEl) {
|
|
275
|
-
this.updateState(
|
|
276
|
-
addEventListener(accordionGroupEl, 'ionValueChange', this.
|
|
348
|
+
this.updateState();
|
|
349
|
+
addEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler);
|
|
277
350
|
}
|
|
278
351
|
}
|
|
279
352
|
disconnectedCallback() {
|
|
280
353
|
const accordionGroupEl = this.accordionGroupEl;
|
|
281
354
|
if (accordionGroupEl) {
|
|
282
|
-
removeEventListener(accordionGroupEl, 'ionValueChange', this.
|
|
355
|
+
removeEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler);
|
|
283
356
|
}
|
|
284
357
|
}
|
|
285
358
|
componentDidLoad() {
|
|
@@ -303,6 +376,11 @@ export class Accordion {
|
|
|
303
376
|
const { accordionGroupEl, disabled, readonly, value, state } = this;
|
|
304
377
|
if (disabled || readonly)
|
|
305
378
|
return;
|
|
379
|
+
/**
|
|
380
|
+
* Mark that the user has interacted with the accordion.
|
|
381
|
+
* This enables animations for all future state changes.
|
|
382
|
+
*/
|
|
383
|
+
this.hasInteracted = true;
|
|
306
384
|
if (accordionGroupEl) {
|
|
307
385
|
/**
|
|
308
386
|
* Because the accordion group may or may
|
|
@@ -323,7 +401,7 @@ export class Accordion {
|
|
|
323
401
|
const headerPart = expanded ? 'header expanded' : 'header';
|
|
324
402
|
const contentPart = expanded ? 'content expanded' : 'content';
|
|
325
403
|
this.setAria(expanded);
|
|
326
|
-
return (h(Host, { key: '
|
|
404
|
+
return (h(Host, { key: '9c90bce01eff7e5774a19f69c872f3761d66cf3c', class: {
|
|
327
405
|
[mode]: true,
|
|
328
406
|
'accordion-expanding': this.state === 8 /* AccordionState.Expanding */,
|
|
329
407
|
'accordion-expanded': this.state === 4 /* AccordionState.Expanded */,
|
|
@@ -334,7 +412,7 @@ export class Accordion {
|
|
|
334
412
|
'accordion-disabled': disabled,
|
|
335
413
|
'accordion-readonly': readonly,
|
|
336
414
|
'accordion-animated': this.shouldAnimate(),
|
|
337
|
-
} }, h("div", { key: '
|
|
415
|
+
} }, h("div", { key: 'cab40d5bcf3c93fd78e70b6d3906a541e725837d', onClick: () => this.toggleExpanded(), id: "header", part: headerPart, "aria-controls": "content", ref: (headerEl) => (this.headerEl = headerEl) }, h("slot", { key: '672bc7fb3f9e18076b41e20fc9eaeab7cafcf3a2', name: "header" })), h("div", { key: 'fd777ca5b4ab04aa4f44c339d58c8cd987c52bcb', id: "content", part: contentPart, role: "region", "aria-labelledby": "header", ref: (contentEl) => (this.contentEl = contentEl) }, h("div", { key: '0aad70a71e2cd2c16b2e98fa0bdd40421d95fe16', id: "content-wrapper", ref: (contentElWrapper) => (this.contentElWrapper = contentElWrapper) }, h("slot", { key: 'd630e10ac7c56b4dbf943b523f26759b83aead55', name: "content" })))));
|
|
338
416
|
}
|
|
339
417
|
static get is() { return "ion-accordion"; }
|
|
340
418
|
static get encapsulation() { return "shadow"; }
|
|
@@ -459,7 +537,8 @@ export class Accordion {
|
|
|
459
537
|
return {
|
|
460
538
|
"state": {},
|
|
461
539
|
"isNext": {},
|
|
462
|
-
"isPrevious": {}
|
|
540
|
+
"isPrevious": {},
|
|
541
|
+
"hasInteracted": {}
|
|
463
542
|
};
|
|
464
543
|
}
|
|
465
544
|
static get elementRef() { return "el"; }
|
|
@@ -216,11 +216,7 @@ export class Button {
|
|
|
216
216
|
target,
|
|
217
217
|
};
|
|
218
218
|
let fill = this.fill;
|
|
219
|
-
|
|
220
|
-
* We check both undefined and null to
|
|
221
|
-
* work around https://github.com/ionic-team/stencil/issues/3586.
|
|
222
|
-
*/
|
|
223
|
-
if (fill == null) {
|
|
219
|
+
if (fill === undefined) {
|
|
224
220
|
fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
|
|
225
221
|
}
|
|
226
222
|
/**
|
|
@@ -233,7 +229,7 @@ export class Button {
|
|
|
233
229
|
{
|
|
234
230
|
type !== 'button' && this.renderHiddenButton();
|
|
235
231
|
}
|
|
236
|
-
return (h(Host, { key: '
|
|
232
|
+
return (h(Host, { key: 'ed82ea53705523f9afc5f1a9addff44cc6424f27', onClick: this.handleClick, "aria-disabled": disabled ? 'true' : null, class: createColorClasses(color, {
|
|
237
233
|
[mode]: true,
|
|
238
234
|
[buttonType]: true,
|
|
239
235
|
[`${buttonType}-${expand}`]: expand !== undefined,
|
|
@@ -248,7 +244,7 @@ export class Button {
|
|
|
248
244
|
'button-disabled': disabled,
|
|
249
245
|
'ion-activatable': true,
|
|
250
246
|
'ion-focusable': true,
|
|
251
|
-
}) }, h(TagType, Object.assign({ key: '
|
|
247
|
+
}) }, h(TagType, Object.assign({ key: 'fadec13053469dd0405bbbc61b70ced568aa4826' }, attrs, { class: "button-native", part: "native", disabled: disabled, onFocus: this.onFocus, onBlur: this.onBlur }, inheritedAttributes), h("span", { key: '6bf0e5144fb1148002e88038522402b789689d2c', class: "button-inner" }, h("slot", { key: '25da0ca155cfa9e2754842c34f4fd09f576ac2d2', name: "icon-only", onSlotchange: this.slotChanged }), h("slot", { key: '51414065bb11953ec9d818f8d9353589bc9072c5', name: "start" }), h("slot", { key: 'c9b5f8842aeabd20628df2f4600f1257ea913d8d' }), h("slot", { key: '478dd3671c7be1909fc84e672f0fa8dfe6082263', name: "end" })), mode === 'md' && h("ion-ripple-effect", { key: 'e1d55f85a55144d743f58a5914cd116cb065fa8c', type: this.rippleType }))));
|
|
252
248
|
}
|
|
253
249
|
static get is() { return "ion-button"; }
|
|
254
250
|
static get encapsulation() { return "shadow"; }
|
|
@@ -77,7 +77,6 @@ export class Checkbox {
|
|
|
77
77
|
};
|
|
78
78
|
this.toggleChecked = (ev) => {
|
|
79
79
|
ev.preventDefault();
|
|
80
|
-
this.setFocus();
|
|
81
80
|
this.setChecked(!this.checked);
|
|
82
81
|
this.indeterminate = false;
|
|
83
82
|
};
|
|
@@ -114,9 +113,7 @@ export class Checkbox {
|
|
|
114
113
|
}
|
|
115
114
|
/** @internal */
|
|
116
115
|
async setFocus() {
|
|
117
|
-
|
|
118
|
-
this.focusEl.focus();
|
|
119
|
-
}
|
|
116
|
+
this.el.focus();
|
|
120
117
|
}
|
|
121
118
|
getHintTextID() {
|
|
122
119
|
const { el, helperText, errorText, helperTextId, errorTextId } = this;
|
|
@@ -152,7 +149,7 @@ export class Checkbox {
|
|
|
152
149
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
153
150
|
// The host element must have a checkbox role to ensure proper VoiceOver
|
|
154
151
|
// support in Safari for accessibility.
|
|
155
|
-
return (h(Host, { key: '
|
|
152
|
+
return (h(Host, { key: 'ee2e02d28f9d15a1ec746609f7e9559444f621e5', role: "checkbox", "aria-checked": indeterminate ? 'mixed' : `${checked}`, "aria-describedby": this.getHintTextID(), "aria-invalid": this.getHintTextID() === this.errorTextId, "aria-labelledby": hasLabelContent ? this.inputLabelId : null, "aria-label": inheritedAttributes['aria-label'] || null, "aria-disabled": disabled ? 'true' : null, tabindex: disabled ? undefined : 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, onBlur: this.onBlur, onClick: this.onClick, class: createColorClasses(color, {
|
|
156
153
|
[mode]: true,
|
|
157
154
|
'in-item': hostContext('ion-item', el),
|
|
158
155
|
'checkbox-checked': checked,
|
|
@@ -162,10 +159,10 @@ export class Checkbox {
|
|
|
162
159
|
[`checkbox-justify-${justify}`]: justify !== undefined,
|
|
163
160
|
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
|
|
164
161
|
[`checkbox-label-placement-${labelPlacement}`]: true,
|
|
165
|
-
})
|
|
162
|
+
}) }, h("label", { key: '84d4c33da0348dc65ad36fb0fafd48be366dcf3b', class: "checkbox-wrapper", htmlFor: inputId }, h("input", Object.assign({ key: '427db69a3ab8a17aa0867519c90f585b8930406b', type: "checkbox", checked: checked ? true : undefined, disabled: disabled, id: inputId, onChange: this.toggleChecked, required: required }, inheritedAttributes)), h("div", { key: '9dda7024b3a4f1ee55351f783f9a10f9b4ad0d12', class: {
|
|
166
163
|
'label-text-wrapper': true,
|
|
167
164
|
'label-text-wrapper-hidden': !hasLabelContent,
|
|
168
|
-
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: '
|
|
165
|
+
}, part: "label", id: this.inputLabelId, onClick: this.onDivLabelClick }, h("slot", { key: 'f9d1d545ffd4164b650808241b51ea1bedc6a42c' }), this.renderHintText()), h("div", { key: 'a96d61ac324864228f14caa0e9f2c0d15418882e', class: "native-wrapper" }, h("svg", { key: '64ff3e4d87e190601811ef64323edec18d510cd1', class: "checkbox-icon", viewBox: "0 0 24 24", part: "container", "aria-hidden": "true" }, path)))));
|
|
169
166
|
}
|
|
170
167
|
getSVGPath(mode, indeterminate) {
|
|
171
168
|
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" }));
|
|
@@ -152,6 +152,15 @@ ion-header ion-toolbar:first-of-type {
|
|
|
152
152
|
--opacity-scale: inherit;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Override styles applied during the page transition to prevent
|
|
157
|
+
* header flickering.
|
|
158
|
+
*/
|
|
159
|
+
.header-collapse-fade.header-transitioning ion-toolbar {
|
|
160
|
+
--background: transparent;
|
|
161
|
+
--border-style: none;
|
|
162
|
+
}
|
|
163
|
+
|
|
155
164
|
.header-collapse-condense {
|
|
156
165
|
z-index: 9;
|
|
157
166
|
}
|
|
@@ -175,7 +184,6 @@ ion-header ion-toolbar:first-of-type {
|
|
|
175
184
|
* since it needs to blend in with the header above it.
|
|
176
185
|
*/
|
|
177
186
|
.header-collapse-condense ion-toolbar {
|
|
178
|
-
--background: var(--ion-background-color, #fff);
|
|
179
187
|
z-index: 0;
|
|
180
188
|
}
|
|
181
189
|
|
|
@@ -201,6 +209,24 @@ ion-header ion-toolbar:first-of-type {
|
|
|
201
209
|
transition: all 0.2s ease-in-out;
|
|
202
210
|
}
|
|
203
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Large title toolbar should just use the content background
|
|
214
|
+
* since it needs to blend in with the header above it.
|
|
215
|
+
*/
|
|
216
|
+
.header-collapse-condense ion-toolbar,
|
|
217
|
+
.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar {
|
|
218
|
+
--background: var(--ion-background-color, #fff);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Override styles applied during the page transition to prevent
|
|
223
|
+
* header flickering.
|
|
224
|
+
*/
|
|
225
|
+
.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar {
|
|
226
|
+
--border-style: none;
|
|
227
|
+
--opacity-scale: 1;
|
|
228
|
+
}
|
|
229
|
+
|
|
204
230
|
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,
|
|
205
231
|
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
|
|
206
232
|
opacity: 0;
|
|
@@ -6,7 +6,7 @@ import { findIonContent, getScrollElement, printIonContentErrorMsg } from "../..
|
|
|
6
6
|
import { inheritAriaAttributes } from "../../utils/helpers";
|
|
7
7
|
import { hostContext } from "../../utils/theme";
|
|
8
8
|
import { getIonMode } from "../../global/ionic-global";
|
|
9
|
-
import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity, } from "./header.utils";
|
|
9
|
+
import { cloneElement, createHeaderIndex, handleContentScroll, handleHeaderFade, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity, getRoleType, } from "./header.utils";
|
|
10
10
|
/**
|
|
11
11
|
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
|
12
12
|
*/
|
|
@@ -145,16 +145,17 @@ export class Header {
|
|
|
145
145
|
const { translucent, inheritedAttributes } = this;
|
|
146
146
|
const mode = getIonMode(this);
|
|
147
147
|
const collapse = this.collapse || 'none';
|
|
148
|
+
const isCondensed = collapse === 'condense';
|
|
148
149
|
// banner role must be at top level, so remove role if inside a menu
|
|
149
|
-
const roleType = hostContext('ion-menu', this.el)
|
|
150
|
-
return (h(Host, Object.assign({ key: '
|
|
150
|
+
const roleType = getRoleType(hostContext('ion-menu', this.el), isCondensed, mode);
|
|
151
|
+
return (h(Host, Object.assign({ key: '863c4568cd7b8c0ec55109f193bbbaed68a1346e', role: roleType, class: {
|
|
151
152
|
[mode]: true,
|
|
152
153
|
// Used internally for styling
|
|
153
154
|
[`header-${mode}`]: true,
|
|
154
155
|
[`header-translucent`]: this.translucent,
|
|
155
156
|
[`header-collapse-${collapse}`]: true,
|
|
156
157
|
[`header-translucent-${mode}`]: this.translucent,
|
|
157
|
-
} }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '
|
|
158
|
+
} }, inheritedAttributes), mode === 'ios' && translucent && h("div", { key: '25c3bdce328b0b35607d154c8b8374679313d881', class: "header-background" }), h("slot", { key: 'b44fab0a9be7920b9650da26117c783e751e1702' })));
|
|
158
159
|
}
|
|
159
160
|
static get is() { return "ion-header"; }
|
|
160
161
|
static get originalStyleUrls() {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { readTask, writeTask } from "@stencil/core";
|
|
5
5
|
import { clamp } from "../../utils/helpers";
|
|
6
6
|
const TRANSITION = 'all 0.2s ease-in-out';
|
|
7
|
+
const ROLE_NONE = 'none';
|
|
8
|
+
const ROLE_BANNER = 'banner';
|
|
7
9
|
export const cloneElement = (tagName) => {
|
|
8
10
|
const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
|
|
9
11
|
if (getCachedEl !== null) {
|
|
@@ -130,6 +132,7 @@ export const setHeaderActive = (headerIndex, active = true) => {
|
|
|
130
132
|
const toolbars = headerIndex.toolbars;
|
|
131
133
|
const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
|
|
132
134
|
if (active) {
|
|
135
|
+
headerEl.setAttribute('role', ROLE_BANNER);
|
|
133
136
|
headerEl.classList.remove('header-collapse-condense-inactive');
|
|
134
137
|
ionTitles.forEach((ionTitle) => {
|
|
135
138
|
if (ionTitle) {
|
|
@@ -138,6 +141,16 @@ export const setHeaderActive = (headerIndex, active = true) => {
|
|
|
138
141
|
});
|
|
139
142
|
}
|
|
140
143
|
else {
|
|
144
|
+
/**
|
|
145
|
+
* There can only be one banner landmark per page.
|
|
146
|
+
* By default, all ion-headers have the banner role.
|
|
147
|
+
* This causes an accessibility issue when using a
|
|
148
|
+
* condensed header since there are two ion-headers
|
|
149
|
+
* on the page at once (active and inactive).
|
|
150
|
+
* To solve this, the role needs to be toggled
|
|
151
|
+
* based on which header is active.
|
|
152
|
+
*/
|
|
153
|
+
headerEl.setAttribute('role', ROLE_NONE);
|
|
141
154
|
headerEl.classList.add('header-collapse-condense-inactive');
|
|
142
155
|
/**
|
|
143
156
|
* The small title should only be accessed by screen readers
|
|
@@ -197,3 +210,27 @@ export const handleHeaderFade = (scrollEl, baseEl, condenseHeader) => {
|
|
|
197
210
|
});
|
|
198
211
|
});
|
|
199
212
|
};
|
|
213
|
+
/**
|
|
214
|
+
* Get the role type for the ion-header.
|
|
215
|
+
*
|
|
216
|
+
* @param isInsideMenu If ion-header is inside ion-menu.
|
|
217
|
+
* @param isCondensed If ion-header has collapse="condense".
|
|
218
|
+
* @param mode The current mode.
|
|
219
|
+
* @returns 'none' if inside ion-menu or if condensed in md
|
|
220
|
+
* mode, otherwise 'banner'.
|
|
221
|
+
*/
|
|
222
|
+
export const getRoleType = (isInsideMenu, isCondensed, mode) => {
|
|
223
|
+
// If the header is inside a menu, it should not have the banner role.
|
|
224
|
+
if (isInsideMenu) {
|
|
225
|
+
return ROLE_NONE;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Only apply role="none" to `md` mode condensed headers
|
|
229
|
+
* since the large header is never shown.
|
|
230
|
+
*/
|
|
231
|
+
if (isCondensed && mode === 'md') {
|
|
232
|
+
return ROLE_NONE;
|
|
233
|
+
}
|
|
234
|
+
// Default to banner role.
|
|
235
|
+
return ROLE_BANNER;
|
|
236
|
+
};
|
|
@@ -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,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
766
|
getHintTextID() {
|
|
721
|
-
const {
|
|
722
|
-
if (
|
|
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() {
|