Skip to content

Commit a6e734c

Browse files
committed
Merge remote-tracking branch 'origin/feature-8.5' into ROU-11564
2 parents ed77d81 + 41da4c3 commit a6e734c

35 files changed

+674
-48
lines changed

core/api.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ ion-checkbox,prop,justify,"end" | "space-between" | "start" | undefined,undefine
403403
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
404404
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
405405
ion-checkbox,prop,name,string,this.inputId,false,false
406+
ion-checkbox,prop,required,boolean,false,false,false
406407
ion-checkbox,prop,value,any,'on',false,false
407408
ion-checkbox,event,ionBlur,void,true
408409
ion-checkbox,event,ionChange,CheckboxChangeEventDetail<any>,true
@@ -1074,6 +1075,7 @@ ion-modal,prop,backdropDismiss,boolean,true,false,false
10741075
ion-modal,prop,breakpoints,number[] | undefined,undefined,false,false
10751076
ion-modal,prop,canDismiss,((data?: any, role?: string | undefined) => Promise<boolean>) | boolean,true,false,false
10761077
ion-modal,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
1078+
ion-modal,prop,expandToScroll,boolean,true,false,false
10771079
ion-modal,prop,focusTrap,boolean,true,false,false
10781080
ion-modal,prop,handle,boolean | undefined,undefined,false,false
10791081
ion-modal,prop,handleBehavior,"cycle" | "none" | undefined,'none',false,false
@@ -1631,6 +1633,7 @@ ion-select,prop,multiple,boolean,false,false,false
16311633
ion-select,prop,name,string,this.inputId,false,false
16321634
ion-select,prop,okText,string,'OK',false,false
16331635
ion-select,prop,placeholder,string | undefined,undefined,false,false
1636+
ion-select,prop,required,boolean,false,false,false
16341637
ion-select,prop,selectedText,null | string | undefined,undefined,false,false
16351638
ion-select,prop,shape,"round" | undefined,undefined,false,false
16361639
ion-select,prop,toggleIcon,string | undefined,undefined,false,false
@@ -1944,6 +1947,7 @@ ion-toggle,prop,justify,"end" | "space-between" | "start" | undefined,undefined,
19441947
ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
19451948
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
19461949
ion-toggle,prop,name,string,this.inputId,false,false
1950+
ion-toggle,prop,required,boolean,false,false,false
19471951
ion-toggle,prop,value,null | string | undefined,'on',false,false
19481952
ion-toggle,event,ionBlur,void,true
19491953
ion-toggle,event,ionChange,ToggleChangeEventDetail<any>,true
@@ -2000,4 +2004,7 @@ ion-toolbar,css-prop,--padding-end,md
20002004
ion-toolbar,css-prop,--padding-start,ios
20012005
ion-toolbar,css-prop,--padding-start,md
20022006
ion-toolbar,css-prop,--padding-top,ios
2003-
ion-toolbar,css-prop,--padding-top,md
2007+
ion-toolbar,css-prop,--padding-top,md
2008+
ion-toolbar,part,background
2009+
ion-toolbar,part,container
2010+
ion-toolbar,part,content

