diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e49112c0..fe2edcce96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + + +### Bug Fixes + +* **alert:** use correct heading structure for subHeader when header exists ([#29964](https://github.com/ionic-team/ionic-framework/issues/29964)) ([0fdcb32](https://github.com/ionic-team/ionic-framework/commit/0fdcb32ce0f99b284b314f79f7d0b071bc37faec)) + + +### Features + +* **menu:** pass role to ionWillClose and ionDidClose events ([#29954](https://github.com/ionic-team/ionic-framework/issues/29954)) ([ee2fa19](https://github.com/ionic-team/ionic-framework/commit/ee2fa19a1e9f09d492c7c08340d95ba6a56ebb2b)) +* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6)) +* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040)) + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 9f8cea162c..07089bd960 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + + +### Bug Fixes + +* **alert:** use correct heading structure for subHeader when header exists ([#29964](https://github.com/ionic-team/ionic-framework/issues/29964)) ([0fdcb32](https://github.com/ionic-team/ionic-framework/commit/0fdcb32ce0f99b284b314f79f7d0b071bc37faec)) + + +### Features + +* **menu:** pass role to ionWillClose and ionDidClose events ([#29954](https://github.com/ionic-team/ionic-framework/issues/29954)) ([ee2fa19](https://github.com/ionic-team/ionic-framework/commit/ee2fa19a1e9f09d492c7c08340d95ba6a56ebb2b)) +* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6)) +* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040)) + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) diff --git a/core/api.txt b/core/api.txt index b2d0e4cc8f..e035f35494 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1289,15 +1289,15 @@ ion-menu,prop,side,"end" | "start",'start',false,true ion-menu,prop,swipeGesture,boolean,true,false,false ion-menu,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-menu,prop,type,"overlay" | "push" | "reveal" | undefined,undefined,false,false -ion-menu,method,close,close(animated?: boolean) => Promise +ion-menu,method,close,close(animated?: boolean, role?: string) => Promise ion-menu,method,isActive,isActive() => Promise ion-menu,method,isOpen,isOpen() => Promise ion-menu,method,open,open(animated?: boolean) => Promise -ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean) => Promise +ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean, role?: string) => Promise ion-menu,method,toggle,toggle(animated?: boolean) => Promise -ion-menu,event,ionDidClose,void,true +ion-menu,event,ionDidClose,MenuCloseEventDetail,true ion-menu,event,ionDidOpen,void,true -ion-menu,event,ionWillClose,void,true +ion-menu,event,ionWillClose,MenuCloseEventDetail,true ion-menu,event,ionWillOpen,void,true ion-menu,css-prop,--background,ionic ion-menu,css-prop,--background,ios @@ -1950,6 +1950,7 @@ ion-segment,css-prop,--background,ios ion-segment,css-prop,--background,md ion-segment-button,shadow +ion-segment-button,prop,contentId,string | undefined,undefined,false,true ion-segment-button,prop,disabled,boolean,false,false,false ion-segment-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-start" | "icon-top" | "label-hide" | undefined,'icon-top',false,false ion-segment-button,prop,mode,"ios" | "md",undefined,false,false @@ -2039,6 +2040,12 @@ ion-segment-button,part,indicator ion-segment-button,part,indicator-background ion-segment-button,part,native +ion-segment-content,shadow + +ion-segment-view,shadow +ion-segment-view,prop,disabled,boolean,false,false,false +ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true + ion-select,shadow ion-select,prop,cancelText,string,'Cancel',false,false ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true @@ -2046,7 +2053,7 @@ ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) ion-select,prop,disabled,boolean,false,false,false ion-select,prop,expandedIcon,string | undefined,undefined,false,false ion-select,prop,fill,"outline" | "solid" | undefined,undefined,false,false -ion-select,prop,interface,"action-sheet" | "alert" | "popover",'alert',false,false +ion-select,prop,interface,"action-sheet" | "alert" | "modal" | "popover",'alert',false,false ion-select,prop,interfaceOptions,any,{},false,false ion-select,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false ion-select,prop,label,string | undefined,undefined,false,false @@ -2121,6 +2128,11 @@ ion-select,part,label ion-select,part,placeholder ion-select,part,text +ion-select-modal,scoped +ion-select-modal,prop,header,string | undefined,undefined,false,false +ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false +ion-select-modal,prop,options,SelectModalOption[],[],false,false + ion-select-option,shadow ion-select-option,prop,disabled,boolean,false,false,false ion-select-option,prop,mode,"ios" | "md",undefined,false,false diff --git a/core/package-lock.json b/core/package-lock.json index 08cb818563..6a43363505 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { "@phosphor-icons/core": "^2.1.1", diff --git a/core/package.json b/core/package.json index 4b33e885dc..c0d1b998b8 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.3.4", + "version": "8.4.0", "description": "Base components for Ionic", "keywords": [ "ionic", diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 766b836a72..8d3c764b41 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -18,7 +18,7 @@ import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; import { SpinnerTypes } from "./components/spinner/spinner-configs"; import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; -import { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface"; +import { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface"; import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface"; import { ViewController } from "./components/nav/view-controller"; @@ -34,7 +34,9 @@ import { NavigationHookCallback } from "./components/route/route-interface"; import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface"; import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface"; import { SegmentButtonLayout } from "./components/segment-button/segment-button-interface"; +import { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface"; import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface"; +import { SelectModalOption } from "./components/select-modal/select-modal-interface"; import { SelectPopoverOption } from "./components/select-popover/select-popover-interface"; import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface"; import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface"; @@ -53,7 +55,7 @@ export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; export { SpinnerTypes } from "./components/spinner/spinner-configs"; export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; -export { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface"; +export { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface"; export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface"; export { ViewController } from "./components/nav/view-controller"; @@ -69,7 +71,9 @@ export { NavigationHookCallback } from "./components/route/route-interface"; export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface"; export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface"; export { SegmentButtonLayout } from "./components/segment-button/segment-button-interface"; +export { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface"; export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface"; +export { SelectModalOption } from "./components/select-modal/select-modal-interface"; export { SelectPopoverOption } from "./components/select-popover/select-popover-interface"; export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface"; export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface"; @@ -751,6 +755,7 @@ export namespace Components { * The name of the control, which is submitted with the form data. */ "name": string; + "setFocus": () => Promise; /** * Set to `"soft"` for a checkbox with more rounded corners. Only available when the theme is `"ionic"`. */ @@ -1888,7 +1893,7 @@ export namespace Components { /** * Closes the menu. If the menu is already closed or it can't be closed, it returns `false`. */ - "close": (animated?: boolean) => Promise; + "close": (animated?: boolean, role?: string) => Promise; /** * The `id` of the main content. When using a router this is typically `ion-router-outlet`. When not using a router, this is typically your main view's `ion-content`. This is not the id of the `ion-content` inside of your `ion-menu`. */ @@ -1924,7 +1929,7 @@ export namespace Components { /** * Opens or closes the button. If the operation can't be completed successfully, it returns `false`. */ - "setOpen": (shouldOpen: boolean, animated?: boolean) => Promise; + "setOpen": (shouldOpen: boolean, animated?: boolean, role?: string) => Promise; /** * Which side of the view the menu should be placed. */ @@ -2647,7 +2652,7 @@ export namespace Components { */ "name": string; "setButtonTabindex": (value: number) => Promise; - "setFocus": (ev: globalThis.Event) => Promise; + "setFocus": (ev?: globalThis.Event) => Promise; /** * The theme determines the visual appearance of the component. */ @@ -3157,6 +3162,10 @@ export namespace Components { "value"?: SegmentValue; } interface IonSegmentButton { + /** + * The `id` of the segment content. + */ + "contentId"?: string; /** * If `true`, the user cannot interact with the segment button. */ @@ -3183,6 +3192,19 @@ export namespace Components { */ "value": SegmentValue; } + interface IonSegmentContent { + } + interface IonSegmentView { + /** + * If `true`, the segment view cannot be interacted with. + */ + "disabled": boolean; + /** + * @param id : The id of the segment content to display. + * @param smoothScroll : Whether to animate the scroll transition. + */ + "setContent": (id: string, smoothScroll?: boolean) => Promise; + } interface IonSelect { /** * The text to display on the cancel button. @@ -3209,11 +3231,11 @@ export namespace Components { */ "fill"?: 'outline' | 'solid'; /** - * The interface the select should use: `action-sheet`, `popover` or `alert`. + * The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`. */ "interface": SelectInterface; /** - * Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface. + * Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet), the [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface. */ "interfaceOptions": any; /** @@ -3274,6 +3296,11 @@ export namespace Components { */ "value"?: any | null; } + interface IonSelectModal { + "header"?: string; + "multiple"?: boolean; + "options": SelectModalOption[]; + } interface IonSelectOption { /** * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons. @@ -4008,6 +4035,10 @@ export interface IonSegmentCustomEvent extends CustomEvent { detail: T; target: HTMLIonSegmentElement; } +export interface IonSegmentViewCustomEvent extends CustomEvent { + detail: T; + target: HTMLIonSegmentViewElement; +} export interface IonSelectCustomEvent extends CustomEvent { detail: T; target: HTMLIonSelectElement; @@ -4561,9 +4592,9 @@ declare global { }; interface HTMLIonMenuElementEventMap { "ionWillOpen": void; - "ionWillClose": void; + "ionWillClose": MenuCloseEventDetail; "ionDidOpen": void; - "ionDidClose": void; + "ionDidClose": MenuCloseEventDetail; "ionMenuChange": MenuChangeEventDetail; } interface HTMLIonMenuElement extends Components.IonMenu, HTMLStencilElement { @@ -5004,6 +5035,29 @@ declare global { prototype: HTMLIonSegmentButtonElement; new (): HTMLIonSegmentButtonElement; }; + interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement { + } + var HTMLIonSegmentContentElement: { + prototype: HTMLIonSegmentContentElement; + new (): HTMLIonSegmentContentElement; + }; + interface HTMLIonSegmentViewElementEventMap { + "ionSegmentViewScroll": SegmentViewScrollEvent; + } + interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLIonSegmentViewElement: { + prototype: HTMLIonSegmentViewElement; + new (): HTMLIonSegmentViewElement; + }; interface HTMLIonSelectElementEventMap { "ionChange": SelectChangeEventDetail; "ionCancel": void; @@ -5026,6 +5080,12 @@ declare global { prototype: HTMLIonSelectElement; new (): HTMLIonSelectElement; }; + interface HTMLIonSelectModalElement extends Components.IonSelectModal, HTMLStencilElement { + } + var HTMLIonSelectModalElement: { + prototype: HTMLIonSelectModalElement; + new (): HTMLIonSelectModalElement; + }; interface HTMLIonSelectOptionElement extends Components.IonSelectOption, HTMLStencilElement { } var HTMLIonSelectOptionElement: { @@ -5313,7 +5373,10 @@ declare global { "ion-searchbar": HTMLIonSearchbarElement; "ion-segment": HTMLIonSegmentElement; "ion-segment-button": HTMLIonSegmentButtonElement; + "ion-segment-content": HTMLIonSegmentContentElement; + "ion-segment-view": HTMLIonSegmentViewElement; "ion-select": HTMLIonSelectElement; + "ion-select-modal": HTMLIonSelectModalElement; "ion-select-option": HTMLIonSelectOptionElement; "ion-select-popover": HTMLIonSelectPopoverElement; "ion-skeleton-text": HTMLIonSkeletonTextElement; @@ -7252,7 +7315,7 @@ declare namespace LocalJSX { /** * Emitted when the menu is closed. */ - "onIonDidClose"?: (event: IonMenuCustomEvent) => void; + "onIonDidClose"?: (event: IonMenuCustomEvent) => void; /** * Emitted when the menu is open. */ @@ -7264,7 +7327,7 @@ declare namespace LocalJSX { /** * Emitted when the menu is about to be closed. */ - "onIonWillClose"?: (event: IonMenuCustomEvent) => void; + "onIonWillClose"?: (event: IonMenuCustomEvent) => void; /** * Emitted when the menu is about to be opened. */ @@ -8506,6 +8569,10 @@ declare namespace LocalJSX { "value"?: SegmentValue; } interface IonSegmentButton { + /** + * The `id` of the segment content. + */ + "contentId"?: string; /** * If `true`, the user cannot interact with the segment button. */ @@ -8531,6 +8598,18 @@ declare namespace LocalJSX { */ "value"?: SegmentValue; } + interface IonSegmentContent { + } + interface IonSegmentView { + /** + * If `true`, the segment view cannot be interacted with. + */ + "disabled"?: boolean; + /** + * Emitted when the segment view is scrolled. + */ + "onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent) => void; + } interface IonSelect { /** * The text to display on the cancel button. @@ -8557,11 +8636,11 @@ declare namespace LocalJSX { */ "fill"?: 'outline' | 'solid'; /** - * The interface the select should use: `action-sheet`, `popover` or `alert`. + * The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`. */ "interface"?: SelectInterface; /** - * Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface. + * Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet), the [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface. */ "interfaceOptions"?: any; /** @@ -8641,6 +8720,11 @@ declare namespace LocalJSX { */ "value"?: any | null; } + interface IonSelectModal { + "header"?: string; + "multiple"?: boolean; + "options"?: SelectModalOption[]; + } interface IonSelectOption { /** * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons. @@ -9346,7 +9430,10 @@ declare namespace LocalJSX { "ion-searchbar": IonSearchbar; "ion-segment": IonSegment; "ion-segment-button": IonSegmentButton; + "ion-segment-content": IonSegmentContent; + "ion-segment-view": IonSegmentView; "ion-select": IonSelect; + "ion-select-modal": IonSelectModal; "ion-select-option": IonSelectOption; "ion-select-popover": IonSelectPopover; "ion-skeleton-text": IonSkeletonText; @@ -9445,7 +9532,10 @@ declare module "@stencil/core" { "ion-searchbar": LocalJSX.IonSearchbar & JSXBase.HTMLAttributes; "ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes; "ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes; + "ion-segment-content": LocalJSX.IonSegmentContent & JSXBase.HTMLAttributes; + "ion-segment-view": LocalJSX.IonSegmentView & JSXBase.HTMLAttributes; "ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes; + "ion-select-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes; "ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes; "ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes; "ion-skeleton-text": LocalJSX.IonSkeletonText & JSXBase.HTMLAttributes; diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index 294c7f941b..0b8bb9cc15 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -732,10 +732,12 @@ export class Alert implements ComponentInterface, OverlayInterface { const role = this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert'; /** - * If the header is defined, use that. Otherwise, fall back to the subHeader. - * If neither is defined, don't set aria-labelledby. + * Use both the header and subHeader ids if they are defined. + * If only the header is defined, use the header id. + * If only the subHeader is defined, use the subHeader id. + * If neither are defined, do not set aria-labelledby. */ - const ariaLabelledBy = header ? hdrId : subHeader ? subHdrId : null; + const ariaLabelledBy = header && subHeader ? `${hdrId} ${subHdrId}` : header ? hdrId : subHeader ? subHdrId : null; return ( )} - {subHeader && ( + {/* If no header exists, subHeader should be the highest heading level, h2 */} + {subHeader && !header && (

