Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(select): add modal as interface #29972

Merged
merged 9 commits into from
Oct 31, 2024
7 changes: 6 additions & 1 deletion core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,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
Expand Down Expand Up @@ -1672,6 +1672,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,value,any,undefined,false,false
Expand Down
32 changes: 27 additions & 5 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./compone
import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
import { SegmentButtonLayout } from "./components/segment-button/segment-button-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";
Expand Down Expand Up @@ -70,6 +71,7 @@ export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./compone
export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
export { SegmentButtonLayout } from "./components/segment-button/segment-button-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";
Expand Down Expand Up @@ -639,6 +641,7 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
"setFocus": () => Promise<void>;
/**
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
*/
Expand Down Expand Up @@ -2279,7 +2282,7 @@ export namespace Components {
*/
"name": string;
"setButtonTabindex": (value: number) => Promise<void>;
"setFocus": (ev: globalThis.Event) => Promise<void>;
"setFocus": (ev?: globalThis.Event) => Promise<void>;
/**
* the value of the radio.
*/
Expand Down Expand Up @@ -2741,11 +2744,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;
/**
Expand Down Expand Up @@ -2802,6 +2805,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.
Expand Down Expand Up @@ -4434,6 +4442,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: {
Expand Down Expand Up @@ -4722,6 +4736,7 @@ declare global {
"ion-segment": HTMLIonSegmentElement;
"ion-segment-button": HTMLIonSegmentButtonElement;
"ion-select": HTMLIonSelectElement;
"ion-select-modal": HTMLIonSelectModalElement;
"ion-select-option": HTMLIonSelectOptionElement;
"ion-select-popover": HTMLIonSelectPopoverElement;
"ion-skeleton-text": HTMLIonSkeletonTextElement;
Expand Down Expand Up @@ -7497,11 +7512,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;
/**
Expand Down Expand Up @@ -7577,6 +7592,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.
Expand Down Expand Up @@ -8163,6 +8183,7 @@ declare namespace LocalJSX {
"ion-segment": IonSegment;
"ion-segment-button": IonSegmentButton;
"ion-select": IonSelect;
"ion-select-modal": IonSelectModal;
"ion-select-option": IonSelectOption;
"ion-select-popover": IonSelectPopover;
"ion-skeleton-text": IonSkeletonText;
Expand Down Expand Up @@ -8262,6 +8283,7 @@ declare module "@stencil/core" {
"ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes<HTMLIonSegmentElement>;
"ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes<HTMLIonSegmentButtonElement>;
"ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes<HTMLIonSelectElement>;
"ion-select-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes<HTMLIonSelectModalElement>;
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
"ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes<HTMLIonSelectPopoverElement>;
"ion-skeleton-text": LocalJSX.IonSkeletonText & JSXBase.HTMLAttributes<HTMLIonSkeletonTextElement>;
Expand Down
6 changes: 4 additions & 2 deletions core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -121,7 +121,9 @@ export class Checkbox implements ComponentInterface {
};
}

private setFocus() {
/** @internal */
@Method()
async setFocus() {
if (this.focusEl) {
this.focusEl.focus();
}
Expand Down
6 changes: 4 additions & 2 deletions core/src/components/radio-group/radio-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ export class RadioGroup implements ComponentInterface {

@Listen('keydown', { target: 'document' })
onKeydown(ev: KeyboardEvent) {
const inSelectPopover = !!this.el.closest('ion-select-popover');
// We don't want the value to automatically change/emit when the radio group is part of a select interface
// as this will cause the interface to close when navigating through the radio group options
const inSelectInterface = !!this.el.closest('ion-select-popover') || !!this.el.closest('ion-select-modal');

if (ev.target && !this.el.contains(ev.target as HTMLElement)) {
return;
Expand Down Expand Up @@ -187,7 +189,7 @@ export class RadioGroup implements ComponentInterface {
if (next && radios.includes(next)) {
next.setFocus(ev);

if (!inSelectPopover) {
if (!inSelectInterface) {
this.value = next.value;
this.emitValueChange(ev);
}
Expand Down
8 changes: 5 additions & 3 deletions core/src/components/radio/radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ export class Radio implements ComponentInterface {

/** @internal */
@Method()
async setFocus(ev: globalThis.Event) {
ev.stopPropagation();
ev.preventDefault();
async setFocus(ev?: globalThis.Event) {
if (ev !== undefined) {
ev.stopPropagation();
ev.preventDefault();
}

this.el.focus();
}
Expand Down
8 changes: 8 additions & 0 deletions core/src/components/select-modal/select-modal-interface.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
1 change: 1 addition & 0 deletions core/src/components/select-modal/select-modal.ios.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "./select-modal";
30 changes: 30 additions & 0 deletions core/src/components/select-modal/select-modal.md.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@import "./select-modal";
@import "../../themes/ionic.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)};
}
3 changes: 3 additions & 0 deletions core/src/components/select-modal/select-modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
height: 100%;
}
Loading
Loading