core/src/components.d.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,10 @@ export namespace Components {
643643
* The name of the control, which is submitted with the form data.
644644
*/
645645
"name": string;
646+
/**
647+
* 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.
648+
*/
649+
"required": boolean;
646650
"setFocus": () => Promise<void>;
647651
/**
648652
* 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 {
17311735
* Animation to use when the modal is presented.
17321736
*/
17331737
"enterAnimation"?: AnimationBuilder;
1738+
/**
1739+
* 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.
1740+
*/
1741+
"expandToScroll": boolean;
17341742
/**
17351743
* 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.
17361744
*/
@@ -2808,6 +2816,10 @@ export namespace Components {
28082816
* The text to display when the select is empty.
28092817
*/
28102818
"placeholder"?: string;
2819+
/**
2820+
* 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.
2821+
*/
2822+
"required": boolean;
28112823
/**
28122824
* The text to display instead of the selected option's value.
28132825
*/
@@ -3280,6 +3292,10 @@ export namespace Components {
32803292
* The name of the control, which is submitted with the form data.
32813293
*/
32823294
"name": string;
3295+
/**
3296+
* 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.
3297+
*/
3298+
"required": boolean;
32833299
/**
32843300
* 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>`.
32853301
*/
@@ -5435,6 +5451,10 @@ declare namespace LocalJSX {
54355451
* Emitted when the checkbox has focus.
54365452
*/
54375453
"onIonFocus"?: (event: IonCheckboxCustomEvent<void>) => void;
5454+
/**
5455+
* 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.
5456+
*/
5457+
"required"?: boolean;
54385458
/**
54395459
* 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>`.
54405460
*/
@@ -6532,6 +6552,10 @@ declare namespace LocalJSX {
65326552
* Animation to use when the modal is presented.
65336553
*/
65346554
"enterAnimation"?: AnimationBuilder;
6555+
/**
6556+
* 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.
6557+
*/
6558+
"expandToScroll"?: boolean;
65356559
/**
65366560
* 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.
65376561
*/
@@ -7640,6 +7664,10 @@ declare namespace LocalJSX {
76407664
* The text to display when the select is empty.
76417665
*/
76427666
"placeholder"?: string;
7667+
/**
7668+
* 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.
7669+
*/
7670+
"required"?: boolean;
76437671
/**
76447672
* The text to display instead of the selected option's value.
76457673
*/
@@ -8155,6 +8183,10 @@ declare namespace LocalJSX {
81558183
* Emitted when the toggle has focus.
81568184
*/
81578185
"onIonFocus"?: (event: IonToggleCustomEvent<void>) => void;
8186+
/**
8187+
* 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.
8188+
*/
8189+
"required"?: boolean;
81588190
/**
81598191
* 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>`.
81608192
*/

core/src/components/checkbox/checkbox.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ export class Checkbox implements ComponentInterface {
9898
*/
9999
@Prop() alignment?: 'start' | 'center';
100100

101+
/**
102+
* If true, screen readers will announce it as a required field. This property
103+
* works only for accessibility purposes, it will not prevent the form from
104+
* submitting if the value is invalid.
105+
*/
106+
@Prop() required = false;
107+
101108
/**
102109
* Emitted when the checked property has changed as a result of a user action such as a click.
103110
*
@@ -182,6 +189,7 @@ export class Checkbox implements ComponentInterface {
182189
name,
183190
value,
184191
alignment,
192+
required,
185193
} = this;
186194
const mode = getIonMode(this);
187195
const path = getSVGPath(mode, indeterminate);
@@ -218,6 +226,7 @@ export class Checkbox implements ComponentInterface {
218226
onFocus={() => this.onFocus()}
219227
onBlur={() => this.onBlur()}
220228
ref={(focusEl) => (this.focusEl = focusEl)}
229+
required={required}
221230
{...inheritedAttributes}
222231
/>
223232
<div

core/src/components/checkbox/test/checkbox.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,33 @@ describe('ion-checkbox: indeterminate', () => {
5454
expect(checkbox.getAttribute('aria-checked')).toBe('mixed');
5555
});
5656
});
57+
58+
describe('ion-checkbox: required', () => {
59+
it('should have a required attribute in inner input when true', async () => {
60+
const page = await newSpecPage({
61+
components: [Checkbox],
62+
html: `
63+
<ion-checkbox required="true">Checkbox</ion-checkbox>
64+
`,
65+
});
66+
67+
const checkbox = page.body.querySelector('ion-checkbox')!;
68+
const nativeInput = checkbox.shadowRoot?.querySelector('input[type=checkbox]')!;
69+
70+
expect(nativeInput.hasAttribute('required')).toBeTruthy();
71+
});
72+
73+
it('should not have a required attribute in inner input when false', async () => {
74+
const page = await newSpecPage({
75+
components: [Checkbox],
76+
html: `
77+
<ion-checkbox required="false">Checkbox</ion-checkbox>
78+
`,
79+
});
80+
81+
const checkbox = page.body.querySelector('ion-checkbox')!;
82+
const nativeInput = checkbox.shadowRoot?.querySelector('input[type=checkbox]')!;
83+
84+
expect(nativeInput.hasAttribute('required')).toBeFalsy();
85+
});
86+
});

core/src/components/modal/animations/ios.enter.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,78 @@ const createEnterAnimation = () => {
1717

1818
const wrapperAnimation = createAnimation().fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
1919

20-
return { backdropAnimation, wrapperAnimation };
20+
return { backdropAnimation, wrapperAnimation, contentAnimation: undefined };
2121
};
2222

2323
/**
2424
* iOS Modal Enter Animation for the Card presentation style
2525
*/
2626
export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => {
27-
const { presentingEl, currentBreakpoint } = opts;
27+
const { presentingEl, currentBreakpoint, expandToScroll } = opts;
2828
const root = getElementRoot(baseEl);
29-
const { wrapperAnimation, backdropAnimation } =
29+
const { wrapperAnimation, backdropAnimation, contentAnimation } =
3030
currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
3131

3232
backdropAnimation.addElement(root.querySelector('ion-backdrop')!);
3333

3434
wrapperAnimation.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!).beforeStyles({ opacity: 1 });
3535

36+
// The content animation is only added if scrolling is enabled for
37+
// all the breakpoints.
38+
!expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
39+
3640
const baseAnimation = createAnimation('entering-base')
3741
.addElement(baseEl)
3842
.easing('cubic-bezier(0.32,0.72,0,1)')
3943
.duration(500)
40-
.addAnimation(wrapperAnimation);
44+
.addAnimation([wrapperAnimation])
45+
.beforeAddWrite(() => {
46+
if (expandToScroll) {
47+
// Scroll can only be done when the modal is fully expanded.
48+
return;
49+
}
50+
51+
/**
52+
* There are some browsers that causes flickering when
53+
* dragging the content when scroll is enabled at every
54+
* breakpoint. This is due to the wrapper element being
55+
* transformed off the screen and having a snap animation.
56+
*
57+
* A workaround is to clone the footer element and append
58+
* it outside of the wrapper element. This way, the footer
59+
* is still visible and the drag can be done without
60+
* flickering. The original footer is hidden until the modal
61+
* is dismissed. This maintains the animation of the footer
62+
* when the modal is dismissed.
63+
*
64+
* The workaround needs to be done before the animation starts
65+
* so there are no flickering issues.
66+
*/
67+
const ionFooter = baseEl.querySelector('ion-footer');
68+
/**
69+
* This check is needed to prevent more than one footer
70+
* from being appended to the shadow root.
71+
* Otherwise, iOS and MD enter animations would append
72+
* the footer twice.
73+
*/
74+
const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
75+
if (ionFooter && !ionFooterAlreadyAppended) {
76+
const footerHeight = ionFooter.clientHeight;
77+
const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
78+
79+
baseEl.shadowRoot!.appendChild(clonedFooter);
80+
ionFooter.style.setProperty('display', 'none');
81+
ionFooter.setAttribute('aria-hidden', 'true');
82+
83+
// Padding is added to prevent some content from being hidden.
84+
const page = baseEl.querySelector('.ion-page') as HTMLElement;
85+
page.style.setProperty('padding-bottom', `${footerHeight}px`);
86+
}
87+
});
88+
89+
if (contentAnimation) {
90+
baseAnimation.addAnimation(contentAnimation);
91+
}
4192

4293
if (presentingEl) {
4394
const isMobile = window.innerWidth < 768;

core/src/components/modal/animations/ios.leave.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const createLeaveAnimation = () => {
1919
* iOS Modal Leave Animation
2020
*/
2121
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions, duration = 500): Animation => {
22-
const { presentingEl, currentBreakpoint } = opts;
22+
const { presentingEl, currentBreakpoint, expandToScroll } = opts;
2323
const root = getElementRoot(baseEl);
2424
const { wrapperAnimation, backdropAnimation } =
2525
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
@@ -32,7 +32,33 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
3232
.addElement(baseEl)
3333
.easing('cubic-bezier(0.32,0.72,0,1)')
3434
.duration(duration)
35-
.addAnimation(wrapperAnimation);
35+
.addAnimation(wrapperAnimation)
36+
.beforeAddWrite(() => {
37+
if (expandToScroll) {
38+
// Scroll can only be done when the modal is fully expanded.
39+
return;
40+
}
41+
42+
/**
43+
* If expandToScroll is disabled, we need to swap
44+
* the visibility to the original, so the footer
45+
* dismisses with the modal and doesn't stay
46+
* until the modal is removed from the DOM.
47+
*/
48+
const ionFooter = baseEl.querySelector('ion-footer');
49+
if (ionFooter) {
50+
const clonedFooter = baseEl.shadowRoot!.querySelector('ion-footer')!;
51+
52+
ionFooter.style.removeProperty('display');
53+
ionFooter.removeAttribute('aria-hidden');
54+
55+
clonedFooter.style.setProperty('display', 'none');
56+
clonedFooter.setAttribute('aria-hidden', 'true');
57+
58+
const page = baseEl.querySelector('.ion-page') as HTMLElement;
59+
page.style.removeProperty('padding-bottom');
60+
}
61+
});
3662

3763
if (presentingEl) {
3864
const isMobile = window.innerWidth < 768;

0 commit comments

Comments
 (0)