{subHeader}

)} + {/* If a header exists, subHeader should be one level below it, h3 */} + {subHeader && header && ( +

+ {subHeader} +

+ )} {this.renderAlertMessage(msgId)} diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts b/core/src/components/alert/test/a11y/alert.e2e.ts index b5aeb63d05..df6a8b47db 100644 --- a/core/src/components/alert/test/a11y/alert.e2e.ts +++ b/core/src/components/alert/test/a11y/alert.e2e.ts @@ -17,6 +17,27 @@ const testAria = async ( const alert = page.locator('ion-alert'); + const header = alert.locator('.alert-title'); + const subHeader = alert.locator('.alert-sub-title'); + + // If a header exists, it should be an h2 element + if ((await header.count()) > 0) { + const headerTagName = await header.evaluate((el) => el.tagName); + expect(headerTagName).toBe('H2'); + } + + // If a header and subHeader exist, the subHeader should be an h3 element + if ((await header.count()) > 0 && (await subHeader.count()) > 0) { + const subHeaderTagName = await subHeader.evaluate((el) => el.tagName); + expect(subHeaderTagName).toBe('H3'); + } + + // If a subHeader exists without a header, the subHeader should be an h2 element + if ((await header.count()) === 0 && (await subHeader.count()) > 0) { + const subHeaderTagName = await subHeader.evaluate((el) => el.tagName); + expect(subHeaderTagName).toBe('H2'); + } + /** * expect().toHaveAttribute() can't check for a null value, so grab and check * the values manually instead. @@ -124,16 +145,24 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => { await page.goto(`/src/components/alert/test/a11y`, config); }); - test('should have aria-labelledby when header is set', async ({ page }) => { - await testAria(page, 'noMessage', 'alert-1-hdr', null); + test('should have aria-labelledby set to both when header and subHeader are set', async ({ page }) => { + await testAria(page, 'bothHeadersOnly', 'alert-1-hdr alert-1-sub-hdr', null); + }); + + test('should have aria-labelledby set when only header is set', async ({ page }) => { + await testAria(page, 'headerOnly', 'alert-1-hdr', null); + }); + + test('should fall back to subHeader for aria-labelledby if header is not defined', async ({ page }) => { + await testAria(page, 'subHeaderOnly', 'alert-1-sub-hdr', null); }); test('should have aria-describedby when message is set', async ({ page }) => { await testAria(page, 'noHeaders', null, 'alert-1-msg'); }); - test('should fall back to subHeader for aria-labelledby if header is not defined', async ({ page }) => { - await testAria(page, 'subHeaderOnly', 'alert-1-sub-hdr', 'alert-1-msg'); + test('should have aria-labelledby and aria-describedby when headers and message are set', async ({ page }) => { + await testAria(page, 'headersAndMessage', 'alert-1-hdr alert-1-sub-hdr', 'alert-1-msg'); }); test('should allow for manually specifying aria attributes', async ({ page }) => { diff --git a/core/src/components/alert/test/a11y/index.html b/core/src/components/alert/test/a11y/index.html index 00b3e1d928..cb137ba731 100644 --- a/core/src/components/alert/test/a11y/index.html +++ b/core/src/components/alert/test/a11y/index.html @@ -19,10 +19,11 @@

Alert - A11y

- + + - + @@ -34,11 +35,17 @@ await alert.present(); } - function presentBothHeaders() { + function presentBothHeadersOnly() { openAlert({ header: 'Header', subHeader: 'Subtitle', - message: 'This is an alert message.', + buttons: ['OK'], + }); + } + + function presentHeaderOnly() { + openAlert({ + header: 'Header', buttons: ['OK'], }); } @@ -46,7 +53,6 @@ function presentSubHeaderOnly() { openAlert({ subHeader: 'Subtitle', - message: 'This is an alert message.', buttons: ['OK'], }); } @@ -58,10 +64,11 @@ }); } - function presentNoMessage() { + function presentHeadersAndMessage() { openAlert({ header: 'Header', subHeader: 'Subtitle', + message: 'This is an alert message.', buttons: ['OK'], }); } diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index a2ae66bcee..6a382fed83 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -1,5 +1,5 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Component, Element, Event, Host, Prop, h } from '@stencil/core'; +import { Component, Element, Event, Host, Method, Prop, h } from '@stencil/core'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers'; import { createColorClasses, hostContext } from '@utils/theme'; @@ -134,7 +134,9 @@ export class Checkbox implements ComponentInterface { }; } - private setFocus() { + /** @internal */ + @Method() + async setFocus() { if (this.focusEl) { this.focusEl.focus(); } diff --git a/core/src/components/menu/menu-interface.ts b/core/src/components/menu/menu-interface.ts index f7aba9d830..44ef985f0c 100644 --- a/core/src/components/menu/menu-interface.ts +++ b/core/src/components/menu/menu-interface.ts @@ -22,7 +22,7 @@ export interface MenuI { close(animated?: boolean): Promise; toggle(animated?: boolean): Promise; setOpen(shouldOpen: boolean, animated?: boolean): Promise; - _setOpen(shouldOpen: boolean, animated?: boolean): Promise; + _setOpen(shouldOpen: boolean, animated?: boolean, role?: string): Promise; } export interface MenuControllerI { @@ -42,7 +42,7 @@ export interface MenuControllerI { _createAnimation(type: string, menuCmp: MenuI): Promise; _register(menu: MenuI): void; _unregister(menu: MenuI): void; - _setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise; + _setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean, role?: string): Promise; } export interface MenuChangeEventDetail { @@ -50,6 +50,10 @@ export interface MenuChangeEventDetail { open: boolean; } +export interface MenuCloseEventDetail { + role?: string; +} + export interface MenuCustomEvent extends CustomEvent { detail: T; target: HTMLIonMenuElement; diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 6b3ca6658f..e116424f25 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -7,14 +7,14 @@ import { shouldUseCloseWatcher } from '@utils/hardware-back-button'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers'; import { menuController } from '@utils/menu-controller'; -import { getPresentedOverlay } from '@utils/overlays'; +import { BACKDROP, GESTURE, getPresentedOverlay } from '@utils/overlays'; import { hostContext } from '@utils/theme'; import { config } from '../../global/config'; import { getIonMode, getIonTheme } from '../../global/ionic-global'; import type { Animation, Gesture, GestureDetail } from '../../interface'; -import type { MenuChangeEventDetail, MenuI, MenuType, Side } from './menu-interface'; +import type { MenuChangeEventDetail, MenuCloseEventDetail, MenuI, MenuType, Side } from './menu-interface'; const iosEasing = 'cubic-bezier(0.32,0.72,0,1)'; const mdEasing = 'cubic-bezier(0.0,0.0,0.2,1)'; @@ -183,7 +183,7 @@ export class Menu implements ComponentInterface, MenuI { /** * Emitted when the menu is about to be closed. */ - @Event() ionWillClose!: EventEmitter; + @Event() ionWillClose!: EventEmitter; /** * Emitted when the menu is open. */ @@ -192,7 +192,7 @@ export class Menu implements ComponentInterface, MenuI { /** * Emitted when the menu is closed. */ - @Event() ionDidClose!: EventEmitter; + @Event() ionDidClose!: EventEmitter; /** * Emitted when the menu state is changed. @@ -335,14 +335,14 @@ export class Menu implements ComponentInterface, MenuI { if (shouldClose) { ev.preventDefault(); ev.stopPropagation(); - this.close(); + this.close(undefined, BACKDROP); } } } onKeydown(ev: KeyboardEvent) { if (ev.key === 'Escape') { - this.close(); + this.close(undefined, BACKDROP); } } @@ -379,8 +379,8 @@ export class Menu implements ComponentInterface, MenuI { * it returns `false`. */ @Method() - close(animated = true): Promise { - return this.setOpen(false, animated); + close(animated = true, role?: string): Promise { + return this.setOpen(false, animated, role); } /** @@ -397,8 +397,8 @@ export class Menu implements ComponentInterface, MenuI { * If the operation can't be completed successfully, it returns `false`. */ @Method() - setOpen(shouldOpen: boolean, animated = true): Promise { - return menuController._setOpen(this, shouldOpen, animated); + setOpen(shouldOpen: boolean, animated = true, role?: string): Promise { + return menuController._setOpen(this, shouldOpen, animated, role); } private trapKeyboardFocus(ev: Event, doc: Document) { @@ -442,13 +442,13 @@ export class Menu implements ComponentInterface, MenuI { } } - async _setOpen(shouldOpen: boolean, animated = true): Promise { + async _setOpen(shouldOpen: boolean, animated = true, role?: string): Promise { // If the menu is disabled or it is currently being animated, let's do nothing if (!this._isActive() || this.isAnimating || shouldOpen === this._isOpen) { return false; } - this.beforeAnimation(shouldOpen); + this.beforeAnimation(shouldOpen, role); await this.loadAnimation(); await this.startAnimation(shouldOpen, animated); @@ -463,7 +463,7 @@ export class Menu implements ComponentInterface, MenuI { return false; } - this.afterAnimation(shouldOpen); + this.afterAnimation(shouldOpen, role); return true; } @@ -546,7 +546,7 @@ export class Menu implements ComponentInterface, MenuI { } private onWillStart(): Promise { - this.beforeAnimation(!this._isOpen); + this.beforeAnimation(!this._isOpen, GESTURE); return this.loadAnimation(); } @@ -628,11 +628,11 @@ export class Menu implements ComponentInterface, MenuI { this.animation .easing('cubic-bezier(0.4, 0.0, 0.6, 1)') - .onFinish(() => this.afterAnimation(shouldOpen), { oneTimeCallback: true }) + .onFinish(() => this.afterAnimation(shouldOpen, GESTURE), { oneTimeCallback: true }) .progressEnd(playTo ? 1 : 0, this._isOpen ? 1 - newStepValue : newStepValue, 300); } - private beforeAnimation(shouldOpen: boolean) { + private beforeAnimation(shouldOpen: boolean, role?: string) { assert(!this.isAnimating, '_before() should not be called while animating'); // this places the menu into the correct location before it animates in @@ -675,11 +675,11 @@ export class Menu implements ComponentInterface, MenuI { if (shouldOpen) { this.ionWillOpen.emit(); } else { - this.ionWillClose.emit(); + this.ionWillClose.emit({ role }); } } - private afterAnimation(isOpen: boolean) { + private afterAnimation(isOpen: boolean, role?: string) { // keep opening/closing the menu disabled for a touch more yet // only add listeners/css if it's enabled and isOpen // and only remove listeners/css if it's not open @@ -735,7 +735,7 @@ export class Menu implements ComponentInterface, MenuI { } // emit close event - this.ionDidClose.emit(); + this.ionDidClose.emit({ role }); // undo focus trapping so multiple menus don't collide document.removeEventListener('focus', this.handleFocus, true); @@ -771,7 +771,7 @@ export class Menu implements ComponentInterface, MenuI { * If the menu is disabled then we should * forcibly close the menu even if it is open. */ - this.afterAnimation(false); + this.afterAnimation(false, GESTURE); } } diff --git a/core/src/components/menu/test/basic/index.html b/core/src/components/menu/test/basic/index.html index 34a90240ab..4e79a4fbb2 100644 --- a/core/src/components/menu/test/basic/index.html +++ b/core/src/components/menu/test/basic/index.html @@ -51,7 +51,9 @@ - Button + + Button + Menu Item Menu Item Menu Item @@ -125,6 +127,19 @@ + + + + + + + + + + + Segment View - Basic + + + + + + + No + + + Value + + + + No + Value + + + + + Paid + + + Free + + + Top + + + + + Free + Top + + + + + Orange + + + Banana + + + Pear + + + Peach + + + Grape + + + Mango + + + Apple + + + Strawberry + + + Cherry + + + + Orange + Banana + Pear + Peach + Grape + Mango + Apple + Strawberry + Cherry + + + + + + + + + + Footer + + + + + + + diff --git a/core/src/components/segment-view/test/basic/segment-view.e2e.ts b/core/src/components/segment-view/test/basic/segment-view.e2e.ts new file mode 100644 index 0000000000..447a60be30 --- /dev/null +++ b/core/src/components/segment-view/test/basic/segment-view.e2e.ts @@ -0,0 +1,173 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('segment-view: basic'), () => { + test('should show the first content with no initial value', async ({ page }) => { + await page.setContent( + ` + + + Paid + + + Free + + + Top + + + + + Free + Top + + `, + config + ); + + const segmentContent = page.locator('ion-segment-content[id="paid"]'); + await expect(segmentContent).toBeInViewport(); + }); + + test('should show the content matching the initial value', async ({ page }) => { + await page.setContent( + ` + + + Paid + + + Free + + + Top + + + + + Free + Top + + `, + config + ); + + const segmentContent = page.locator('ion-segment-content[id="free"]'); + await expect(segmentContent).toBeInViewport(); + }); + + test('should update the content when changing the value by clicking a segment button', async ({ page }) => { + await page.setContent( + ` + + + Paid + + + Free + + + Top + + + + + Free + Top + + `, + config + ); + + await page.locator('ion-segment-button[value="top"]').click(); + + const segmentContent = page.locator('ion-segment-content[id="top"]'); + await expect(segmentContent).toBeInViewport(); + }); + }); + + test('should set correct segment button as checked when changing the value by scrolling the segment content', async ({ + page, + }) => { + await page.setContent( + ` + + + Paid + + + Free + + + Top + + + + + Free + Top + + `, + config + ); + + await page + .locator('ion-segment-view') + .evaluate( + (segmentView: HTMLIonSegmentViewElement) => !segmentView.classList.contains('segment-view-scroll-disabled') + ); + + await page.waitForChanges(); + + await page.locator('ion-segment-content[id="top"]').scrollIntoViewIfNeeded(); + + const segmentButton = page.locator('ion-segment-button[value="top"]'); + await expect(segmentButton).toHaveClass(/segment-button-checked/); + }); + + test('should set correct segment button as checked and show correct content when programmatically setting the segment vale', async ({ + page, + }) => { + await page.setContent( + ` + + + Paid + + + Free + + + Top + + + + + Free + Top + + `, + config + ); + + await page + .locator('ion-segment-view') + .evaluate( + (segmentView: HTMLIonSegmentViewElement) => !segmentView.classList.contains('segment-view-scroll-disabled') + ); + + await page.waitForChanges(); + + await page.locator('ion-segment').evaluate((segment: HTMLIonSegmentElement) => (segment.value = 'top')); + + const segmentButton = page.locator('ion-segment-button[value="top"]'); + await expect(segmentButton).toHaveClass(/segment-button-checked/); + + const segmentContent = page.locator('ion-segment-content[id="top"]'); + await expect(segmentContent).toBeInViewport(); + }); +}); diff --git a/core/src/components/segment-view/test/disabled/index.html b/core/src/components/segment-view/test/disabled/index.html new file mode 100644 index 0000000000..d19722de6d --- /dev/null +++ b/core/src/components/segment-view/test/disabled/index.html @@ -0,0 +1,103 @@ + + + + + Segment View - Disabled + + + + + + + + + + + + + + + Segment View - Disabled + + + + + + + All + + + Favorites + + + + All + Favorites + + + + + Paid + + + Free + + + Top + + + + + Free + Top + + + + + Bookmarks + + + Reading List + + + Shared Links + + + + Bookmarks + Reading List + Shared Links + + + + + diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts new file mode 100644 index 0000000000..c7dead8943 --- /dev/null +++ b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts @@ -0,0 +1,49 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across directions + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('segment-view: disabled'), () => { + test('should not have visual regressions', async ({ page }) => { + await page.goto('/src/components/segment-view/test/disabled', config); + + await expect(page).toHaveScreenshot(screenshot(`segment-view-disabled`)); + }); + }); +}); + +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('segment-view: disabled'), () => { + test('should keep button enabled even when disabled prop is set', async ({ page }) => { + await page.setContent( + ` + + + Paid + + + Free + + + Top + + + + + Free + Top + + `, + config + ); + + const segmentButton = page.locator('ion-segment-button[value="free"]'); + await expect(segmentButton).not.toHaveClass(/segment-button-disabled/); + }); + }); +}); diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..58fa6b0d0a Binary files /dev/null and b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6ad473ada4 Binary files /dev/null and b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..521946d9f1 Binary files /dev/null and b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Chrome-linux.png b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e192269dd6 Binary files /dev/null and b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Firefox-linux.png b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..1d84031d3e Binary files /dev/null and b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Safari-linux.png b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..697b8ebfe9 Binary files /dev/null and b/core/src/components/segment-view/test/disabled/segment-view.e2e.ts-snapshots/segment-view-disabled-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index 14e854953c..dfa23ca6d1 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -7,6 +7,7 @@ import { createColorClasses, hostContext } from '@utils/theme'; import { getIonTheme } from '../../global/ionic-global'; import type { Color, StyleEventDetail } from '../../interface'; +import type { SegmentViewScrollEvent } from '../segment-view/segment-view-interface'; import type { SegmentChangeEventDetail, SegmentValue } from './segment-interface'; @@ -29,6 +30,16 @@ export class Segment implements ComponentInterface { // Value before the segment is dragged private valueBeforeGesture?: SegmentValue; + private segmentViewEl?: HTMLIonSegmentViewElement | null = null; + private lastNextIndex?: number; + + /** + * Whether to update the segment view, if exists, when the value changes. + * This behavior is enabled by default, but is set false when scrolling content views + * since we don't want to "double scroll" the segment view. + */ + private triggerScrollOnValueChange?: boolean; + @Element() el!: HTMLIonSegmentElement; @State() activated = false; @@ -80,13 +91,41 @@ export class Segment implements ComponentInterface { @Prop({ mutable: true }) value?: SegmentValue; @Watch('value') - protected valueChanged(value: SegmentValue | undefined) { + protected valueChanged(value: SegmentValue | undefined, oldValue?: SegmentValue | undefined) { + // Force a value to exist if we're using a segment view + if (this.segmentViewEl && value === undefined) { + this.value = this.getButtons()[0].value; + return; + } + + if (oldValue !== undefined && value !== undefined) { + const buttons = this.getButtons(); + const previous = buttons.find((button) => button.value === oldValue); + const current = buttons.find((button) => button.value === value); + + if (previous && current) { + if (!this.segmentViewEl) { + this.checkButton(previous, current); + } else if (this.triggerScrollOnValueChange !== false) { + this.updateSegmentView(); + } + } + } else if (value !== undefined && oldValue === undefined && this.segmentViewEl) { + this.updateSegmentView(); + } + /** * `ionSelect` is emitted every time the value changes (internal or external changes). * Used by `ion-segment-button` to determine if the button should be checked. */ this.ionSelect.emit({ value }); - this.scrollActiveButtonIntoView(); + + // The scroll listener should handle scrolling the active button into view as needed + if (!this.segmentViewEl) { + this.scrollActiveButtonIntoView(); + } + + this.triggerScrollOnValueChange = undefined; } /** @@ -120,9 +159,13 @@ export class Segment implements ComponentInterface { disabledChanged() { this.gestureChanged(); - const buttons = this.getButtons(); - for (const button of buttons) { - button.disabled = this.disabled; + if (!this.segmentViewEl) { + const buttons = this.getButtons(); + for (const button of buttons) { + button.disabled = this.disabled; + } + } else { + this.segmentViewEl.disabled = this.disabled; } } @@ -134,6 +177,12 @@ export class Segment implements ComponentInterface { connectedCallback() { this.emitStyle(); + + this.segmentViewEl = this.getSegmentView(); + } + + disconnectedCallback() { + this.segmentViewEl = null; } componentWillLoad() { @@ -172,6 +221,10 @@ export class Segment implements ComponentInterface { if (this.disabled) { this.disabledChanged(); } + + // Update segment view based on the initial value, + // but do not animate the scroll + this.updateSegmentView(false); } onStart(detail: GestureDetail) { @@ -194,6 +247,7 @@ export class Segment implements ComponentInterface { if (value !== undefined) { if (this.valueBeforeGesture !== value) { this.emitValueChange(); + this.updateSegmentView(); } } this.valueBeforeGesture = undefined; @@ -210,7 +264,7 @@ export class Segment implements ComponentInterface { this.ionChange.emit({ value }); } - private getButtons() { + private getButtons(): HTMLIonSegmentButtonElement[] { return Array.from(this.el.querySelectorAll('ion-segment-button')); } @@ -226,11 +280,7 @@ export class Segment implements ComponentInterface { const buttons = this.getButtons(); buttons.forEach((button) => { - if (activated) { - button.classList.add('segment-button-activated'); - } else { - button.classList.remove('segment-button-activated'); - } + button.classList.toggle('segment-button-activated', activated); }); this.activated = activated; } @@ -295,6 +345,8 @@ export class Segment implements ComponentInterface { // Remove the transform to slide the indicator back to the button clicked currentIndicator.style.setProperty('transform', ''); + + this.scrollActiveButtonIntoView(true); }); this.value = current.value; @@ -314,6 +366,74 @@ export class Segment implements ComponentInterface { } } + private getSegmentView() { + const buttons = this.getButtons(); + // Get the first button with a contentId + const firstContentId = buttons.find((button: HTMLIonSegmentButtonElement) => button.contentId); + // Get the segment content with an id matching the button's contentId + const segmentContent = document.querySelector(`ion-segment-content[id="${firstContentId?.contentId}"]`); + // Return the segment view for that matching segment content + return segmentContent?.closest('ion-segment-view'); + } + + @Listen('ionSegmentViewScroll', { target: 'body' }) + handleSegmentViewScroll(ev: CustomEvent) { + const { scrollRatio, isManualScroll } = ev.detail; + + if (!isManualScroll) { + return; + } + + const dispatchedFrom = ev.target as HTMLElement; + const segmentViewEl = this.segmentViewEl as EventTarget; + const segmentEl = this.el; + + // Only update the indicator if the event was dispatched from the correct segment view + if (ev.composedPath().includes(segmentViewEl) || dispatchedFrom?.contains(segmentEl)) { + const buttons = this.getButtons(); + + // If no buttons are found or there is no value set then do nothing + if (!buttons.length) return; + + const index = buttons.findIndex((button) => button.value === this.value); + const current = buttons[index]; + + const nextIndex = Math.round(scrollRatio * (buttons.length - 1)); + + if (this.lastNextIndex === undefined || this.lastNextIndex !== nextIndex) { + this.lastNextIndex = nextIndex; + this.triggerScrollOnValueChange = false; + + this.checkButton(current, buttons[nextIndex]); + this.emitValueChange(); + } + } + } + + /** + * Finds the related segment view and sets its current content + * based on the selected segment button. This method + * should be called on initial load of the segment, + * after the gesture is completed (if dragging between segments) + * and when a segment button is clicked directly. + */ + private updateSegmentView(smoothScroll = true) { + const buttons = this.getButtons(); + const button = buttons.find((btn) => btn.value === this.value); + + // If the button does not have a contentId then there is + // no associated segment view to update + if (!button?.contentId) { + return; + } + + const segmentView = this.segmentViewEl; + + if (segmentView) { + segmentView.setContent(button.contentId, smoothScroll); + } + } + private scrollActiveButtonIntoView(smoothScroll = true) { const { scrollable, value, el } = this; @@ -494,7 +614,13 @@ export class Segment implements ComponentInterface { this.emitValueChange(); } - if (this.scrollable || !this.swipeGesture) { + if (this.segmentViewEl) { + this.updateSegmentView(); + + if (this.scrollable && previous) { + this.checkButton(previous, current); + } + } else if (this.scrollable || !this.swipeGesture) { if (previous) { this.checkButton(previous, current); } else { diff --git a/core/src/components/select-modal/select-modal-interface.ts b/core/src/components/select-modal/select-modal-interface.ts new file mode 100644 index 0000000000..2005400cb8 --- /dev/null +++ b/core/src/components/select-modal/select-modal-interface.ts @@ -0,0 +1,8 @@ +export interface SelectModalOption { + text: string; + value: string; + disabled: boolean; + checked: boolean; + cssClass?: string | string[]; + handler?: (value: any) => boolean | void | { [key: string]: any }; +} diff --git a/core/src/components/select-modal/select-modal.ios.scss b/core/src/components/select-modal/select-modal.ios.scss new file mode 100644 index 0000000000..7e60b693fc --- /dev/null +++ b/core/src/components/select-modal/select-modal.ios.scss @@ -0,0 +1 @@ +@import "./select-modal"; diff --git a/core/src/components/select-modal/select-modal.md.scss b/core/src/components/select-modal/select-modal.md.scss new file mode 100644 index 0000000000..be64d9ae90 --- /dev/null +++ b/core/src/components/select-modal/select-modal.md.scss @@ -0,0 +1,30 @@ +@import "./select-modal"; +@import "../../themes/mixins.scss"; +@import "../item/item.md.vars"; + +ion-list ion-radio::part(container) { + display: none; +} + +ion-list ion-radio::part(label) { + @include margin(0); +} + +ion-item { + --inner-border-width: 0; +} + +.item-radio-checked { + --background: #{ion-color(primary, base, 0.08)}; + --background-focused: #{ion-color(primary, base)}; + --background-focused-opacity: 0.2; + --background-hover: #{ion-color(primary, base)}; + --background-hover-opacity: 0.12; +} + +.item-checkbox-checked { + --background-activated: #{$item-md-color}; + --background-focused: #{$item-md-color}; + --background-hover: #{$item-md-color}; + --color: #{ion-color(primary, base)}; +} diff --git a/core/src/components/select-modal/select-modal.scss b/core/src/components/select-modal/select-modal.scss new file mode 100644 index 0000000000..683ae23fae --- /dev/null +++ b/core/src/components/select-modal/select-modal.scss @@ -0,0 +1,3 @@ +:host { + height: 100%; +} diff --git a/core/src/components/select-modal/select-modal.tsx b/core/src/components/select-modal/select-modal.tsx new file mode 100644 index 0000000000..ad92786043 --- /dev/null +++ b/core/src/components/select-modal/select-modal.tsx @@ -0,0 +1,161 @@ +import { getIonMode } from '@global/ionic-global'; +import type { ComponentInterface } from '@stencil/core'; +import { Component, Element, Host, Prop, forceUpdate, h } from '@stencil/core'; +import { safeCall } from '@utils/overlays'; +import { getClassMap } from '@utils/theme'; + +import type { CheckboxCustomEvent } from '../checkbox/checkbox-interface'; +import type { RadioGroupCustomEvent } from '../radio-group/radio-group-interface'; + +import type { SelectModalOption } from './select-modal-interface'; + +@Component({ + tag: 'ion-select-modal', + styleUrls: { + ios: 'select-modal.ios.scss', + md: 'select-modal.md.scss', + ionic: 'select-modal.md.scss', + }, + scoped: true, +}) +export class SelectModal implements ComponentInterface { + @Element() el!: HTMLIonSelectModalElement; + + @Prop() header?: string; + + @Prop() multiple?: boolean; + + @Prop() options: SelectModalOption[] = []; + + private closeModal() { + const modal = this.el.closest('ion-modal'); + + if (modal) { + modal.dismiss(); + } + } + + private findOptionFromEvent(ev: CheckboxCustomEvent | RadioGroupCustomEvent) { + const { options } = this; + return options.find((o) => o.value === ev.target.value); + } + + private getValues(ev?: CheckboxCustomEvent | RadioGroupCustomEvent): string | string[] | undefined { + const { multiple, options } = this; + + if (multiple) { + // this is a modal with checkboxes (multiple value select) + // return an array of all the checked values + return options.filter((o) => o.checked).map((o) => o.value); + } + + // this is a modal with radio buttons (single value select) + // return the value that was clicked, otherwise undefined + const option = ev ? this.findOptionFromEvent(ev) : null; + return option ? option.value : undefined; + } + + private callOptionHandler(ev: CheckboxCustomEvent | RadioGroupCustomEvent) { + const option = this.findOptionFromEvent(ev); + const values = this.getValues(ev); + if (option?.handler) { + safeCall(option.handler, values); + } + } + + private setChecked(ev: CheckboxCustomEvent): void { + const { multiple } = this; + const option = this.findOptionFromEvent(ev); + + // this is a modal with checkboxes (multiple value select) + // we need to set the checked value for this option + if (multiple && option) { + option.checked = ev.detail.checked; + } + } + + private renderRadioOptions() { + const checked = this.options.filter((o) => o.checked).map((o) => o.value)[0]; + + return ( + this.callOptionHandler(ev)}> + {this.options.map((option) => ( + + this.closeModal()} + onKeyUp={(ev) => { + if (ev.key === ' ') { + /** + * Selecting a radio option with keyboard navigation, + * either through the Enter or Space keys, should + * dismiss the modal. + */ + this.closeModal(); + } + }} + > + {option.text} + + + ))} + + ); + } + + private renderCheckboxOptions() { + return this.options.map((option) => ( + + { + this.setChecked(ev); + this.callOptionHandler(ev); + // TODO FW-4784 + forceUpdate(this); + }} + > + {option.text} + + + )); + } + + render() { + return ( + + + + {this.header !== undefined && {this.header}} + + + this.closeModal()}>Close + + + + + {this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()} + + + ); + } +} diff --git a/core/src/components/select-modal/test/basic/index.html b/core/src/components/select-modal/test/basic/index.html new file mode 100644 index 0000000000..8ddb3b54f0 --- /dev/null +++ b/core/src/components/select-modal/test/basic/index.html @@ -0,0 +1,40 @@ + + + + + Select - Modal + + + + + + + + + + + + + Select Modal - Basic + + + + + + + + + + + + + diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts b/core/src/components/select-modal/test/basic/select-modal.e2e.ts new file mode 100644 index 0000000000..126082712c --- /dev/null +++ b/core/src/components/select-modal/test/basic/select-modal.e2e.ts @@ -0,0 +1,101 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +import type { SelectModalOption } from '../../select-modal-interface'; +import { SelectModalPage } from '../fixtures'; + +const options: SelectModalOption[] = [ + { value: 'apple', text: 'Apple', disabled: false, checked: false }, + { value: 'banana', text: 'Banana', disabled: false, checked: false }, +]; + +const checkedOptions: SelectModalOption[] = [ + { value: 'apple', text: 'Apple', disabled: false, checked: true }, + { value: 'banana', text: 'Banana', disabled: false, checked: false }, +]; + +/** + * This behavior does not vary across modes/directions. + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('select-modal: basic'), () => { + test.beforeEach(({ browserName }) => { + test.skip(browserName === 'webkit', 'ROU-5437'); + }); + + test.describe('single selection', () => { + let selectModalPage: SelectModalPage; + + test.beforeEach(async ({ page }) => { + selectModalPage = new SelectModalPage(page); + }); + + test('clicking an unselected option should dismiss the modal', async () => { + await selectModalPage.setup(config, options, false); + + await selectModalPage.clickOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('clicking a selected option should dismiss the modal', async () => { + await selectModalPage.setup(config, checkedOptions, false); + + await selectModalPage.clickOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('pressing Space on an unselected option should dismiss the modal', async () => { + await selectModalPage.setup(config, options, false); + + await selectModalPage.pressSpaceOnOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('pressing Space on a selected option should dismiss the modal', async ({ browserName }) => { + test.skip(browserName === 'firefox', 'Same behavior as ROU-5437'); + + await selectModalPage.setup(config, checkedOptions, false); + + await selectModalPage.pressSpaceOnOption('apple'); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + + test('clicking the close button should dismiss the modal', async () => { + await selectModalPage.setup(config, options, false); + + const closeButton = selectModalPage.modal.locator('ion-header ion-toolbar ion-button'); + await closeButton.click(); + await selectModalPage.ionModalDidDismiss.next(); + await expect(selectModalPage.modal).not.toBeVisible(); + }); + }); + }); +}); + +/** + * This behavior does not vary across directions. + * The components used inside of `ion-select-modal` + * do have RTL logic, but those are tested in their + * respective component test files. + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('select-modal: rendering'), () => { + let selectModalPage: SelectModalPage; + + test.beforeEach(async ({ page }) => { + selectModalPage = new SelectModalPage(page); + }); + test('should not have visual regressions with single selection', async () => { + await selectModalPage.setup(config, checkedOptions, false); + await selectModalPage.screenshot(screenshot, 'select-modal-diff'); + }); + test('should not have visual regressions with multiple selection', async () => { + await selectModalPage.setup(config, checkedOptions, true); + await selectModalPage.screenshot(screenshot, 'select-modal-multiple-diff'); + }); + }); +}); diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..72f5453ea8 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b69898dc6d Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..05425333c5 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..7613855bd6 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..0d71098bb5 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..c190809cd8 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..084a1cd1dc Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..73ad712007 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..aaf5d78f03 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..8482f64dba Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..29c3709ef5 Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..b55b7bc77e Binary files /dev/null and b/core/src/components/select-modal/test/basic/select-modal.e2e.ts-snapshots/select-modal-multiple-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/select-modal/test/fixtures.ts b/core/src/components/select-modal/test/fixtures.ts new file mode 100644 index 0000000000..2058848aa8 --- /dev/null +++ b/core/src/components/select-modal/test/fixtures.ts @@ -0,0 +1,73 @@ +import { expect } from '@playwright/test'; +import type { E2EPage, E2ELocator, EventSpy, E2EPageOptions, ScreenshotFn } from '@utils/test/playwright'; + +import type { SelectModalOption } from '../select-modal-interface'; + +export class SelectModalPage { + private page: E2EPage; + private multiple?: boolean; + private options: SelectModalOption[] = []; + + // Locators + modal!: E2ELocator; + selectModal!: E2ELocator; + + // Event spies + ionModalDidDismiss!: EventSpy; + + constructor(page: E2EPage) { + this.page = page; + } + + async setup(config: E2EPageOptions, options: SelectModalOption[], multiple = false) { + const { page } = this; + + await page.setContent( + ` + + + + + `, + config + ); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + this.modal = page.locator('ion-modal'); + this.selectModal = page.locator('ion-select-modal'); + this.multiple = multiple; + this.options = options; + + await this.modal.evaluate((modal: HTMLIonModalElement) => modal.present()); + + await ionModalDidPresent.next(); + } + + async screenshot(screenshot: ScreenshotFn, name: string) { + await expect(this.selectModal).toHaveScreenshot(screenshot(name)); + } + + async clickOption(value: string) { + const option = this.getOption(value); + await option.click(); + } + + async pressSpaceOnOption(value: string) { + const option = this.getOption(value); + await option.press('Space'); + } + + private getOption(value: string) { + const { multiple, selectModal } = this; + const selector = multiple ? 'ion-checkbox' : 'ion-radio'; + const index = this.options.findIndex((o) => o.value === value); + + return selectModal.locator(selector).nth(index); + } +} diff --git a/core/src/components/select/select-interface.ts b/core/src/components/select/select-interface.ts index 4fa2a18828..8e65377a82 100644 --- a/core/src/components/select/select-interface.ts +++ b/core/src/components/select/select-interface.ts @@ -1,4 +1,4 @@ -export type SelectInterface = 'action-sheet' | 'popover' | 'alert'; +export type SelectInterface = 'action-sheet' | 'popover' | 'alert' | 'modal'; export type SelectCompareFn = (currentValue: any, compareValue: any) => boolean; diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index f3d5c09833..ab0de8f0f3 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -5,7 +5,7 @@ import type { NotchController } from '@utils/forms'; import { compareOptions, createNotchController, isOptionSelected } from '@utils/forms'; import { focusVisibleElement, renderHiddenInput, inheritAttributes } from '@utils/helpers'; import type { Attributes } from '@utils/helpers'; -import { actionSheetController, alertController, popoverController } from '@utils/overlays'; +import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays'; import type { OverlaySelect } from '@utils/overlays-interface'; import { isRTL } from '@utils/rtl'; import { createColorClasses, hostContext } from '@utils/theme'; @@ -21,6 +21,7 @@ import type { CssClassMap, PopoverOptions, StyleEventDetail, + ModalOptions, } from '../../interface'; import type { ActionSheetButton } from '../action-sheet/action-sheet-interface'; import type { AlertInput } from '../alert/alert-interface'; @@ -102,15 +103,15 @@ export class Select implements ComponentInterface { @Prop() fill?: 'outline' | 'solid'; /** - * The interface the select should use: `action-sheet`, `popover` or `alert`. + * The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`. */ @Prop() interface: SelectInterface = 'alert'; /** * Any additional options that the `alert`, `action-sheet` or `popover` interface * can take. See the [ion-alert docs](./alert), the - * [ion-action-sheet docs](./action-sheet) and the - * [ion-popover docs](./popover) for the + * [ion-action-sheet docs](./action-sheet), the + * [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the * create options for each interface. * * Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface. @@ -322,9 +323,9 @@ export class Select implements ComponentInterface { await overlay.present(); - // focus selected option for popovers - if (this.interface === 'popover') { - const indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value); + // focus selected option for popovers and modals + if (this.interface === 'popover' || this.interface === 'modal') { + const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value); if (indexOfSelected > -1) { const selectedItem = overlay.querySelector( @@ -332,8 +333,6 @@ export class Select implements ComponentInterface { ); if (selectedItem) { - focusVisibleElement(selectedItem); - /** * Browsers such as Firefox do not * correctly delegate focus when manually @@ -345,10 +344,17 @@ export class Select implements ComponentInterface { * we only need to worry about those two components * when focusing. */ - const interactiveEl = selectedItem.querySelector('ion-radio, ion-checkbox'); + const interactiveEl = selectedItem.querySelector('ion-radio, ion-checkbox') as + | HTMLIonRadioElement + | HTMLIonCheckboxElement + | null; if (interactiveEl) { - interactiveEl.focus(); + // Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling + // and removing `ion-focused` style + interactiveEl.setFocus(); } + + focusVisibleElement(selectedItem); } } else { /** @@ -356,14 +362,18 @@ export class Select implements ComponentInterface { */ const firstEnabledOption = overlay.querySelector( 'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)' - ); - if (firstEnabledOption) { - focusVisibleElement(firstEnabledOption.closest('ion-item')!); + ) as HTMLIonRadioElement | HTMLIonCheckboxElement | null; + if (firstEnabledOption) { /** * Focus the option for the same reason as we do above. + * + * Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling + * and removing `ion-focused` style */ - firstEnabledOption.focus(); + firstEnabledOption.setFocus(); + + focusVisibleElement(firstEnabledOption.closest('ion-item')!); } } } @@ -393,6 +403,9 @@ export class Select implements ComponentInterface { if (selectInterface === 'popover') { return this.openPopover(ev!); } + if (selectInterface === 'modal') { + return this.openModal(); + } return this.openAlert(); } @@ -410,7 +423,13 @@ export class Select implements ComponentInterface { case 'popover': const popover = overlay.querySelector('ion-select-popover'); if (popover) { - popover.options = this.createPopoverOptions(childOpts, value); + popover.options = this.createOverlaySelectOptions(childOpts, value); + } + break; + case 'modal': + const modal = overlay.querySelector('ion-select-modal'); + if (modal) { + modal.options = this.createOverlaySelectOptions(childOpts, value); } break; case 'alert': @@ -479,7 +498,7 @@ export class Select implements ComponentInterface { return alertInputs; } - private createPopoverOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] { + private createOverlaySelectOptions(data: HTMLIonSelectOptionElement[], selectValue: any): SelectPopoverOption[] { const popoverOptions = data.map((option) => { const value = getOptionValue(option); @@ -557,7 +576,7 @@ export class Select implements ComponentInterface { message: interfaceOptions.message, multiple, value, - options: this.createPopoverOptions(this.childOpts, value), + options: this.createOverlaySelectOptions(this.childOpts, value), }, }; @@ -651,6 +670,40 @@ export class Select implements ComponentInterface { return alertController.create(alertOpts); } + private openModal() { + const { multiple, value, interfaceOptions } = this; + const theme = getIonTheme(this); + + const modalOpts: ModalOptions = { + ...interfaceOptions, + mode: theme, + + cssClass: ['select-modal', interfaceOptions.cssClass], + component: 'ion-select-modal', + componentProps: { + header: interfaceOptions.header, + multiple, + value, + options: this.createOverlaySelectOptions(this.childOpts, value), + }, + }; + + /** + * Workaround for Stencil to autodefine + * ion-select-modal and ion-modal when + * using Custom Elements build. + */ + // eslint-disable-next-line + if (false) { + // eslint-disable-next-line + // @ts-ignore + document.createElement('ion-select-modal'); + document.createElement('ion-modal'); + } + + return modalController.create(modalOpts); + } + /** * Close the select interface. */ diff --git a/core/src/components/select/test/basic/index.html b/core/src/components/select/test/basic/index.html index 421dd4505f..c958d08f90 100644 --- a/core/src/components/select/test/basic/index.html +++ b/core/src/components/select/test/basic/index.html @@ -51,6 +51,14 @@ Pears + + + + Apples + Oranges + Pears + + @@ -76,6 +84,15 @@ Honey Badger + + + + Bird + Cat + Dog + Honey Badger + + @@ -124,6 +141,16 @@ Onions + + + + Pepperoni + Bacon + Extra Cheese + Mushrooms + Onions + + @@ -152,6 +179,14 @@ message: '$1.50 charge for every topping', }; customActionSheetSelect.interfaceOptions = customActionSheetOptions; + + var customModalSelect = document.getElementById('customModalSelect'); + var customModalSheetOptions = { + header: 'Pizza Toppings', + breakpoints: [0.5], + initialBreakpoint: 0.5, + }; + customModalSelect.interfaceOptions = customModalSheetOptions; diff --git a/core/src/components/select/test/basic/select.e2e.ts b/core/src/components/select/test/basic/select.e2e.ts index d44c8ca761..3574772849 100644 --- a/core/src/components/select/test/basic/select.e2e.ts +++ b/core/src/components/select/test/basic/select.e2e.ts @@ -58,6 +58,24 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { await expect(popover).toBeVisible(); }); }); + + test.describe('select: modal', () => { + test('it should open a modal select', async ({ page }) => { + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#customModalSelect'); + + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // select has no value, so first option should be focused by default + const modalOption1 = modal.locator('.select-interface-option:first-of-type ion-radio'); + await expect(modalOption1).toBeFocused(); + + await expect(modal).toBeVisible(); + }); + }); }); }); diff --git a/core/src/utils/menu-controller/index.ts b/core/src/utils/menu-controller/index.ts index aa09e8a825..dca6c5bc8c 100644 --- a/core/src/utils/menu-controller/index.ts +++ b/core/src/utils/menu-controller/index.ts @@ -174,7 +174,7 @@ const createMenuController = (): MenuControllerI => { } }; - const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean): Promise => { + const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean, role: string): Promise => { if (isAnimatingSync()) { return false; } @@ -184,7 +184,7 @@ const createMenuController = (): MenuControllerI => { await openedMenu.setOpen(false, false); } } - return menu._setOpen(shouldOpen, animated); + return menu._setOpen(shouldOpen, animated, role); }; const _createAnimation = (type: string, menuCmp: MenuI) => { diff --git a/core/src/utils/overlays-interface.ts b/core/src/utils/overlays-interface.ts index d9f8a9e7ff..06650cd20f 100644 --- a/core/src/utils/overlays-interface.ts +++ b/core/src/utils/overlays-interface.ts @@ -77,4 +77,8 @@ export interface HTMLIonOverlayElement extends HTMLStencilElement { present: () => Promise; } -export type OverlaySelect = HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement; +export type OverlaySelect = + | HTMLIonActionSheetElement + | HTMLIonAlertElement + | HTMLIonPopoverElement + | HTMLIonModalElement; diff --git a/lerna.json b/lerna.json index 6b73eec8d4..9573d91b5e 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "core", "packages/*" ], - "version": "8.3.4" + "version": "8.4.0" } \ No newline at end of file diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index 8e18462ddf..f76d843625 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index 3a9a06112a..d076c1944e 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular-server", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.4" + "@ionic/core": "^8.4.0" }, "devDependencies": { "@angular-eslint/eslint-plugin": "^16.0.0", @@ -1031,9 +1031,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -7188,9 +7188,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index ceb075f4a7..2ab94afe04 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "8.3.4", + "version": "8.4.0", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^8.3.4" + "@ionic/core": "^8.4.0" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 5899927ba3..623f30b8ae 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + + +### Features + +* **menu:** pass role to ionWillClose and ionDidClose events ([#29954](https://github.com/ionic-team/ionic-framework/issues/29954)) ([ee2fa19](https://github.com/ionic-team/ionic-framework/commit/ee2fa19a1e9f09d492c7c08340d95ba6a56ebb2b)) +* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6)) +* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040)) + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index aaf40af261..99ae2425d5 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.4", + "@ionic/core": "^8.4.0", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -1398,9 +1398,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -9820,9 +9820,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/angular/package.json b/packages/angular/package.json index cf3edb1374..e717fcb1fd 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "8.3.4", + "version": "8.4.0", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -47,7 +47,7 @@ } }, "dependencies": { - "@ionic/core": "^8.3.4", + "@ionic/core": "^8.4.0", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/angular/src/directives/proxies-list.ts b/packages/angular/src/directives/proxies-list.ts index 1874d0bfe2..b61d09669c 100644 --- a/packages/angular/src/directives/proxies-list.ts +++ b/packages/angular/src/directives/proxies-list.ts @@ -69,7 +69,10 @@ export const DIRECTIVES = [ d.IonSearchbar, d.IonSegment, d.IonSegmentButton, + d.IonSegmentContent, + d.IonSegmentView, d.IonSelect, + d.IonSelectModal, d.IonSelectOption, d.IonSkeletonText, d.IonSpinner, diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 45463539ff..daed64577b 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -1338,6 +1338,8 @@ export class IonMenu { } +import type { MenuCloseEventDetail as IIonMenuMenuCloseEventDetail } from '@ionic/core'; + export declare interface IonMenu extends Components.IonMenu { /** * Emitted when the menu is about to be opened. @@ -1346,7 +1348,7 @@ export declare interface IonMenu extends Components.IonMenu { /** * Emitted when the menu is about to be closed. */ - ionWillClose: EventEmitter>; + ionWillClose: EventEmitter>; /** * Emitted when the menu is open. */ @@ -1354,7 +1356,7 @@ export declare interface IonMenu extends Components.IonMenu { /** * Emitted when the menu is closed. */ - ionDidClose: EventEmitter>; + ionDidClose: EventEmitter>; } @@ -1991,14 +1993,14 @@ This event will not emit when programmatically setting the `value` property. @ProxyCmp({ - inputs: ['disabled', 'layout', 'mode', 'theme', 'type', 'value'] + inputs: ['contentId', 'disabled', 'layout', 'mode', 'theme', 'type', 'value'] }) @Component({ selector: 'ion-segment-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'layout', 'mode', 'theme', 'type', 'value'], + inputs: ['contentId', 'disabled', 'layout', 'mode', 'theme', 'type', 'value'], }) export class IonSegmentButton { protected el: HTMLElement; @@ -2012,6 +2014,57 @@ export class IonSegmentButton { export declare interface IonSegmentButton extends Components.IonSegmentButton {} +@ProxyCmp({ +}) +@Component({ + selector: 'ion-segment-content', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], +}) +export class IonSegmentContent { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonSegmentContent extends Components.IonSegmentContent {} + + +@ProxyCmp({ + inputs: ['disabled'] +}) +@Component({ + selector: 'ion-segment-view', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled'], +}) +export class IonSegmentView { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionSegmentViewScroll']); + } +} + + +import type { SegmentViewScrollEvent as IIonSegmentViewSegmentViewScrollEvent } from '@ionic/core'; + +export declare interface IonSegmentView extends Components.IonSegmentView { + /** + * Emitted when the segment view is scrolled. + */ + ionSegmentViewScroll: EventEmitter>; +} + + @ProxyCmp({ inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'theme', 'toggleIcon', 'value'], methods: ['open'] @@ -2061,6 +2114,28 @@ This event will not emit when programmatically setting the `value` property. } +@ProxyCmp({ + inputs: ['header', 'multiple', 'options'] +}) +@Component({ + selector: 'ion-select-modal', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['header', 'multiple', 'options'], +}) +export class IonSelectModal { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonSelectModal extends Components.IonSelectModal {} + + @ProxyCmp({ inputs: ['disabled', 'mode', 'theme', 'value'] }) diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index d171b2c0ff..df589497e9 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -65,6 +65,9 @@ import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/compon import { defineCustomElement as defineIonRippleEffect } from '@ionic/core/components/ion-ripple-effect.js'; import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-row.js'; import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js'; +import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js'; +import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js'; +import { defineCustomElement as defineIonSelectModal } from '@ionic/core/components/ion-select-modal.js'; import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js'; import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; @@ -1322,6 +1325,8 @@ export class IonMenu { } +import type { MenuCloseEventDetail as IIonMenuMenuCloseEventDetail } from '@ionic/core/components'; + export declare interface IonMenu extends Components.IonMenu { /** * Emitted when the menu is about to be opened. @@ -1330,7 +1335,7 @@ export declare interface IonMenu extends Components.IonMenu { /** * Emitted when the menu is about to be closed. */ - ionWillClose: EventEmitter>; + ionWillClose: EventEmitter>; /** * Emitted when the menu is open. */ @@ -1338,7 +1343,7 @@ export declare interface IonMenu extends Components.IonMenu { /** * Emitted when the menu is closed. */ - ionDidClose: EventEmitter>; + ionDidClose: EventEmitter>; } @@ -1822,14 +1827,14 @@ export declare interface IonRow extends Components.IonRow {} @ProxyCmp({ defineCustomElementFn: defineIonSegmentButton, - inputs: ['disabled', 'layout', 'mode', 'theme', 'type', 'value'] + inputs: ['contentId', 'disabled', 'layout', 'mode', 'theme', 'type', 'value'] }) @Component({ selector: 'ion-segment-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'layout', 'mode', 'theme', 'type', 'value'], + inputs: ['contentId', 'disabled', 'layout', 'mode', 'theme', 'type', 'value'], standalone: true }) export class IonSegmentButton { @@ -1844,6 +1849,85 @@ export class IonSegmentButton { export declare interface IonSegmentButton extends Components.IonSegmentButton {} +@ProxyCmp({ + defineCustomElementFn: defineIonSegmentContent +}) +@Component({ + selector: 'ion-segment-content', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: [], + standalone: true +}) +export class IonSegmentContent { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonSegmentContent extends Components.IonSegmentContent {} + + +@ProxyCmp({ + defineCustomElementFn: defineIonSegmentView, + inputs: ['disabled'] +}) +@Component({ + selector: 'ion-segment-view', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['disabled'], + standalone: true +}) +export class IonSegmentView { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + proxyOutputs(this, this.el, ['ionSegmentViewScroll']); + } +} + + +import type { SegmentViewScrollEvent as IIonSegmentViewSegmentViewScrollEvent } from '@ionic/core/components'; + +export declare interface IonSegmentView extends Components.IonSegmentView { + /** + * Emitted when the segment view is scrolled. + */ + ionSegmentViewScroll: EventEmitter>; +} + + +@ProxyCmp({ + defineCustomElementFn: defineIonSelectModal, + inputs: ['header', 'multiple', 'options'] +}) +@Component({ + selector: 'ion-select-modal', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['header', 'multiple', 'options'], + standalone: true +}) +export class IonSelectModal { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonSelectModal extends Components.IonSelectModal {} + + @ProxyCmp({ defineCustomElementFn: defineIonSelectOption, inputs: ['disabled', 'mode', 'theme', 'value'] diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md index 01d59dbdfa..b4fa7afd0f 100644 --- a/packages/docs/CHANGELOG.md +++ b/packages/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) **Note:** Version bump only for package @ionic/docs diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index b2ec110251..cecd55ac06 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/docs", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT" } } diff --git a/packages/docs/package.json b/packages/docs/package.json index 9402ac8627..ecfb015d6e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "8.3.4", + "version": "8.4.0", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 182220662a..d4fbfb89f7 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index 597890f036..eec354f3d5 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react-router", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { - "@ionic/react": "^8.3.4", + "@ionic/react": "^8.4.0", "tslib": "*" }, "devDependencies": { @@ -238,9 +238,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -414,11 +414,11 @@ } }, "node_modules/@ionic/react": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.4.tgz", - "integrity": "sha512-CIoTHg/1nJJN11IjmsUqeQB1nIP4SxQyo2nBH+MhzeVCMv8Tj00Y4rU/9RYzKfRI7Zfsi9MOwVrwpGhPWi4KWA==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.4.0.tgz", + "integrity": "sha512-wCtixCwf673Qnes1uGxmRoyUP4FnGtEyUVwtkcfj9IBrPUbw641Ws8J4jRjQ2rOO1WkWkSCeHKnd+KYCqyulZg==", "dependencies": { - "@ionic/core": "8.3.4", + "@ionic/core": "8.4.0", "ionicons": "^7.0.0", "tslib": "*" }, @@ -4057,9 +4057,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -4163,11 +4163,11 @@ "requires": {} }, "@ionic/react": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.4.tgz", - "integrity": "sha512-CIoTHg/1nJJN11IjmsUqeQB1nIP4SxQyo2nBH+MhzeVCMv8Tj00Y4rU/9RYzKfRI7Zfsi9MOwVrwpGhPWi4KWA==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.4.0.tgz", + "integrity": "sha512-wCtixCwf673Qnes1uGxmRoyUP4FnGtEyUVwtkcfj9IBrPUbw641Ws8J4jRjQ2rOO1WkWkSCeHKnd+KYCqyulZg==", "requires": { - "@ionic/core": "8.3.4", + "@ionic/core": "8.4.0", "ionicons": "^7.0.0", "tslib": "*" } diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 79225e3bfb..cfcd1a4e45 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "8.3.4", + "version": "8.4.0", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -36,7 +36,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^8.3.4", + "@ionic/react": "^8.4.0", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index f9f691caab..bcb4d07e5c 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + + +### Features + +* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6)) +* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040)) + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) **Note:** Version bump only for package @ionic/react diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 232c3805ca..05689bce96 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.4", + "@ionic/core": "^8.4.0", "ionicons": "^7.0.0", "tslib": "*" }, @@ -736,9 +736,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -12315,9 +12315,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/react/package.json b/packages/react/package.json index 59b96e911f..42b40b1483 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "8.3.4", + "version": "8.4.0", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -39,7 +39,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^8.3.4", + "@ionic/core": "^8.4.0", "ionicons": "^7.0.0", "tslib": "*" }, diff --git a/packages/react/src/components/proxies.ts b/packages/react/src/components/proxies.ts index 05800f3877..a9f1416ee2 100644 --- a/packages/react/src/components/proxies.ts +++ b/packages/react/src/components/proxies.ts @@ -61,7 +61,10 @@ import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion- import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js'; import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js'; import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js'; +import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js'; +import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js'; import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js'; +import { defineCustomElement as defineIonSelectModal } from '@ionic/core/components/ion-select-modal.js'; import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js'; import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; @@ -130,7 +133,10 @@ export const IonRow = /*@__PURE__*/createReactComponent('ion-searchbar', undefined, undefined, defineIonSearchbar); export const IonSegment = /*@__PURE__*/createReactComponent('ion-segment', undefined, undefined, defineIonSegment); export const IonSegmentButton = /*@__PURE__*/createReactComponent('ion-segment-button', undefined, undefined, defineIonSegmentButton); +export const IonSegmentContent = /*@__PURE__*/createReactComponent('ion-segment-content', undefined, undefined, defineIonSegmentContent); +export const IonSegmentView = /*@__PURE__*/createReactComponent('ion-segment-view', undefined, undefined, defineIonSegmentView); export const IonSelect = /*@__PURE__*/createReactComponent('ion-select', undefined, undefined, defineIonSelect); +export const IonSelectModal = /*@__PURE__*/createReactComponent('ion-select-modal', undefined, undefined, defineIonSelectModal); export const IonSelectOption = /*@__PURE__*/createReactComponent('ion-select-option', undefined, undefined, defineIonSelectOption); export const IonSkeletonText = /*@__PURE__*/createReactComponent('ion-skeleton-text', undefined, undefined, defineIonSkeletonText); export const IonSpinner = /*@__PURE__*/createReactComponent('ion-spinner', undefined, undefined, defineIonSpinner); diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index b8e456b3ce..a761ceda1e 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + +**Note:** Version bump only for package @ionic/vue-router + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 6e3138dc0d..de31e28449 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue-router", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { - "@ionic/vue": "^8.3.4" + "@ionic/vue": "^8.4.0" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", @@ -661,9 +661,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -852,11 +852,11 @@ } }, "node_modules/@ionic/vue": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.3.4.tgz", - "integrity": "sha512-s7P5mTd078CbPK2dpIxxWGNirQacG3sXhJulJ1L0J6+6VI+HHHLKy4ueTRgPl5GLDmjGgdMCBuYu1n4K2nNXFg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.4.0.tgz", + "integrity": "sha512-mtSerl9oC21d6xv1q+QuGm61IzJbqpkWbt0lQryXZ3kK1/aVVOnHAN5bX8tPPUN2ALA7CyTWXaCvhxceRV/paA==", "dependencies": { - "@ionic/core": "8.3.4", + "@ionic/core": "8.4.0", "ionicons": "^7.0.0" } }, @@ -7878,9 +7878,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -7993,11 +7993,11 @@ "requires": {} }, "@ionic/vue": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.3.4.tgz", - "integrity": "sha512-s7P5mTd078CbPK2dpIxxWGNirQacG3sXhJulJ1L0J6+6VI+HHHLKy4ueTRgPl5GLDmjGgdMCBuYu1n4K2nNXFg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.4.0.tgz", + "integrity": "sha512-mtSerl9oC21d6xv1q+QuGm61IzJbqpkWbt0lQryXZ3kK1/aVVOnHAN5bX8tPPUN2ALA7CyTWXaCvhxceRV/paA==", "requires": { - "@ionic/core": "8.3.4", + "@ionic/core": "8.4.0", "ionicons": "^7.0.0" } }, diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index f6a6991066..7cb544beab 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "8.3.4", + "version": "8.4.0", "description": "Vue Router integration for @ionic/vue", "scripts": { "test.spec": "jest", @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/ionic-team/ionic-framework#readme", "dependencies": { - "@ionic/vue": "^8.3.4" + "@ionic/vue": "^8.4.0" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 59e84626d9..f7db26db10 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04) + + +### Features + +* **segment-view:** adds support for new `ion-segment-view` component ([#29969](https://github.com/ionic-team/ionic-framework/issues/29969)) ([89508fb](https://github.com/ionic-team/ionic-framework/commit/89508fb89172900b1d11cc3fc18883f57a7fbab6)) +* **select:** add `modal` as interface ([#29972](https://github.com/ionic-team/ionic-framework/issues/29972)) ([3628ea8](https://github.com/ionic-team/ionic-framework/commit/3628ea875a66a717783de5e0a4df440872339040)) + + + + + ## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index 2adae2a2ac..5733d9b5a6 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "8.3.4", + "version": "8.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "8.3.4", + "version": "8.4.0", "license": "MIT", "dependencies": { - "@ionic/core": "^8.3.4", + "@ionic/core": "^8.4.0", "ionicons": "^7.0.0" }, "devDependencies": { @@ -208,9 +208,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -3970,9 +3970,9 @@ "dev": true }, "@ionic/core": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.4.tgz", - "integrity": "sha512-MkgaQ9+oQwj3AK/i25MkLgKUUH3/nvNjd4YlmUyFjPG7l2IBHsrLlSmuFGCyniB+doI9Hynu3T9CZP7NS4RjRQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.4.0.tgz", + "integrity": "sha512-mZ2Ni9QByFGWBNr5W/F/nyPV+cXLhK+6W5BRziy7QPX6YIS57KH8FpY+CjE7BEcpE78anyY49bZt3eOWcES02g==", "requires": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", diff --git a/packages/vue/package.json b/packages/vue/package.json index 86f48a1a0a..e3920d69df 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "8.3.4", + "version": "8.4.0", "description": "Vue specific wrapper for @ionic/core", "scripts": { "eslint": "eslint src", @@ -66,7 +66,7 @@ "vue-router": "^4.0.16" }, "dependencies": { - "@ionic/core": "^8.3.4", + "@ionic/core": "^8.4.0", "ionicons": "^7.0.0" }, "vetur": { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 72fac0c958..d5a84a2e39 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -67,7 +67,10 @@ import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion- import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js'; import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js'; import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js'; +import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js'; +import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js'; import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js'; +import { defineCustomElement as defineIonSelectModal } from '@ionic/core/components/ion-select-modal.js'; import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js'; import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; @@ -759,6 +762,7 @@ export const IonSegment = /*@__PURE__*/ defineContainer('ion-segment-button', defineIonSegmentButton, [ + 'contentId', 'disabled', 'layout', 'type', @@ -767,6 +771,15 @@ export const IonSegmentButton = /*@__PURE__*/ defineContainer('ion-segment-content', defineIonSegmentContent); + + +export const IonSegmentView = /*@__PURE__*/ defineContainer('ion-segment-view', defineIonSegmentView, [ + 'disabled', + 'ionSegmentViewScroll' +]); + + export const IonSelect = /*@__PURE__*/ defineContainer('ion-select', defineIonSelect, [ 'cancelText', 'color', @@ -797,6 +810,13 @@ export const IonSelect = /*@__PURE__*/ defineContainer('ion-select-modal', defineIonSelectModal, [ + 'header', + 'multiple', + 'options' +]); + + export const IonSelectOption = /*@__PURE__*/ defineContainer('ion-select-option', defineIonSelectOption, [ 'disabled', 'value'