@ionic/core 8.7.7-nightly.20251014 → 8.7.7
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/header.js +42 -4
- package/components/index2.js +74 -3
- package/components/ion-input.js +6 -14
- package/components/ion-select.js +58 -10
- package/components/ion-textarea.js +5 -13
- 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-app_8.cjs.entry.js +43 -5
- package/dist/cjs/ion-button_2.cjs.entry.js +3 -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/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/button/button.js +3 -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/utils/forms/index.js +1 -0
- package/dist/collection/utils/forms/validity.js +15 -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-app_8.entry.js +43 -5
- package/dist/esm/ion-button_2.entry.js +3 -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/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-4c85d268.entry.js +4 -0
- package/dist/ionic/p-4cc26913.entry.js +4 -0
- package/dist/ionic/p-8bdfc8f6.entry.js +4 -0
- 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-c59314fd.entry.js → p-a80f1b04.entry.js} +1 -1
- package/dist/ionic/{p-c85c40ee.entry.js → p-dbbe606a.entry.js} +1 -1
- package/dist/ionic/{p-de7b5fa3.entry.js → p-e16b69e1.entry.js} +1 -1
- package/dist/ionic/p-f65f9308.entry.js +4 -0
- package/dist/types/components/header/header.utils.d.ts +10 -0
- package/dist/types/components/input/input.d.ts +0 -4
- package/dist/types/components/select/select.d.ts +6 -0
- package/dist/types/components/textarea/textarea.d.ts +0 -4
- 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 +161 -45
- package/hydrate/index.mjs +161 -45
- package/package.json +2 -2
- package/dist/ionic/p-1c8a476d.entry.js +0 -4
- package/dist/ionic/p-49f0149c.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-913a7c1e.entry.js +0 -4
- package/dist/ionic/p-CMhMiYSX.js +0 -4
|
@@ -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() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { Build, Host, forceUpdate, h, writeTask, } from "@stencil/core";
|
|
5
|
-
import { createNotchController } from "../../utils/forms/index";
|
|
5
|
+
import { createNotchController, checkInvalidState } from "../../utils/forms/index";
|
|
6
6
|
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
|
|
7
7
|
import { createSlotMutationController } from "../../utils/slot-mutation-controller";
|
|
8
8
|
import { createColorClasses, hostContext } from "../../utils/theme";
|
|
@@ -187,14 +187,6 @@ export class Textarea {
|
|
|
187
187
|
this.el.click();
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Checks if the textarea is in an invalid state based on Ionic validation classes
|
|
192
|
-
*/
|
|
193
|
-
checkValidationState() {
|
|
194
|
-
const hasIonTouched = this.el.classList.contains('ion-touched');
|
|
195
|
-
const hasIonInvalid = this.el.classList.contains('ion-invalid');
|
|
196
|
-
return hasIonTouched && hasIonInvalid;
|
|
197
|
-
}
|
|
198
190
|
connectedCallback() {
|
|
199
191
|
const { el } = this;
|
|
200
192
|
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
|
|
@@ -202,7 +194,7 @@ export class Textarea {
|
|
|
202
194
|
// Watch for class changes to update validation state
|
|
203
195
|
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
204
196
|
this.validationObserver = new MutationObserver(() => {
|
|
205
|
-
const newIsInvalid = this.
|
|
197
|
+
const newIsInvalid = checkInvalidState(this.el);
|
|
206
198
|
if (this.isInvalid !== newIsInvalid) {
|
|
207
199
|
this.isInvalid = newIsInvalid;
|
|
208
200
|
// Force a re-render to update aria-describedby immediately
|
|
@@ -215,7 +207,7 @@ export class Textarea {
|
|
|
215
207
|
});
|
|
216
208
|
}
|
|
217
209
|
// Always set initial state
|
|
218
|
-
this.isInvalid = this.
|
|
210
|
+
this.isInvalid = checkInvalidState(this.el);
|
|
219
211
|
this.debounceChanged();
|
|
220
212
|
if (Build.isBrowser) {
|
|
221
213
|
document.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
|
@@ -479,7 +471,7 @@ export class Textarea {
|
|
|
479
471
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
480
472
|
*/
|
|
481
473
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
|
|
482
|
-
return (h(Host, { key: '
|
|
474
|
+
return (h(Host, { key: 'a70a62d7aae3831a50acd74f60b930925ada1326', class: createColorClasses(this.color, {
|
|
483
475
|
[mode]: true,
|
|
484
476
|
'has-value': hasValue,
|
|
485
477
|
'has-focus': hasFocus,
|
|
@@ -488,7 +480,7 @@ export class Textarea {
|
|
|
488
480
|
[`textarea-shape-${shape}`]: shape !== undefined,
|
|
489
481
|
[`textarea-label-placement-${labelPlacement}`]: true,
|
|
490
482
|
'textarea-disabled': disabled,
|
|
491
|
-
}) }, h("label", { key: '
|
|
483
|
+
}) }, h("label", { key: '8a2dd59a60f7469df84018eb0ede3a9ec3862703', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: '1bfc368236e3da7a225a45118c27fbfc1fe5fa46', class: "textarea-wrapper-inner" }, h("div", { key: '215cbb2635ff52e31a8973376989b85e7245d40f', class: "start-slot-wrapper" }, h("slot", { key: '9f6b461cdee9d629deb695d2bea054ece2f32305', name: "start" })), h("div", { key: 'c1af35a2d5bc452bebe0b22a26d15ff52b4e9fc8', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", Object.assign({ key: '69a69b3cf0932baafbe37e6e846f1a571608d3f2', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined }, this.inheritedAttributes), value)), h("div", { key: 'c053ea8b865d0e29763aed2e4939cc9c9e374c15', class: "end-slot-wrapper" }, h("slot", { key: '930aa641833b0df54b9ea10368fc2f46d5f491f6', name: "end" }))), shouldRenderHighlight && h("div", { key: '8d12597d15f5f429d80e8272ea99e64ed924e482', class: "textarea-highlight" })), this.renderBottomContent()));
|
|
492
484
|
}
|
|
493
485
|
static get is() { return "ion-textarea"; }
|
|
494
486
|
static get encapsulation() { return "scoped"; }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the form element is in an invalid state based on
|
|
6
|
+
* Ionic validation classes.
|
|
7
|
+
*
|
|
8
|
+
* @param el The form element to check.
|
|
9
|
+
* @returns `true` if the element is invalid, `false` otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export const checkInvalidState = (el) => {
|
|
12
|
+
const hasIonTouched = el.classList.contains('ion-touched');
|
|
13
|
+
const hasIonInvalid = el.classList.contains('ion-invalid');
|
|
14
|
+
return hasIonTouched && hasIonInvalid;
|
|
15
|
+
};
|
|
@@ -10,11 +10,22 @@ const iosTransitionAnimation = () => import('./ios.transition');
|
|
|
10
10
|
const mdTransitionAnimation = () => import('./md.transition');
|
|
11
11
|
const focusController = createFocusController();
|
|
12
12
|
// TODO(FW-2832): types
|
|
13
|
+
/**
|
|
14
|
+
* Executes the main page transition.
|
|
15
|
+
* It also manages the lifecycle of header visibility (if any)
|
|
16
|
+
* to prevent visual flickering in iOS. The flickering only
|
|
17
|
+
* occurs for a condensed header that is placed above the content.
|
|
18
|
+
*
|
|
19
|
+
* @param opts Options for the transition.
|
|
20
|
+
* @returns A promise that resolves when the transition is complete.
|
|
21
|
+
*/
|
|
13
22
|
export const transition = (opts) => {
|
|
14
23
|
return new Promise((resolve, reject) => {
|
|
15
24
|
writeTask(() => {
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
const transitioningInactiveHeader = getIosIonHeader(opts);
|
|
26
|
+
beforeTransition(opts, transitioningInactiveHeader);
|
|
27
|
+
runTransition(opts)
|
|
28
|
+
.then((result) => {
|
|
18
29
|
if (result.animation) {
|
|
19
30
|
result.animation.destroy();
|
|
20
31
|
}
|
|
@@ -23,15 +34,21 @@ export const transition = (opts) => {
|
|
|
23
34
|
}, (error) => {
|
|
24
35
|
afterTransition(opts);
|
|
25
36
|
reject(error);
|
|
37
|
+
})
|
|
38
|
+
.finally(() => {
|
|
39
|
+
// Ensure that the header is restored to its original state.
|
|
40
|
+
setHeaderTransitionClass(transitioningInactiveHeader, false);
|
|
26
41
|
});
|
|
27
42
|
});
|
|
28
43
|
});
|
|
29
44
|
};
|
|
30
|
-
const beforeTransition = (opts) => {
|
|
45
|
+
const beforeTransition = (opts, transitioningInactiveHeader) => {
|
|
31
46
|
const enteringEl = opts.enteringEl;
|
|
32
47
|
const leavingEl = opts.leavingEl;
|
|
33
48
|
focusController.saveViewFocus(leavingEl);
|
|
34
49
|
setZIndex(enteringEl, leavingEl, opts.direction);
|
|
50
|
+
// Prevent flickering of the header by adding a class.
|
|
51
|
+
setHeaderTransitionClass(transitioningInactiveHeader, true);
|
|
35
52
|
if (opts.showGoBack) {
|
|
36
53
|
enteringEl.classList.add('can-go-back');
|
|
37
54
|
}
|
|
@@ -220,6 +237,39 @@ const setZIndex = (enteringEl, leavingEl, direction) => {
|
|
|
220
237
|
leavingEl.style.zIndex = '100';
|
|
221
238
|
}
|
|
222
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* Add a class to ensure that the header (if any)
|
|
242
|
+
* does not flicker during the transition. By adding the
|
|
243
|
+
* transitioning class, we ensure that the header has
|
|
244
|
+
* the necessary styles to prevent the following flickers:
|
|
245
|
+
* 1. When entering a page with a condensed header, the
|
|
246
|
+
* header should never be visible. However,
|
|
247
|
+
* it briefly renders the background color while
|
|
248
|
+
* the transition is occurring.
|
|
249
|
+
* 2. When leaving a page with a condensed header, the
|
|
250
|
+
* header has an opacity of 0 and the pages
|
|
251
|
+
* have a z-index which causes the entering page to
|
|
252
|
+
* briefly show it's content underneath the leaving page.
|
|
253
|
+
* 3. When entering a page or leaving a page with a fade
|
|
254
|
+
* header, the header should not have a background color.
|
|
255
|
+
* However, it briefly shows the background color while
|
|
256
|
+
* the transition is occurring.
|
|
257
|
+
*
|
|
258
|
+
* @param header The header element to modify.
|
|
259
|
+
* @param isTransitioning Whether the transition is occurring.
|
|
260
|
+
*/
|
|
261
|
+
const setHeaderTransitionClass = (header, isTransitioning) => {
|
|
262
|
+
if (!header) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const transitionClass = 'header-transitioning';
|
|
266
|
+
if (isTransitioning) {
|
|
267
|
+
header.classList.add(transitionClass);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
header.classList.remove(transitionClass);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
223
273
|
export const getIonPageElement = (element) => {
|
|
224
274
|
if (element.classList.contains('ion-page')) {
|
|
225
275
|
return element;
|
|
@@ -231,3 +281,24 @@ export const getIonPageElement = (element) => {
|
|
|
231
281
|
// idk, return the original element so at least something animates and we don't have a null pointer
|
|
232
282
|
return element;
|
|
233
283
|
};
|
|
284
|
+
/**
|
|
285
|
+
* Retrieves the ion-header element from a page based on the
|
|
286
|
+
* direction of the transition.
|
|
287
|
+
*
|
|
288
|
+
* @param opts Options for the transition.
|
|
289
|
+
* @returns The ion-header element or null if not found or not in 'ios' mode.
|
|
290
|
+
*/
|
|
291
|
+
const getIosIonHeader = (opts) => {
|
|
292
|
+
const enteringEl = opts.enteringEl;
|
|
293
|
+
const leavingEl = opts.leavingEl;
|
|
294
|
+
const direction = opts.direction;
|
|
295
|
+
const mode = opts.mode;
|
|
296
|
+
if (mode !== 'ios') {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
const element = direction === 'back' ? leavingEl : enteringEl;
|
|
300
|
+
if (!element) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
return element.querySelector('ion-header');
|
|
304
|
+
};
|
package/dist/docs.json
CHANGED
|
@@ -118,15 +118,26 @@ const createFocusController = () => {
|
|
|
118
118
|
};
|
|
119
119
|
const LAST_FOCUS = 'ion-last-focus';
|
|
120
120
|
|
|
121
|
-
const iosTransitionAnimation = () => import('./ios.transition-
|
|
122
|
-
const mdTransitionAnimation = () => import('./md.transition-
|
|
121
|
+
const iosTransitionAnimation = () => import('./ios.transition-BDzw0_Hm.js');
|
|
122
|
+
const mdTransitionAnimation = () => import('./md.transition-BzDYi3qq.js');
|
|
123
123
|
const focusController = createFocusController();
|
|
124
124
|
// TODO(FW-2832): types
|
|
125
|
+
/**
|
|
126
|
+
* Executes the main page transition.
|
|
127
|
+
* It also manages the lifecycle of header visibility (if any)
|
|
128
|
+
* to prevent visual flickering in iOS. The flickering only
|
|
129
|
+
* occurs for a condensed header that is placed above the content.
|
|
130
|
+
*
|
|
131
|
+
* @param opts Options for the transition.
|
|
132
|
+
* @returns A promise that resolves when the transition is complete.
|
|
133
|
+
*/
|
|
125
134
|
const transition = (opts) => {
|
|
126
135
|
return new Promise((resolve, reject) => {
|
|
127
136
|
writeTask(() => {
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
const transitioningInactiveHeader = getIosIonHeader(opts);
|
|
138
|
+
beforeTransition(opts, transitioningInactiveHeader);
|
|
139
|
+
runTransition(opts)
|
|
140
|
+
.then((result) => {
|
|
130
141
|
if (result.animation) {
|
|
131
142
|
result.animation.destroy();
|
|
132
143
|
}
|
|
@@ -135,15 +146,21 @@ const transition = (opts) => {
|
|
|
135
146
|
}, (error) => {
|
|
136
147
|
afterTransition(opts);
|
|
137
148
|
reject(error);
|
|
149
|
+
})
|
|
150
|
+
.finally(() => {
|
|
151
|
+
// Ensure that the header is restored to its original state.
|
|
152
|
+
setHeaderTransitionClass(transitioningInactiveHeader, false);
|
|
138
153
|
});
|
|
139
154
|
});
|
|
140
155
|
});
|
|
141
156
|
};
|
|
142
|
-
const beforeTransition = (opts) => {
|
|
157
|
+
const beforeTransition = (opts, transitioningInactiveHeader) => {
|
|
143
158
|
const enteringEl = opts.enteringEl;
|
|
144
159
|
const leavingEl = opts.leavingEl;
|
|
145
160
|
focusController.saveViewFocus(leavingEl);
|
|
146
161
|
setZIndex(enteringEl, leavingEl, opts.direction);
|
|
162
|
+
// Prevent flickering of the header by adding a class.
|
|
163
|
+
setHeaderTransitionClass(transitioningInactiveHeader, true);
|
|
147
164
|
if (opts.showGoBack) {
|
|
148
165
|
enteringEl.classList.add('can-go-back');
|
|
149
166
|
}
|
|
@@ -332,6 +349,39 @@ const setZIndex = (enteringEl, leavingEl, direction) => {
|
|
|
332
349
|
leavingEl.style.zIndex = '100';
|
|
333
350
|
}
|
|
334
351
|
};
|
|
352
|
+
/**
|
|
353
|
+
* Add a class to ensure that the header (if any)
|
|
354
|
+
* does not flicker during the transition. By adding the
|
|
355
|
+
* transitioning class, we ensure that the header has
|
|
356
|
+
* the necessary styles to prevent the following flickers:
|
|
357
|
+
* 1. When entering a page with a condensed header, the
|
|
358
|
+
* header should never be visible. However,
|
|
359
|
+
* it briefly renders the background color while
|
|
360
|
+
* the transition is occurring.
|
|
361
|
+
* 2. When leaving a page with a condensed header, the
|
|
362
|
+
* header has an opacity of 0 and the pages
|
|
363
|
+
* have a z-index which causes the entering page to
|
|
364
|
+
* briefly show it's content underneath the leaving page.
|
|
365
|
+
* 3. When entering a page or leaving a page with a fade
|
|
366
|
+
* header, the header should not have a background color.
|
|
367
|
+
* However, it briefly shows the background color while
|
|
368
|
+
* the transition is occurring.
|
|
369
|
+
*
|
|
370
|
+
* @param header The header element to modify.
|
|
371
|
+
* @param isTransitioning Whether the transition is occurring.
|
|
372
|
+
*/
|
|
373
|
+
const setHeaderTransitionClass = (header, isTransitioning) => {
|
|
374
|
+
if (!header) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const transitionClass = 'header-transitioning';
|
|
378
|
+
if (isTransitioning) {
|
|
379
|
+
header.classList.add(transitionClass);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
header.classList.remove(transitionClass);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
335
385
|
const getIonPageElement = (element) => {
|
|
336
386
|
if (element.classList.contains('ion-page')) {
|
|
337
387
|
return element;
|
|
@@ -343,5 +393,26 @@ const getIonPageElement = (element) => {
|
|
|
343
393
|
// idk, return the original element so at least something animates and we don't have a null pointer
|
|
344
394
|
return element;
|
|
345
395
|
};
|
|
396
|
+
/**
|
|
397
|
+
* Retrieves the ion-header element from a page based on the
|
|
398
|
+
* direction of the transition.
|
|
399
|
+
*
|
|
400
|
+
* @param opts Options for the transition.
|
|
401
|
+
* @returns The ion-header element or null if not found or not in 'ios' mode.
|
|
402
|
+
*/
|
|
403
|
+
const getIosIonHeader = (opts) => {
|
|
404
|
+
const enteringEl = opts.enteringEl;
|
|
405
|
+
const leavingEl = opts.leavingEl;
|
|
406
|
+
const direction = opts.direction;
|
|
407
|
+
const mode = opts.mode;
|
|
408
|
+
if (mode !== 'ios') {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
const element = direction === 'back' ? leavingEl : enteringEl;
|
|
412
|
+
if (!element) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return element.querySelector('ion-header');
|
|
416
|
+
};
|
|
346
417
|
|
|
347
418
|
export { LIFECYCLE_WILL_ENTER as L, LIFECYCLE_DID_ENTER as a, LIFECYCLE_WILL_LEAVE as b, LIFECYCLE_DID_LEAVE as c, LIFECYCLE_WILL_UNLOAD as d, deepReady as e, getIonPageElement as g, lifecycle as l, setPageHidden as s, transition as t, waitForMount as w };
|
package/dist/esm/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
export { c as createAnimation } from './animation-Dt8bGnA-.js';
|
|
5
|
-
export { a as LIFECYCLE_DID_ENTER, c as LIFECYCLE_DID_LEAVE, L as LIFECYCLE_WILL_ENTER, b as LIFECYCLE_WILL_LEAVE, d as LIFECYCLE_WILL_UNLOAD, g as getIonPageElement } from './index-
|
|
6
|
-
export { iosTransitionAnimation } from './ios.transition-
|
|
7
|
-
export { mdTransitionAnimation } from './md.transition-
|
|
5
|
+
export { a as LIFECYCLE_DID_ENTER, c as LIFECYCLE_DID_LEAVE, L as LIFECYCLE_WILL_ENTER, b as LIFECYCLE_WILL_LEAVE, d as LIFECYCLE_WILL_UNLOAD, g as getIonPageElement } from './index-r2D9DEro.js';
|
|
6
|
+
export { iosTransitionAnimation } from './ios.transition-BDzw0_Hm.js';
|
|
7
|
+
export { mdTransitionAnimation } from './md.transition-BzDYi3qq.js';
|
|
8
8
|
export { g as getTimeGivenProgression } from './cubic-bezier-hHmYLOfE.js';
|
|
9
9
|
export { createGesture } from './index-CfgBF1SE.js';
|
|
10
10
|
export { g as getPlatforms, i as initialize, a as isPlatform } from './ionic-global-CDrldh-5.js';
|