Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dafb0b9005 | ||
|
|
6b164c8f44 | ||
|
|
5546e95288 | ||
|
|
52d6e09b27 | ||
|
|
a6e734cf1a | ||
|
|
ed77d81eaa | ||
|
|
e4957fa4d2 | ||
|
|
d9f8c1352f | ||
|
|
41da4c3565 | ||
|
|
0b549835b6 | ||
|
|
0bbb9f37b4 | ||
|
|
166e43554e | ||
|
|
621333d927 | ||
|
|
6cf454f7c4 | ||
|
|
295fa00527 | ||
|
|
353159149a | ||
|
|
ac4ea3232b | ||
|
|
efd3e0fd2b |
@@ -403,6 +403,7 @@ ion-checkbox,prop,justify,"end" | "space-between" | "start" | undefined,undefine
|
||||
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
|
||||
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-checkbox,prop,name,string,this.inputId,false,false
|
||||
ion-checkbox,prop,required,boolean,false,false,false
|
||||
ion-checkbox,prop,value,any,'on',false,false
|
||||
ion-checkbox,event,ionBlur,void,true
|
||||
ion-checkbox,event,ionChange,CheckboxChangeEventDetail<any>,true
|
||||
@@ -1074,6 +1075,7 @@ ion-modal,prop,backdropDismiss,boolean,true,false,false
|
||||
ion-modal,prop,breakpoints,number[] | undefined,undefined,false,false
|
||||
ion-modal,prop,canDismiss,((data?: any, role?: string | undefined) => Promise<boolean>) | boolean,true,false,false
|
||||
ion-modal,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
|
||||
ion-modal,prop,expandToScroll,boolean,true,false,false
|
||||
ion-modal,prop,focusTrap,boolean,true,false,false
|
||||
ion-modal,prop,handle,boolean | undefined,undefined,false,false
|
||||
ion-modal,prop,handleBehavior,"cycle" | "none" | undefined,'none',false,false
|
||||
@@ -1631,6 +1633,7 @@ ion-select,prop,multiple,boolean,false,false,false
|
||||
ion-select,prop,name,string,this.inputId,false,false
|
||||
ion-select,prop,okText,string,'OK',false,false
|
||||
ion-select,prop,placeholder,string | undefined,undefined,false,false
|
||||
ion-select,prop,required,boolean,false,false,false
|
||||
ion-select,prop,selectedText,null | string | undefined,undefined,false,false
|
||||
ion-select,prop,shape,"round" | undefined,undefined,false,false
|
||||
ion-select,prop,toggleIcon,string | undefined,undefined,false,false
|
||||
@@ -1944,6 +1947,7 @@ ion-toggle,prop,justify,"end" | "space-between" | "start" | undefined,undefined,
|
||||
ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
|
||||
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-toggle,prop,name,string,this.inputId,false,false
|
||||
ion-toggle,prop,required,boolean,false,false,false
|
||||
ion-toggle,prop,value,null | string | undefined,'on',false,false
|
||||
ion-toggle,event,ionBlur,void,true
|
||||
ion-toggle,event,ionChange,ToggleChangeEventDetail<any>,true
|
||||
@@ -2000,4 +2004,7 @@ ion-toolbar,css-prop,--padding-end,md
|
||||
ion-toolbar,css-prop,--padding-start,ios
|
||||
ion-toolbar,css-prop,--padding-start,md
|
||||
ion-toolbar,css-prop,--padding-top,ios
|
||||
ion-toolbar,css-prop,--padding-top,md
|
||||
ion-toolbar,css-prop,--padding-top,md
|
||||
ion-toolbar,part,background
|
||||
ion-toolbar,part,container
|
||||
ion-toolbar,part,content
|
||||
2
core/package-lock.json
generated
@@ -18162,4 +18162,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
core/src/components.d.ts
vendored
@@ -643,6 +643,10 @@ export namespace Components {
|
||||
* The name of the control, which is submitted with the form data.
|
||||
*/
|
||||
"name": string;
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
|
||||
*/
|
||||
"required": boolean;
|
||||
"setFocus": () => Promise<void>;
|
||||
/**
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
|
||||
@@ -1731,6 +1735,10 @@ export namespace Components {
|
||||
* Animation to use when the modal is presented.
|
||||
*/
|
||||
"enterAnimation"?: AnimationBuilder;
|
||||
/**
|
||||
* Controls whether scrolling or dragging within the sheet modal expands it to a larger breakpoint. This only takes effect when `breakpoints` and `initialBreakpoint` are set. If `true`, scrolling or dragging anywhere in the modal will first expand it to the next breakpoint. Once fully expanded, scrolling will affect the content. If `false`, scrolling will always affect the content, and the modal will only expand when dragging the header or handle.
|
||||
*/
|
||||
"expandToScroll": boolean;
|
||||
/**
|
||||
* If `true`, focus will not be allowed to move outside of this overlay. If `false`, focus will be allowed to move outside of the overlay. In most scenarios this property should remain set to `true`. Setting this property to `false` can cause severe accessibility issues as users relying on assistive technologies may be able to move focus into a confusing state. We recommend only setting this to `false` when absolutely necessary. Developers may want to consider disabling focus trapping if this overlay presents a non-Ionic overlay from a 3rd party library. Developers would disable focus trapping on the Ionic overlay when presenting the 3rd party overlay and then re-enable focus trapping when dismissing the 3rd party overlay and moving focus back to the Ionic overlay.
|
||||
*/
|
||||
@@ -2808,6 +2816,10 @@ export namespace Components {
|
||||
* The text to display when the select is empty.
|
||||
*/
|
||||
"placeholder"?: string;
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
|
||||
*/
|
||||
"required": boolean;
|
||||
/**
|
||||
* The text to display instead of the selected option's value.
|
||||
*/
|
||||
@@ -3280,6 +3292,10 @@ export namespace Components {
|
||||
* The name of the control, which is submitted with the form data.
|
||||
*/
|
||||
"name": string;
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
|
||||
*/
|
||||
"required": boolean;
|
||||
/**
|
||||
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
|
||||
*/
|
||||
@@ -5435,6 +5451,10 @@ declare namespace LocalJSX {
|
||||
* Emitted when the checkbox has focus.
|
||||
*/
|
||||
"onIonFocus"?: (event: IonCheckboxCustomEvent<void>) => void;
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
|
||||
*/
|
||||
"required"?: boolean;
|
||||
/**
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
|
||||
*/
|
||||
@@ -6532,6 +6552,10 @@ declare namespace LocalJSX {
|
||||
* Animation to use when the modal is presented.
|
||||
*/
|
||||
"enterAnimation"?: AnimationBuilder;
|
||||
/**
|
||||
* Controls whether scrolling or dragging within the sheet modal expands it to a larger breakpoint. This only takes effect when `breakpoints` and `initialBreakpoint` are set. If `true`, scrolling or dragging anywhere in the modal will first expand it to the next breakpoint. Once fully expanded, scrolling will affect the content. If `false`, scrolling will always affect the content, and the modal will only expand when dragging the header or handle.
|
||||
*/
|
||||
"expandToScroll"?: boolean;
|
||||
/**
|
||||
* If `true`, focus will not be allowed to move outside of this overlay. If `false`, focus will be allowed to move outside of the overlay. In most scenarios this property should remain set to `true`. Setting this property to `false` can cause severe accessibility issues as users relying on assistive technologies may be able to move focus into a confusing state. We recommend only setting this to `false` when absolutely necessary. Developers may want to consider disabling focus trapping if this overlay presents a non-Ionic overlay from a 3rd party library. Developers would disable focus trapping on the Ionic overlay when presenting the 3rd party overlay and then re-enable focus trapping when dismissing the 3rd party overlay and moving focus back to the Ionic overlay.
|
||||
*/
|
||||
@@ -7640,6 +7664,10 @@ declare namespace LocalJSX {
|
||||
* The text to display when the select is empty.
|
||||
*/
|
||||
"placeholder"?: string;
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
|
||||
*/
|
||||
"required"?: boolean;
|
||||
/**
|
||||
* The text to display instead of the selected option's value.
|
||||
*/
|
||||
@@ -8155,6 +8183,10 @@ declare namespace LocalJSX {
|
||||
* Emitted when the toggle has focus.
|
||||
*/
|
||||
"onIonFocus"?: (event: IonToggleCustomEvent<void>) => void;
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
|
||||
*/
|
||||
"required"?: boolean;
|
||||
/**
|
||||
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
|
||||
*/
|
||||
|
||||
@@ -98,6 +98,13 @@ export class Checkbox implements ComponentInterface {
|
||||
*/
|
||||
@Prop() alignment?: 'start' | 'center';
|
||||
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property
|
||||
* works only for accessibility purposes, it will not prevent the form from
|
||||
* submitting if the value is invalid.
|
||||
*/
|
||||
@Prop() required = false;
|
||||
|
||||
/**
|
||||
* Emitted when the checked property has changed as a result of a user action such as a click.
|
||||
*
|
||||
@@ -182,6 +189,7 @@ export class Checkbox implements ComponentInterface {
|
||||
name,
|
||||
value,
|
||||
alignment,
|
||||
required,
|
||||
} = this;
|
||||
const mode = getIonMode(this);
|
||||
const path = getSVGPath(mode, indeterminate);
|
||||
@@ -218,6 +226,7 @@ export class Checkbox implements ComponentInterface {
|
||||
onFocus={() => this.onFocus()}
|
||||
onBlur={() => this.onBlur()}
|
||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||
required={required}
|
||||
{...inheritedAttributes}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -54,3 +54,33 @@ describe('ion-checkbox: indeterminate', () => {
|
||||
expect(checkbox.getAttribute('aria-checked')).toBe('mixed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ion-checkbox: required', () => {
|
||||
it('should have a required attribute in inner input when true', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Checkbox],
|
||||
html: `
|
||||
<ion-checkbox required="true">Checkbox</ion-checkbox>
|
||||
`,
|
||||
});
|
||||
|
||||
const checkbox = page.body.querySelector('ion-checkbox')!;
|
||||
const nativeInput = checkbox.shadowRoot?.querySelector('input[type=checkbox]')!;
|
||||
|
||||
expect(nativeInput.hasAttribute('required')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not have a required attribute in inner input when false', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Checkbox],
|
||||
html: `
|
||||
<ion-checkbox required="false">Checkbox</ion-checkbox>
|
||||
`,
|
||||
});
|
||||
|
||||
const checkbox = page.body.querySelector('ion-checkbox')!;
|
||||
const nativeInput = checkbox.shadowRoot?.querySelector('input[type=checkbox]')!;
|
||||
|
||||
expect(nativeInput.hasAttribute('required')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -455,6 +455,7 @@ export class Content implements ComponentInterface {
|
||||
overscroll: forceOverscroll,
|
||||
[`content-${rtl}`]: true,
|
||||
})}
|
||||
tabIndex={'0'}
|
||||
style={{
|
||||
'--offset-top': `${this.cTop}px`,
|
||||
'--offset-bottom': `${this.cBottom}px`,
|
||||
|
||||
@@ -17,27 +17,78 @@ const createEnterAnimation = () => {
|
||||
|
||||
const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
|
||||
|
||||
return { backdropAnimation, wrapperAnimation };
|
||||
return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
|
||||
};
|
||||
|
||||
/**
|
||||
* iOS Modal Enter Animation for the Card presentation style
|
||||
*/
|
||||
export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
|
||||
const { presentingEl, currentBreakpoint } = opts;
|
||||
const { presentingEl, currentBreakpoint, expandToScroll } = opts;
|
||||
const root = getElementRoot(baseEl);
|
||||
const { wrapperAnimation, backdropAnimation } =
|
||||
const { wrapperAnimation, backdropAnimation, contentAnimation } =
|
||||
currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
|
||||
|
||||
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
|
||||
|
||||
wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!).beforeStyles({ opacity: 1 });
|
||||
|
||||
// The content animation is only added if scrolling is enabled for
|
||||
// all the breakpoints.
|
||||
!expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
|
||||
|
||||
const baseAnimation = createAnimation('entering-base')
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||
.duration(500)
|
||||
.addAnimation(wrapperAnimation);
|
||||
.addAnimation([wrapperAnimation])
|
||||
.beforeAddWrite(() => {
|
||||
if (expandToScroll) {
|
||||
// Scroll can only be done when the modal is fully expanded.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are some browsers that causes flickering when
|
||||
* dragging the content when scroll is enabled at every
|
||||
* breakpoint. This is due to the wrapper element being
|
||||
* transformed off the screen and having a snap animation.
|
||||
*
|
||||
* A workaround is to clone the footer element and append
|
||||
* it outside of the wrapper element. This way, the footer
|
||||
* is still visible and the drag can be done without
|
||||
* flickering. The original footer is hidden until the modal
|
||||
* is dismissed. This maintains the animation of the footer
|
||||
* when the modal is dismissed.
|
||||
*
|
||||
* The workaround needs to be done before the animation starts
|
||||
* so there are no flickering issues.
|
||||
*/
|
||||
const ionFooter = baseEl.querySelector('ion-footer');
|
||||
/**
|
||||
* This check is needed to prevent more than one footer
|
||||
* from being appended to the shadow root.
|
||||
* Otherwise, iOS and MD enter animations would append
|
||||
* the footer twice.
|
||||
*/
|
||||
const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
|
||||
if (ionFooter && !ionFooterAlreadyAppended) {
|
||||
const footerHeight = ionFooter.clientHeight;
|
||||
const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
|
||||
|
||||
baseEl.shadowRoot!.appendChild(clonedFooter);
|
||||
ionFooter.style.setProperty('display', 'none');
|
||||
ionFooter.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// Padding is added to prevent some content from being hidden.
|
||||
const page = baseEl.querySelector('.ion-page') as HTMLElement;
|
||||
page.style.setProperty('padding-bottom', `${footerHeight}px`);
|
||||
}
|
||||
});
|
||||
|
||||
if (contentAnimation) {
|
||||
baseAnimation.addAnimation(contentAnimation);
|
||||
}
|
||||
|
||||
if (presentingEl) {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
|
||||
@@ -19,7 +19,7 @@ const createLeaveAnimation = () => {
|
||||
* iOS Modal Leave Animation
|
||||
*/
|
||||
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions, duration = 500): Animation => {
|
||||
const { presentingEl, currentBreakpoint } = opts;
|
||||
const { presentingEl, currentBreakpoint, expandToScroll } = opts;
|
||||
const root = getElementRoot(baseEl);
|
||||
const { wrapperAnimation, backdropAnimation } =
|
||||
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
|
||||
@@ -32,7 +32,33 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||
.duration(duration)
|
||||
.addAnimation(wrapperAnimation);
|
||||
.addAnimation(wrapperAnimation)
|
||||
.beforeAddWrite(() => {
|
||||
if (expandToScroll) {
|
||||
// Scroll can only be done when the modal is fully expanded.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If expandToScroll is disabled, we need to swap
|
||||
* the visibility to the original, so the footer
|
||||
* dismisses with the modal and doesn't stay
|
||||
* until the modal is removed from the DOM.
|
||||
*/
|
||||
const ionFooter = baseEl.querySelector('ion-footer');
|
||||
if (ionFooter) {
|
||||
const clonedFooter = baseEl.shadowRoot!.querySelector('ion-footer')!;
|
||||
|
||||
ionFooter.style.removeProperty('display');
|
||||
ionFooter.removeAttribute('aria-hidden');
|
||||
|
||||
clonedFooter.style.setProperty('display', 'none');
|
||||
clonedFooter.setAttribute('aria-hidden', 'true');
|
||||
|
||||
const page = baseEl.querySelector('.ion-page') as HTMLElement;
|
||||
page.style.removeProperty('padding-bottom');
|
||||
}
|
||||
});
|
||||
|
||||
if (presentingEl) {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
|
||||
@@ -19,25 +19,78 @@ const createEnterAnimation = () => {
|
||||
{ offset: 1, opacity: 1, transform: `translateY(0px)` },
|
||||
]);
|
||||
|
||||
return { backdropAnimation, wrapperAnimation };
|
||||
return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
|
||||
};
|
||||
|
||||
/**
|
||||
* Md Modal Enter Animation
|
||||
*/
|
||||
export const mdEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
|
||||
const { currentBreakpoint } = opts;
|
||||
const { currentBreakpoint, expandToScroll } = opts;
|
||||
const root = getElementRoot(baseEl);
|
||||
const { wrapperAnimation, backdropAnimation } =
|
||||
const { wrapperAnimation, backdropAnimation, contentAnimation } =
|
||||
currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
|
||||
|
||||
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
|
||||
|
||||
wrapperAnimation.addElement(root.querySelector('.modal-wrapper')!);
|
||||
|
||||
return createAnimation()
|
||||
// The content animation is only added if scrolling is enabled for
|
||||
// all the breakpoints.
|
||||
expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
|
||||
|
||||
const baseAnimation = createAnimation()
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.36,0.66,0.04,1)')
|
||||
.duration(280)
|
||||
.addAnimation([backdropAnimation, wrapperAnimation]);
|
||||
.addAnimation([backdropAnimation, wrapperAnimation])
|
||||
.beforeAddWrite(() => {
|
||||
if (expandToScroll) {
|
||||
// Scroll can only be done when the modal is fully expanded.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are some browsers that causes flickering when
|
||||
* dragging the content when scroll is enabled at every
|
||||
* breakpoint. This is due to the wrapper element being
|
||||
* transformed off the screen and having a snap animation.
|
||||
*
|
||||
* A workaround is to clone the footer element and append
|
||||
* it outside of the wrapper element. This way, the footer
|
||||
* is still visible and the drag can be done without
|
||||
* flickering. The original footer is hidden until the modal
|
||||
* is dismissed. This maintains the animation of the footer
|
||||
* when the modal is dismissed.
|
||||
*
|
||||
* The workaround needs to be done before the animation starts
|
||||
* so there are no flickering issues.
|
||||
*/
|
||||
const ionFooter = baseEl.querySelector('ion-footer');
|
||||
/**
|
||||
* This check is needed to prevent more than one footer
|
||||
* from being appended to the shadow root.
|
||||
* Otherwise, iOS and MD enter animations would append
|
||||
* the footer twice.
|
||||
*/
|
||||
const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
|
||||
if (ionFooter && !ionFooterAlreadyAppended) {
|
||||
const footerHeight = ionFooter.clientHeight;
|
||||
const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
|
||||
|
||||
baseEl.shadowRoot!.appendChild(clonedFooter);
|
||||
ionFooter.style.setProperty('display', 'none');
|
||||
ionFooter.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// Padding is added to prevent some content from being hidden.
|
||||
const page = baseEl.querySelector('.ion-page') as HTMLElement;
|
||||
page.style.setProperty('padding-bottom', `${footerHeight}px`);
|
||||
}
|
||||
});
|
||||
|
||||
if (contentAnimation) {
|
||||
baseAnimation.addAnimation(contentAnimation);
|
||||
}
|
||||
|
||||
return baseAnimation;
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ const createLeaveAnimation = () => {
|
||||
* Md Modal Leave Animation
|
||||
*/
|
||||
export const mdLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
|
||||
const { currentBreakpoint } = opts;
|
||||
const { currentBreakpoint, expandToScroll } = opts;
|
||||
const root = getElementRoot(baseEl);
|
||||
const { wrapperAnimation, backdropAnimation } =
|
||||
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
|
||||
@@ -29,8 +29,36 @@ export const mdLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOption
|
||||
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
|
||||
wrapperAnimation.addElement(root.querySelector('.modal-wrapper')!);
|
||||
|
||||
return createAnimation()
|
||||
const baseAnimation = createAnimation()
|
||||
.easing('cubic-bezier(0.47,0,0.745,0.715)')
|
||||
.duration(200)
|
||||
.addAnimation([backdropAnimation, wrapperAnimation]);
|
||||
.addAnimation([backdropAnimation, wrapperAnimation])
|
||||
.beforeAddWrite(() => {
|
||||
if (expandToScroll) {
|
||||
// Scroll can only be done when the modal is fully expanded.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If expandToScroll is disabled, we need to swap
|
||||
* the visibility to the original, so the footer
|
||||
* dismisses with the modal and doesn't stay
|
||||
* until the modal is removed from the DOM.
|
||||
*/
|
||||
const ionFooter = baseEl.querySelector('ion-footer');
|
||||
if (ionFooter) {
|
||||
const clonedFooter = baseEl.shadowRoot!.querySelector('ion-footer')!;
|
||||
|
||||
ionFooter.style.removeProperty('display');
|
||||
ionFooter.removeAttribute('aria-hidden');
|
||||
|
||||
clonedFooter.style.setProperty('display', 'none');
|
||||
clonedFooter.setAttribute('aria-hidden', 'true');
|
||||
|
||||
const page = baseEl.querySelector('.ion-page') as HTMLElement;
|
||||
page.style.removeProperty('padding-bottom');
|
||||
}
|
||||
});
|
||||
|
||||
return baseAnimation;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ModalAnimationOptions } from '../modal-interface';
|
||||
import { getBackdropValueForSheet } from '../utils';
|
||||
|
||||
export const createSheetEnterAnimation = (opts: ModalAnimationOptions) => {
|
||||
const { currentBreakpoint, backdropBreakpoint } = opts;
|
||||
const { currentBreakpoint, backdropBreakpoint, expandToScroll } = opts;
|
||||
|
||||
/**
|
||||
* If the backdropBreakpoint is undefined, then the backdrop
|
||||
@@ -29,7 +29,17 @@ export const createSheetEnterAnimation = (opts: ModalAnimationOptions) => {
|
||||
{ offset: 1, opacity: 1, transform: `translateY(${100 - currentBreakpoint! * 100}%)` },
|
||||
]);
|
||||
|
||||
return { wrapperAnimation, backdropAnimation };
|
||||
/**
|
||||
* This allows the content to be scrollable at any breakpoint.
|
||||
*/
|
||||
const contentAnimation = !expandToScroll
|
||||
? createAnimation('contentAnimation').keyframes([
|
||||
{ offset: 0, opacity: 1, maxHeight: `${(1 - currentBreakpoint!) * 100}%` },
|
||||
{ offset: 1, opacity: 1, maxHeight: `${currentBreakpoint! * 100}%` },
|
||||
])
|
||||
: undefined;
|
||||
|
||||
return { wrapperAnimation, backdropAnimation, contentAnimation };
|
||||
};
|
||||
|
||||
export const createSheetLeaveAnimation = (opts: ModalAnimationOptions) => {
|
||||
|
||||
@@ -49,6 +49,7 @@ export const createSheetGesture = (
|
||||
backdropBreakpoint: number,
|
||||
animation: Animation,
|
||||
breakpoints: number[] = [],
|
||||
expandToScroll: boolean,
|
||||
getCurrentBreakpoint: () => number,
|
||||
onDismiss: () => void,
|
||||
onBreakpointChange: (breakpoint: number) => void
|
||||
@@ -71,6 +72,10 @@ export const createSheetGesture = (
|
||||
{ offset: 1, transform: 'translateY(100%)' },
|
||||
],
|
||||
BACKDROP_KEYFRAMES: backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop,
|
||||
CONTENT_KEYFRAMES: [
|
||||
{ offset: 0, maxHeight: '100%' },
|
||||
{ offset: 1, maxHeight: '0%' },
|
||||
],
|
||||
};
|
||||
|
||||
const contentEl = baseEl.querySelector('ion-content');
|
||||
@@ -79,10 +84,11 @@ export const createSheetGesture = (
|
||||
let offset = 0;
|
||||
let canDismissBlocksGesture = false;
|
||||
const canDismissMaxStep = 0.95;
|
||||
const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
|
||||
const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
|
||||
const maxBreakpoint = breakpoints[breakpoints.length - 1];
|
||||
const minBreakpoint = breakpoints[0];
|
||||
const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation');
|
||||
const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation');
|
||||
const contentAnimation = animation.childAnimations.find((ani) => ani.id === 'contentAnimation');
|
||||
|
||||
const enableBackdrop = () => {
|
||||
baseEl.style.setProperty('pointer-events', 'auto');
|
||||
@@ -110,6 +116,36 @@ export const createSheetGesture = (
|
||||
baseEl.classList.add(FOCUS_TRAP_DISABLE_CLASS);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the visible modal footer when `expandToScroll` is disabled.
|
||||
* @param footer The footer to show.
|
||||
*/
|
||||
const swapFooterVisibility = (footer: 'original' | 'cloned') => {
|
||||
const originalFooter = baseEl.querySelector('ion-footer') as HTMLIonFooterElement | null;
|
||||
|
||||
if (!originalFooter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clonedFooter = wrapperEl.nextElementSibling as HTMLIonFooterElement;
|
||||
const footerToHide = footer === 'original' ? clonedFooter : originalFooter;
|
||||
const footerToShow = footer === 'original' ? originalFooter : clonedFooter;
|
||||
|
||||
footerToShow.style.removeProperty('display');
|
||||
footerToShow.removeAttribute('aria-hidden');
|
||||
|
||||
const page = baseEl.querySelector('.ion-page') as HTMLElement;
|
||||
if (footer === 'original') {
|
||||
page.style.removeProperty('padding-bottom');
|
||||
} else {
|
||||
const pagePadding = footerToShow.clientHeight;
|
||||
page.style.setProperty('padding-bottom', `${pagePadding}px`);
|
||||
}
|
||||
|
||||
footerToHide.style.setProperty('display', 'none');
|
||||
footerToHide.setAttribute('aria-hidden', 'true');
|
||||
};
|
||||
|
||||
/**
|
||||
* After the entering animation completes,
|
||||
* we need to set the animation to go from
|
||||
@@ -121,6 +157,7 @@ export const createSheetGesture = (
|
||||
if (wrapperAnimation && backdropAnimation) {
|
||||
wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
|
||||
backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
|
||||
contentAnimation?.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
|
||||
animation.progressStart(true, 1 - currentBreakpoint);
|
||||
|
||||
/**
|
||||
@@ -138,7 +175,7 @@ export const createSheetGesture = (
|
||||
}
|
||||
}
|
||||
|
||||
if (contentEl && currentBreakpoint !== maxBreakpoint) {
|
||||
if (contentEl && currentBreakpoint !== maxBreakpoint && expandToScroll) {
|
||||
contentEl.scrollY = false;
|
||||
}
|
||||
|
||||
@@ -154,6 +191,14 @@ export const createSheetGesture = (
|
||||
const contentEl = findClosestIonContent(detail.event.target! as HTMLElement);
|
||||
currentBreakpoint = getCurrentBreakpoint();
|
||||
|
||||
/**
|
||||
* If we have expandToScroll disabled, we should not allow the swipe gesture to start
|
||||
* if the content is being swiped.
|
||||
*/
|
||||
if (!expandToScroll && contentEl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentBreakpoint === 1 && contentEl) {
|
||||
/**
|
||||
* The modal should never swipe to close on the content with a refresher.
|
||||
@@ -187,6 +232,16 @@ export const createSheetGesture = (
|
||||
*/
|
||||
canDismissBlocksGesture = baseEl.canDismiss !== undefined && baseEl.canDismiss !== true && minBreakpoint === 0;
|
||||
|
||||
/**
|
||||
* If expandToScroll is disabled, we need to swap
|
||||
* the footer visibility to the original, so if the modal
|
||||
* is dismissed, the footer dismisses with the modal
|
||||
* and doesn't stay on the screen after the modal is gone.
|
||||
*/
|
||||
if (!expandToScroll) {
|
||||
swapFooterVisibility('original');
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are pulling down, then it is possible we are pulling on the content.
|
||||
* We do not want scrolling to happen at the same time as the gesture.
|
||||
@@ -323,6 +378,20 @@ export const createSheetGesture = (
|
||||
},
|
||||
]);
|
||||
|
||||
if (contentAnimation) {
|
||||
/**
|
||||
* The modal content should scroll at any breakpoint when expandToScroll
|
||||
* is disabled. In order to do this, the content needs to be completely
|
||||
* viewable so scrolling can access everything. Otherwise, the default
|
||||
* behavior would show the content off the screen and only allow
|
||||
* scrolling when the sheet is fully expanded.
|
||||
*/
|
||||
contentAnimation.keyframes([
|
||||
{ offset: 0, maxHeight: `${(1 - breakpointOffset) * 100}%` },
|
||||
{ offset: 1, maxHeight: `${snapToBreakpoint * 100}%` },
|
||||
]);
|
||||
}
|
||||
|
||||
animation.progressStep(0);
|
||||
}
|
||||
|
||||
@@ -332,6 +401,15 @@ export const createSheetGesture = (
|
||||
*/
|
||||
gesture.enable(false);
|
||||
|
||||
/**
|
||||
* If expandToScroll is disabled, we need to swap
|
||||
* the footer visibility to the cloned one so the footer
|
||||
* doesn't flicker when the sheet's height is animated.
|
||||
*/
|
||||
if (!expandToScroll && shouldRemainOpen) {
|
||||
swapFooterVisibility('cloned');
|
||||
}
|
||||
|
||||
if (shouldPreventDismiss) {
|
||||
handleCanDismiss(baseEl, animation);
|
||||
} else if (!shouldRemainOpen) {
|
||||
@@ -339,13 +417,13 @@ export const createSheetGesture = (
|
||||
}
|
||||
|
||||
/**
|
||||
* If the sheet is going to be fully expanded then we should enable
|
||||
* scrolling immediately. The sheet modal animation takes ~500ms to finish
|
||||
* so if we wait until then there is a visible delay for when scrolling is
|
||||
* re-enabled. Native iOS allows for scrolling on the sheet modal as soon
|
||||
* as the gesture is released, so we align with that.
|
||||
* Enables scrolling immediately if the sheet is about to fully expand
|
||||
* or if it allows scrolling at any breakpoint. Without this, there would
|
||||
* be a ~500ms delay while the modal animation completes, causing a
|
||||
* noticeable lag. Native iOS allows scrolling as soon as the gesture is
|
||||
* released, so we align with that behavior.
|
||||
*/
|
||||
if (contentEl && snapToBreakpoint === breakpoints[breakpoints.length - 1]) {
|
||||
if (contentEl && (snapToBreakpoint === breakpoints[breakpoints.length - 1] || !expandToScroll)) {
|
||||
contentEl.scrollY = true;
|
||||
}
|
||||
|
||||
@@ -365,6 +443,7 @@ export const createSheetGesture = (
|
||||
raf(() => {
|
||||
wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]);
|
||||
backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]);
|
||||
contentAnimation?.keyframes([...SheetDefaults.CONTENT_KEYFRAMES]);
|
||||
animation.progressStart(true, 1 - snapToBreakpoint);
|
||||
currentBreakpoint = snapToBreakpoint;
|
||||
onBreakpointChange(currentBreakpoint);
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface ModalAnimationOptions {
|
||||
presentingEl?: HTMLElement;
|
||||
currentBreakpoint?: number;
|
||||
backdropBreakpoint?: number;
|
||||
expandToScroll: boolean;
|
||||
}
|
||||
|
||||
export interface ModalBreakpointChangeEventDetail {
|
||||
|
||||
@@ -87,3 +87,16 @@
|
||||
:host(.modal-sheet) .modal-wrapper {
|
||||
@include border-radius(var(--border-radius), var(--border-radius), 0, 0);
|
||||
}
|
||||
|
||||
// iOS Sheet Modal - Scroll at all breakpoints
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sheet modals require an additional padding as mentioned in the
|
||||
* `core.scss` file. However, there's a workaround that requires
|
||||
* a cloned footer to be added to the modal. This is only necessary
|
||||
* because the core styles are not being applied to the cloned footer.
|
||||
*/
|
||||
:host(.modal-sheet.modal-no-expand-scroll) ion-footer ion-toolbar:first-of-type {
|
||||
padding-top: $modal-sheet-padding-top;
|
||||
}
|
||||
|
||||
@@ -166,3 +166,13 @@ ion-backdrop {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
// Sheet Modal - Scroll at all breakpoints
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.modal-sheet.modal-no-expand-scroll) ion-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
width: var(--width);
|
||||
}
|
||||
|
||||
@@ -130,6 +130,18 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
*/
|
||||
@Prop() breakpoints?: number[];
|
||||
|
||||
/**
|
||||
* Controls whether scrolling or dragging within the sheet modal expands
|
||||
* it to a larger breakpoint. This only takes effect when `breakpoints`
|
||||
* and `initialBreakpoint` are set.
|
||||
*
|
||||
* If `true`, scrolling or dragging anywhere in the modal will first expand
|
||||
* it to the next breakpoint. Once fully expanded, scrolling will affect the content.
|
||||
* If `false`, scrolling will always affect the content, and the modal will only expand
|
||||
* when dragging the header or handle.
|
||||
*/
|
||||
@Prop() expandToScroll = true;
|
||||
|
||||
/**
|
||||
* A decimal value between 0 and 1 that indicates the
|
||||
* initial point the modal will open at when creating a
|
||||
@@ -562,6 +574,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
presentingEl: presentingElement,
|
||||
currentBreakpoint: this.initialBreakpoint,
|
||||
backdropBreakpoint: this.backdropBreakpoint,
|
||||
expandToScroll: this.expandToScroll,
|
||||
});
|
||||
|
||||
/* tslint:disable-next-line */
|
||||
@@ -616,7 +629,10 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
// should be in the DOM and referenced by now, except
|
||||
// for the presenting el
|
||||
const animationBuilder = this.leaveAnimation || config.get('modalLeave', iosLeaveAnimation);
|
||||
const ani = (this.animation = animationBuilder(el, { presentingEl: this.presentingElement }));
|
||||
const ani = (this.animation = animationBuilder(el, {
|
||||
presentingEl: this.presentingElement,
|
||||
expandToScroll: this.expandToScroll,
|
||||
}));
|
||||
|
||||
const contentEl = findIonContent(el);
|
||||
if (!contentEl) {
|
||||
@@ -668,6 +684,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
presentingEl: this.presentingElement,
|
||||
currentBreakpoint: initialBreakpoint,
|
||||
backdropBreakpoint,
|
||||
expandToScroll: this.expandToScroll,
|
||||
}));
|
||||
|
||||
ani.progressStart(true, 1);
|
||||
@@ -680,6 +697,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
backdropBreakpoint,
|
||||
ani,
|
||||
this.sortedBreakpoints,
|
||||
this.expandToScroll,
|
||||
() => this.currentBreakpoint ?? 0,
|
||||
() => this.sheetOnDismiss(),
|
||||
(breakpoint: number) => {
|
||||
@@ -778,6 +796,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
presentingEl: presentingElement,
|
||||
currentBreakpoint: this.currentBreakpoint ?? this.initialBreakpoint,
|
||||
backdropBreakpoint: this.backdropBreakpoint,
|
||||
expandToScroll: this.expandToScroll,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -927,9 +946,16 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap } =
|
||||
this;
|
||||
|
||||
const {
|
||||
handle,
|
||||
isSheetModal,
|
||||
presentingElement,
|
||||
htmlAttributes,
|
||||
handleBehavior,
|
||||
inheritedAttributes,
|
||||
focusTrap,
|
||||
expandToScroll,
|
||||
} = this;
|
||||
const showHandle = handle !== false && isSheetModal;
|
||||
const mode = getIonMode(this);
|
||||
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
||||
@@ -948,6 +974,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
['modal-default']: !isCardModal && !isSheetModal,
|
||||
[`modal-card`]: isCardModal,
|
||||
[`modal-sheet`]: isSheetModal,
|
||||
[`modal-no-expand-scroll`]: isSheetModal && !expandToScroll,
|
||||
'overlay-hidden': true,
|
||||
[FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false,
|
||||
...getClassMap(this.cssClass),
|
||||
@@ -1019,6 +1046,12 @@ interface ModalOverlayOptions {
|
||||
* to fade in when using a sheet modal.
|
||||
*/
|
||||
backdropBreakpoint: number;
|
||||
|
||||
/**
|
||||
* Whether or not the modal should scroll/drag
|
||||
* the content only when fully expanded.
|
||||
*/
|
||||
expandToScroll?: boolean;
|
||||
}
|
||||
|
||||
type ModalPresentOptions = ModalOverlayOptions;
|
||||
|
||||
@@ -23,3 +23,9 @@ $modal-inset-height-large: 600px;
|
||||
|
||||
/// @prop - Text color of the modal content
|
||||
$modal-text-color: $text-color;
|
||||
|
||||
/// @prop - Padding top of the sheet modal
|
||||
$modal-sheet-padding-top: 6px;
|
||||
|
||||
/// @prop - Padding bottom of the sheet modal
|
||||
$modal-sheet-padding-bottom: 6px;
|
||||
|
||||
@@ -100,6 +100,12 @@
|
||||
>
|
||||
Present Sheet Modal (Max breakpoint is not 1)
|
||||
</button>
|
||||
<button
|
||||
id="scroll-at-edge-modal"
|
||||
onclick="presentModal({ initialBreakpoint: 0.5, breakpoints: [0, 0.25, 0.5, 0.75, 1], expandToScroll: false })"
|
||||
>
|
||||
Present Sheet Modal (Scroll at any breakpoint)
|
||||
</button>
|
||||
<button
|
||||
id="custom-backdrop-modal"
|
||||
onclick="presentModal({ backdropBreakpoint: 0.5, initialBreakpoint: 0.5 })"
|
||||
@@ -184,6 +190,11 @@
|
||||
${items}
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-title>Footer</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
`;
|
||||
|
||||
let extraOptions = {
|
||||
@@ -209,6 +220,7 @@
|
||||
button.addEventListener('click', () => {
|
||||
modalElement.dismiss();
|
||||
});
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
return modalElement;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -196,6 +196,13 @@ export class Select implements ComponentInterface {
|
||||
*/
|
||||
@Prop({ mutable: true }) value?: any | null;
|
||||
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property
|
||||
* works only for accessibility purposes, it will not prevent the form from
|
||||
* submitting if the value is invalid.
|
||||
*/
|
||||
@Prop() required = false;
|
||||
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*
|
||||
@@ -974,7 +981,7 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
private renderListbox() {
|
||||
const { disabled, inputId, isExpanded } = this;
|
||||
const { disabled, inputId, isExpanded, required } = this;
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -983,6 +990,7 @@ export class Select implements ComponentInterface {
|
||||
aria-label={this.ariaLabel}
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={`${isExpanded}`}
|
||||
aria-required={`${required}`}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||
|
||||
@@ -125,3 +125,35 @@ describe('select: slot interactivity', () => {
|
||||
expect(divSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ion-select: required', () => {
|
||||
it('should have a aria-required attribute as true in inner button', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Select],
|
||||
html: `
|
||||
<ion-select required="true"></ion-select>
|
||||
`,
|
||||
});
|
||||
|
||||
const select = page.body.querySelector('ion-select')!;
|
||||
|
||||
const nativeButton = select.shadowRoot!.querySelector('button')!;
|
||||
|
||||
expect(nativeButton.getAttribute('aria-required')).toBe('true');
|
||||
});
|
||||
|
||||
it('should not have a aria-required attribute as false in inner button', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Select],
|
||||
html: `
|
||||
<ion-select required="false"></ion-select>
|
||||
`,
|
||||
});
|
||||
|
||||
const select = page.body.querySelector('ion-select')!;
|
||||
|
||||
const nativeButton = select.shadowRoot!.querySelector('button')!;
|
||||
|
||||
expect(nativeButton.getAttribute('aria-required')).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ export class TabButton implements ComponentInterface, AnchorInterface {
|
||||
role="tab"
|
||||
aria-selected={selected ? 'true' : null}
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
tabindex={disabled ? '-1' : undefined}
|
||||
tabindex={disabled ? '-1' : '0'}
|
||||
{...inheritedAttributes}
|
||||
>
|
||||
<span class="button-inner">
|
||||
|
||||
@@ -55,7 +55,7 @@ export class Tab implements ComponentInterface {
|
||||
|
||||
@Watch('active')
|
||||
changeActive(isActive: boolean) {
|
||||
if (isActive) {
|
||||
if (isActive) {
|
||||
this.prepareLazyLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,9 @@ export class Tabs implements NavOutlet {
|
||||
}
|
||||
|
||||
private onTabClicked = (ev: CustomEvent<TabButtonClickEventDetail>) => {
|
||||
|
||||
console.log("onTabClicked");
|
||||
|
||||
const { href, tab } = ev.detail;
|
||||
if (this.useRouter && href !== undefined) {
|
||||
const router = document.querySelector('ion-router');
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -75,3 +75,33 @@ describe('ion-toggle: disabled', () => {
|
||||
expect(toggle.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ion-toggle: required', () => {
|
||||
it('should have a required attribute in inner input when true', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Toggle],
|
||||
html: `
|
||||
<ion-toggle required="true">Toggle</ion-toggle>
|
||||
`,
|
||||
});
|
||||
|
||||
const toggle = page.body.querySelector('ion-toggle')!;
|
||||
const nativeInput = toggle.shadowRoot?.querySelector('input[role=switch]')!;
|
||||
|
||||
expect(nativeInput.hasAttribute('required')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not have a required attribute in inner input when false', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Toggle],
|
||||
html: `
|
||||
<ion-toggle required="false">Toggle</ion-toggle>
|
||||
`,
|
||||
});
|
||||
|
||||
const toggle = page.body.querySelector('ion-toggle')!;
|
||||
const nativeInput = toggle.shadowRoot?.querySelector('input[role=switch]')!;
|
||||
|
||||
expect(nativeInput.hasAttribute('required')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,6 +108,13 @@ export class Toggle implements ComponentInterface {
|
||||
*/
|
||||
@Prop() alignment?: 'start' | 'center';
|
||||
|
||||
/**
|
||||
* If true, screen readers will announce it as a required field. This property
|
||||
* works only for accessibility purposes, it will not prevent the form from
|
||||
* submitting if the value is invalid.
|
||||
*/
|
||||
@Prop() required = false;
|
||||
|
||||
/**
|
||||
* Emitted when the user switches the toggle on or off.
|
||||
*
|
||||
@@ -290,7 +297,8 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activated, color, checked, disabled, el, justify, labelPlacement, inputId, name, alignment } = this;
|
||||
const { activated, color, checked, disabled, el, justify, labelPlacement, inputId, name, alignment, required } =
|
||||
this;
|
||||
|
||||
const mode = getIonMode(this);
|
||||
const value = this.getValue();
|
||||
@@ -327,6 +335,7 @@ export class Toggle implements ComponentInterface {
|
||||
onFocus={() => this.onFocus()}
|
||||
onBlur={() => this.onBlur()}
|
||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||
required={required}
|
||||
{...this.inheritedAttributes}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -13,6 +13,10 @@ import type { Color, CssClassMap, StyleEventDetail } from '../../interface';
|
||||
* @slot secondary - Content is placed to the left of the toolbar text in `ios` mode, and directly to the right in `md` mode.
|
||||
* @slot primary - Content is placed to the right of the toolbar text in `ios` mode, and to the far right in `md` mode.
|
||||
* @slot end - Content is placed to the right of the toolbar text in LTR, and to the left in RTL.
|
||||
*
|
||||
* @part background - The background of the toolbar, covering the entire area behind the toolbar content.
|
||||
* @part container - The container that wraps all toolbar content, including the default slot and named slot content.
|
||||
* @part content - The container for the default slot, wrapping content provided without a named slot.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-toolbar',
|
||||
@@ -97,11 +101,11 @@ export class Toolbar implements ComponentInterface {
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<div class="toolbar-background"></div>
|
||||
<div class="toolbar-container">
|
||||
<div class="toolbar-background" part="background"></div>
|
||||
<div class="toolbar-container" part="container">
|
||||
<slot name="start"></slot>
|
||||
<slot name="secondary"></slot>
|
||||
<div class="toolbar-content">
|
||||
<div class="toolbar-content" part="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot name="primary"></slot>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@import "../themes/ionic.globals";
|
||||
@import "../components/menu/menu.ios.vars";
|
||||
@import "../components/menu/menu.md.vars";
|
||||
@import "../components/modal/modal.vars";
|
||||
|
||||
:root {
|
||||
/**
|
||||
@@ -55,7 +56,7 @@ body.backdrop-no-scroll {
|
||||
html.ios ion-modal.modal-card ion-header ion-toolbar:first-of-type,
|
||||
html.ios ion-modal.modal-sheet ion-header ion-toolbar:first-of-type,
|
||||
html.ios ion-modal ion-footer ion-toolbar:first-of-type {
|
||||
padding-top: 6px;
|
||||
padding-top: $modal-sheet-padding-top;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ html.ios ion-modal ion-footer ion-toolbar:first-of-type {
|
||||
*/
|
||||
html.ios ion-modal.modal-card ion-header ion-toolbar:last-of-type,
|
||||
html.ios ion-modal.modal-sheet ion-header ion-toolbar:last-of-type {
|
||||
padding-bottom: 6px;
|
||||
padding-bottom: $modal-sheet-padding-bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SpinnerTypes } from '../components/spinner/spinner-configs';
|
||||
import type { TabButtonLayout } from '../components/tab-bar/tab-bar-interface';
|
||||
import type { AnimationBuilder, Mode } from '../interface';
|
||||
|
||||
import type { LogLevel } from './logging';
|
||||
import type { PlatformConfig } from './platform';
|
||||
|
||||
export interface IonicConfig {
|
||||
@@ -220,6 +221,15 @@ export interface IonicConfig {
|
||||
*/
|
||||
experimentalCloseWatcher?: boolean;
|
||||
|
||||
/**
|
||||
* Configures the logging level for Ionic Framework:
|
||||
*
|
||||
* - `'OFF'`: No errors or warnings are logged.
|
||||
* - `'ERROR'`: Logs only errors.
|
||||
* - `'WARN'`: Logs errors and warnings.
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
|
||||
// PRIVATE configs
|
||||
keyboardHeight?: number;
|
||||
inputShims?: boolean;
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { config } from '@global/config';
|
||||
|
||||
export const enum LogLevel {
|
||||
OFF = 'OFF',
|
||||
ERROR = 'ERROR',
|
||||
WARN = 'WARN',
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a warning to the console with an Ionic prefix
|
||||
* to indicate the library that is warning the developer.
|
||||
@@ -5,18 +13,24 @@
|
||||
* @param message - The string message to be logged to the console.
|
||||
*/
|
||||
export const printIonWarning = (message: string, ...params: any[]) => {
|
||||
return console.warn(`[Ionic Warning]: ${message}`, ...params);
|
||||
const logLevel = config.get('logLevel', LogLevel.WARN);
|
||||
if ([LogLevel.WARN].includes(logLevel)) {
|
||||
return console.warn(`[Ionic Warning]: ${message}`, ...params);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* Logs an error to the console with an Ionic prefix
|
||||
* to indicate the library that is warning the developer.
|
||||
*
|
||||
* @param message - The string message to be logged to the console.
|
||||
* @param params - Additional arguments to supply to the console.error.
|
||||
*/
|
||||
export const printIonError = (message: string, ...params: any) => {
|
||||
return console.error(`[Ionic Error]: ${message}`, ...params);
|
||||
export const printIonError = (message: string, ...params: any[]) => {
|
||||
const logLevel = config.get('logLevel', LogLevel.ERROR);
|
||||
if ([LogLevel.ERROR, LogLevel.WARN].includes(logLevel)) {
|
||||
return console.error(`[Ionic Error]: ${message}`, ...params);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
114
core/src/utils/logging/test/logging.spec.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { config } from '@global/config';
|
||||
import { LogLevel } from '../index';
|
||||
|
||||
import { printIonError, printIonWarning } from '../index';
|
||||
|
||||
describe('Logging', () => {
|
||||
describe('#printIonWarning', () => {
|
||||
let consoleWarnSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleWarnSpy = jest.spyOn(console, 'warn');
|
||||
// Suppress console.warn output from polluting the test output
|
||||
consoleWarnSpy.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('when the logLevel configuration is not set', () => {
|
||||
it('logs a warning to the console', () => {
|
||||
config.set('logLevel', undefined);
|
||||
|
||||
printIonWarning('This is a warning message');
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith('[Ionic Warning]: This is a warning message');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the logLevel configuration is set to 'WARN'", () => {
|
||||
it('logs a warning to the console', () => {
|
||||
config.set('logLevel', LogLevel.WARN);
|
||||
|
||||
printIonWarning('This is a warning message');
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith('[Ionic Warning]: This is a warning message');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the logLevel configuration is set to 'ERROR'", () => {
|
||||
it('does not log a warning to the console', () => {
|
||||
config.set('logLevel', LogLevel.ERROR);
|
||||
|
||||
printIonWarning('This is a warning message');
|
||||
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the logLevel configuration is set to 'OFF'", () => {
|
||||
it('does not log a warning to the console', () => {
|
||||
config.set('logLevel', LogLevel.OFF);
|
||||
|
||||
printIonWarning('This is a warning message');
|
||||
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#printIonError', () => {
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleErrorSpy = jest.spyOn(console, 'error');
|
||||
// Suppress console.error output from polluting the test output
|
||||
consoleErrorSpy.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('when the logLevel configuration is not set', () => {
|
||||
it('logs an error to the console', () => {
|
||||
config.set('logLevel', undefined);
|
||||
|
||||
printIonError('This is an error message');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('[Ionic Error]: This is an error message');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the logLevel configuration is set to 'ERROR'", () => {
|
||||
it('logs an error to the console', () => {
|
||||
config.set('logLevel', LogLevel.ERROR);
|
||||
|
||||
printIonError('This is an error message');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('[Ionic Error]: This is an error message');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the logLevel configuration is set to 'WARN'", () => {
|
||||
it('logs an error to the console', () => {
|
||||
config.set('logLevel', LogLevel.WARN);
|
||||
|
||||
printIonError('This is an error message');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('[Ionic Error]: This is an error message');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the logLevel configuration is set to 'OFF'", () => {
|
||||
it('does not log an error to the console', () => {
|
||||
config.set('logLevel', LogLevel.OFF);
|
||||
|
||||
printIonError('This is an error message');
|
||||
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
14
packages/angular-server/package-lock.json
generated
@@ -1031,9 +1031,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.20.0",
|
||||
@@ -7189,9 +7189,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.20.0",
|
||||
"ionicons": "^7.2.2",
|
||||
@@ -11111,4 +11111,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ const MODAL_INPUTS = [
|
||||
'canDismiss',
|
||||
'cssClass',
|
||||
'enterAnimation',
|
||||
'expandToScroll',
|
||||
'event',
|
||||
'focusTrap',
|
||||
'handle',
|
||||
|
||||
14
packages/angular/package-lock.json
generated
@@ -1398,9 +1398,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.20.0",
|
||||
@@ -9821,9 +9821,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.20.0",
|
||||
"ionicons": "^7.2.2",
|
||||
@@ -15021,4 +15021,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,14 +507,14 @@ export declare interface IonCardTitle extends Components.IonCardTitle {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'value']
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-checkbox',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'value'],
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value'],
|
||||
})
|
||||
export class IonCheckbox {
|
||||
protected el: HTMLIonCheckboxElement;
|
||||
@@ -2060,7 +2060,7 @@ export declare interface IonSegmentView extends Components.IonSegmentView {
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
methods: ['open']
|
||||
})
|
||||
@Component({
|
||||
@@ -2068,7 +2068,7 @@ export declare interface IonSegmentView extends Components.IonSegmentView {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
})
|
||||
export class IonSelect {
|
||||
protected el: HTMLIonSelectElement;
|
||||
@@ -2473,14 +2473,14 @@ Shorthand for ionToastDidDismiss.
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'value']
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-toggle',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'value'],
|
||||
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value'],
|
||||
})
|
||||
export class IonToggle {
|
||||
protected el: HTMLIonToggleElement;
|
||||
|
||||
2
packages/docs/package-lock.json
generated
@@ -10,4 +10,4 @@
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
packages/react-router/package-lock.json
generated
@@ -238,9 +238,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.20.0",
|
||||
@@ -415,12 +415,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/react": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.4.2.tgz",
|
||||
"integrity": "sha512-IDQy6a6oo9dG4Bu/svg/n8KSusTPY/bgI32FGGglm2nIe0F3hnjjIoS0Ikp0QdA4shqSnjV2Aq5AzGd3i6x5gg==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.4.3.tgz",
|
||||
"integrity": "sha512-AqwmoRCjTDBIgmywE6VnOOacOry21ma6TWMS8Dg8ZGu41rT6edDSmX/lKvAAV+jg4TkjE/U5n//OxzHBY/0+Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "8.4.2",
|
||||
"@ionic/core": "8.4.3",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
},
|
||||
@@ -4061,9 +4061,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.20.0",
|
||||
"ionicons": "^7.2.2",
|
||||
@@ -4167,11 +4167,11 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@ionic/react": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.4.2.tgz",
|
||||
"integrity": "sha512-IDQy6a6oo9dG4Bu/svg/n8KSusTPY/bgI32FGGglm2nIe0F3hnjjIoS0Ikp0QdA4shqSnjV2Aq5AzGd3i6x5gg==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.4.3.tgz",
|
||||
"integrity": "sha512-AqwmoRCjTDBIgmywE6VnOOacOry21ma6TWMS8Dg8ZGu41rT6edDSmX/lKvAAV+jg4TkjE/U5n//OxzHBY/0+Zg==",
|
||||
"requires": {
|
||||
"@ionic/core": "8.4.2",
|
||||
"@ionic/core": "8.4.3",
|
||||
"ionicons": "^7.0.0",
|
||||
"tslib": "*"
|
||||
}
|
||||
@@ -6670,4 +6670,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
packages/react/package-lock.json
generated
@@ -736,9 +736,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.20.0",
|
||||
@@ -12316,9 +12316,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.20.0",
|
||||
"ionicons": "^7.2.2",
|
||||
@@ -20499,4 +20499,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
packages/vue-router/package-lock.json
generated
@@ -661,9 +661,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.20.0",
|
||||
@@ -853,12 +853,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ionic/vue": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.4.2.tgz",
|
||||
"integrity": "sha512-GJZJJMXRZ1LP7bpIJquSrL8HC5Gwpz9ak/H6BzdhGIhDLEPfe//4EMkijbNGEnrlW6XSamN2lf6SrRZ1Lk5NMA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.4.3.tgz",
|
||||
"integrity": "sha512-jg4zDRfSrk3jSx3jvYf618Nf4pwTngB2viNihsOcXd2o04JvNHytouEQ7zES40/u9+tctRd0Db98nRyDOqwrCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "8.4.2",
|
||||
"@ionic/core": "8.4.3",
|
||||
"ionicons": "^7.0.0"
|
||||
}
|
||||
},
|
||||
@@ -7882,9 +7882,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.20.0",
|
||||
"ionicons": "^7.2.2",
|
||||
@@ -7997,11 +7997,11 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@ionic/vue": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.4.2.tgz",
|
||||
"integrity": "sha512-GJZJJMXRZ1LP7bpIJquSrL8HC5Gwpz9ak/H6BzdhGIhDLEPfe//4EMkijbNGEnrlW6XSamN2lf6SrRZ1Lk5NMA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.4.3.tgz",
|
||||
"integrity": "sha512-jg4zDRfSrk3jSx3jvYf618Nf4pwTngB2viNihsOcXd2o04JvNHytouEQ7zES40/u9+tctRd0Db98nRyDOqwrCw==",
|
||||
"requires": {
|
||||
"@ionic/core": "8.4.2",
|
||||
"@ionic/core": "8.4.3",
|
||||
"ionicons": "^7.0.0"
|
||||
}
|
||||
},
|
||||
@@ -12802,4 +12802,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
packages/vue/package-lock.json
generated
@@ -226,9 +226,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.20.0",
|
||||
@@ -4074,9 +4074,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.2.tgz",
|
||||
"integrity": "sha512-p1/avROJi+z/rTw07nkIi0hBsWw3oITVK3KCMrAccaViQpqQ6a692jX6xdnoycsj/ixeVjXgfV1Useu6hTNaHA==",
|
||||
"version": "8.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.3.tgz",
|
||||
"integrity": "sha512-U9HdZ32bre6OKA5akJVmQMxNB8Art3Nqdn3s7m2W83I5NhLg9Tehaf8ua8jxPZtxCa1nuN7tUbzHmMCkcdqDTw==",
|
||||
"requires": {
|
||||
"@stencil/core": "4.20.0",
|
||||
"ionicons": "^7.2.2",
|
||||
@@ -6686,4 +6686,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const IonAlert = /*@__PURE__*/ defineOverlayContainer<JSX.IonAlert>('ion-
|
||||
|
||||
export const IonLoading = /*@__PURE__*/ defineOverlayContainer<JSX.IonLoading>('ion-loading', defineIonLoadingCustomElement, ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'translucent', 'trigger']);
|
||||
|
||||
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'focusTrap', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'trigger'], true);
|
||||
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'expandToScroll', 'focusTrap', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'trigger'], true);
|
||||
|
||||
export const IonPickerLegacy = /*@__PURE__*/ defineOverlayContainer<JSX.IonPickerLegacy>('ion-picker-legacy', defineIonPickerLegacyCustomElement, ['animated', 'backdropDismiss', 'buttons', 'columns', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop', 'trigger']);
|
||||
|
||||
|
||||
@@ -235,6 +235,7 @@ export const IonCheckbox = /*@__PURE__*/ defineContainer<JSX.IonCheckbox, JSX.Io
|
||||
'labelPlacement',
|
||||
'justify',
|
||||
'alignment',
|
||||
'required',
|
||||
'ionChange',
|
||||
'ionFocus',
|
||||
'ionBlur'
|
||||
@@ -883,6 +884,7 @@ export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect, JSX.IonSel
|
||||
'expandedIcon',
|
||||
'shape',
|
||||
'value',
|
||||
'required',
|
||||
'ionChange',
|
||||
'ionCancel',
|
||||
'ionDismiss',
|
||||
@@ -1016,6 +1018,7 @@ export const IonToggle = /*@__PURE__*/ defineContainer<JSX.IonToggle, JSX.IonTog
|
||||
'labelPlacement',
|
||||
'justify',
|
||||
'alignment',
|
||||
'required',
|
||||
'ionChange',
|
||||
'ionFocus',
|
||||
'ionBlur'
|
||||
|
||||