From 1f9a67370574c38633cc814f70753179b512ff5a Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 12 Oct 2020 16:31:18 -0400 Subject: [PATCH 01/26] feat(select): allow multiple value select with popover interface - brings us closer to matching the Material Design spec --- core/src/components.d.ts | 24 ++- core/src/components/checkbox/readme.md | 13 ++ core/src/components/select-popover/readme.md | 2 +- .../select-popover-interface.ts | 2 +- .../select-popover/select-popover.ios.scss | 2 + .../select-popover/select-popover.md.scss | 13 ++ .../select-popover/select-popover.tsx | 162 ++++++++++++++---- core/src/components/select/select.tsx | 20 ++- .../components/select/test/basic/index.html | 29 ++-- 9 files changed, 209 insertions(+), 58 deletions(-) create mode 100644 core/src/components/select-popover/select-popover.ios.scss create mode 100644 core/src/components/select-popover/select-popover.md.scss diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 559bf6bfb08..eb154608960 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2154,19 +2154,23 @@ export namespace Components { } interface IonSelectPopover { /** - * Header text for the popover + * The header text of the popover */ "header"?: string; /** - * Text for popover body + * The text content of the popover body */ "message"?: string; /** - * Array of options for the popover + * If true, the select accepts multiple values + */ + "multiple"?: boolean; + /** + * An array of options for the popover */ "options": SelectPopoverOption[]; /** - * Subheader text for the popover + * The subheader text of the popover */ "subHeader"?: string; } @@ -5475,19 +5479,23 @@ declare namespace LocalJSX { } interface IonSelectPopover { /** - * Header text for the popover + * The header text of the popover */ "header"?: string; /** - * Text for popover body + * The text content of the popover body */ "message"?: string; /** - * Array of options for the popover + * If true, the select accepts multiple values + */ + "multiple"?: boolean; + /** + * An array of options for the popover */ "options"?: SelectPopoverOption[]; /** - * Subheader text for the popover + * The subheader text of the popover */ "subHeader"?: string; } diff --git a/core/src/components/checkbox/readme.md b/core/src/components/checkbox/readme.md index cf65d428794..f9e56fce31a 100644 --- a/core/src/components/checkbox/readme.md +++ b/core/src/components/checkbox/readme.md @@ -303,6 +303,19 @@ export default defineComponent({ | `--transition` | Transition of the checkbox icon | +## Dependencies + +### Used by + + - ion-select-popover + +### Graph +```mermaid +graph TD; + ion-select-popover --> ion-checkbox + style ion-checkbox fill:#f9f,stroke:#333,stroke-width:4px +``` + ---------------------------------------------- *Built with [StencilJS](https://stenciljs.com/)* diff --git a/core/src/components/select-popover/readme.md b/core/src/components/select-popover/readme.md index 3480e9d0d66..00fbc1f19f5 100644 --- a/core/src/components/select-popover/readme.md +++ b/core/src/components/select-popover/readme.md @@ -1,6 +1,6 @@ # ion-select-popover -SelectPopover is an internal component that is used for create the popover interface, from a Select component. +The select popover is an internal component that is used to create the popover interface from a select component. diff --git a/core/src/components/select-popover/select-popover-interface.ts b/core/src/components/select-popover/select-popover-interface.ts index 194f1e06452..7bc59315492 100644 --- a/core/src/components/select-popover/select-popover-interface.ts +++ b/core/src/components/select-popover/select-popover-interface.ts @@ -5,5 +5,5 @@ export interface SelectPopoverOption { disabled: boolean; checked: boolean; cssClass?: string | string[]; - handler?: () => void; + handler?: (value: any) => boolean | void | {[key: string]: any}; } diff --git a/core/src/components/select-popover/select-popover.ios.scss b/core/src/components/select-popover/select-popover.ios.scss new file mode 100644 index 00000000000..48b965739f2 --- /dev/null +++ b/core/src/components/select-popover/select-popover.ios.scss @@ -0,0 +1,2 @@ +@import "./select-popover"; + diff --git a/core/src/components/select-popover/select-popover.md.scss b/core/src/components/select-popover/select-popover.md.scss new file mode 100644 index 00000000000..9cb4d378e44 --- /dev/null +++ b/core/src/components/select-popover/select-popover.md.scss @@ -0,0 +1,13 @@ +@import "./select-popover"; + +:host ion-list { + padding: 0; +} + +// :host ion-list ion-radio { +// opacity: 0; +// } + +:host ion-item { + --inner-border-width: 0; +} diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx index 4ff4b70a6f5..daeaa736e24 100644 --- a/core/src/components/select-popover/select-popover.tsx +++ b/core/src/components/select-popover/select-popover.tsx @@ -1,4 +1,4 @@ -import { Component, ComponentInterface, Host, Listen, Prop, h } from '@stencil/core'; +import { Component, ComponentInterface, Element, Host, Listen, Prop, h } from '@stencil/core'; import { getIonMode } from '../../global/ionic-global'; import { SelectPopoverOption } from '../../interface'; @@ -10,60 +10,164 @@ import { getClassMap } from '../../utils/theme'; */ @Component({ tag: 'ion-select-popover', - styleUrl: 'select-popover.scss', + styleUrls: { + ios: 'select-popover.ios.scss', + md: 'select-popover.md.scss' + }, scoped: true }) export class SelectPopover implements ComponentInterface { + @Element() el!: HTMLIonSelectPopoverElement; - /** Header text for the popover */ + /** + * The header text of the popover + */ @Prop() header?: string; - /** Subheader text for the popover */ + /** + * The subheader text of the popover + */ @Prop() subHeader?: string; - /** Text for popover body */ + /** + * The text content of the popover body + */ @Prop() message?: string; - /** Array of options for the popover */ + /** + * If true, the select accepts multiple values + */ + @Prop() multiple?: boolean; + + /** + * An array of options for the popover + */ @Prop() options: SelectPopoverOption[] = []; @Listen('ionChange') onSelect(ev: any) { - const option = this.options.find(o => o.value === ev.target.value); - if (option) { - safeCall(option.handler); + this.setChecked(ev); + this.callOptionHandler(ev); + } + + /** + * When an option is selected we need to get the value(s) + * of the selected option(s) and return it in the option + * handler + */ + private async callOptionHandler(ev: any) { + const { options } = this; + const option = await options.find(o => o.value === ev.target.value); + + const values = this.getValues(ev); + + if (option && option.handler) { + safeCall(option.handler, values); } } - render() { - const checkedOption = this.options.find(o => o.checked); + /** + * This is required when selecting a radio that is already + * selected because it will not trigger the ionChange event + * but we still want to close the popover + */ + private rbClick(ev: any) { + this.callOptionHandler(ev); + } + + private setChecked(ev: any): void { + const { multiple, options } = this; + const option = options.find(o => o.value === ev.target.value); + + // this is a popover with checkboxes (multiple value select) + // we need to set the checked value for this option + if (multiple && option) { + option.checked = ev.detail.checked; + } + } + + private getValues(ev: any): any | any[] | null { + const { multiple, options } = this; + + if (multiple) { + // this is a popover with checkboxes (multiple value select) + // return an array of all the checked values + return options.filter(i => i.checked).map(i => i.value); + } + + // this is a popover with radio buttons (single value select) + // return the value that was clicked, otherwise undefined + const option = options.find(i => i.value === ev.target.value); + return option ? option.value : undefined; + } + + renderOptions(options: SelectPopoverOption[]) { + const { multiple } = this; + + switch (multiple) { + case true: return this.renderCheckboxOptions(options); + default: return this.renderRadioOptions(options); + } + } + + renderCheckboxOptions(options: SelectPopoverOption[]) { + return ( + options.map(option => + + + + + {option.text} + + + ) + ) + } + + renderRadioOptions(options: SelectPopoverOption[]) { + const checkedOption = options.find(o => o.checked); const checkedValue = checkedOption ? checkedOption.value : undefined; + + return ( + + {options.map(option => + + + {option.text} + + this.rbClick(ev)} + > + + + )} + + ) + } + + render() { + const { header, message, options, subHeader } = this; + return ( - {this.header !== undefined && {this.header}} - { (this.subHeader !== undefined || this.message !== undefined) && + {header !== undefined && {header}} + { (subHeader !== undefined || message !== undefined) && - {this.subHeader !== undefined &&

{this.subHeader}

} - {this.message !== undefined &&

{this.message}

} + {subHeader !== undefined &&

{subHeader}

} + {message !== undefined &&

{message}

}
} - - {this.options.map(option => - - - {option.text} - - - - - )} - + {this.renderOptions(options)}
); diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index 5bb8e2945ba..0334c85883f 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -183,22 +183,22 @@ export class Select implements ComponentInterface { private createOverlay(ev?: UIEvent): Promise { let selectInterface = this.interface; - if ((selectInterface === 'action-sheet' || selectInterface === 'popover') && this.multiple) { + if (selectInterface === 'action-sheet' && this.multiple) { console.warn(`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`); selectInterface = 'alert'; } if (selectInterface === 'popover' && !ev) { - console.warn('Select interface cannot be a "popover" without passing an event. Using the "alert" interface instead.'); + console.warn(`Select interface cannot be a "${selectInterface}" without passing an event. Using the "alert" interface instead.`); selectInterface = 'alert'; } - if (selectInterface === 'popover') { - return this.openPopover(ev!); - } if (selectInterface === 'action-sheet') { return this.openActionSheet(); } + if (selectInterface === 'popover') { + return this.openPopover(ev!); + } return this.openAlert(); } @@ -291,9 +291,11 @@ export class Select implements ComponentInterface { value, checked: isOptionSelected(value, selectValue, this.compareWith), disabled: option.disabled, - handler: () => { - this.value = value; - this.close(); + handler: (selected: any) => { + this.value = selected; + if (!this.multiple) { + this.close(); + } } }; }); @@ -304,6 +306,7 @@ export class Select implements ComponentInterface { private async openPopover(ev: UIEvent) { const interfaceOptions = this.interfaceOptions; const mode = getIonMode(this); + const multiple = this.multiple; const value = this.value; const popoverOpts: PopoverOptions = { mode, @@ -316,6 +319,7 @@ export class Select implements ComponentInterface { header: interfaceOptions.header, subHeader: interfaceOptions.subHeader, message: interfaceOptions.message, + multiple, value, options: this.createPopoverOptions(this.childOpts, value) } diff --git a/core/src/components/select/test/basic/index.html b/core/src/components/select/test/basic/index.html index 8177ed2cd4e..836156a6e38 100644 --- a/core/src/components/select/test/basic/index.html +++ b/core/src/components/select/test/basic/index.html @@ -134,14 +134,11 @@ - Gaming - - NES - Nintendo64 - PlayStation - Sega Genesis - Sega Saturn - SNES + Favorite food + + Steak + Pizza + Tacos @@ -178,7 +175,6 @@ - @@ -244,7 +240,7 @@ Numbers - + 0 1 2 @@ -254,6 +250,17 @@ + + Toppings + + Extra cheese + Mushroom + Onion + Pepperoni + Sausage + + + Disabled @@ -348,7 +355,7 @@ objectSelectElement.appendChild(selectOption) }); - + objectSelectElement.value = { id: 1, first: 'Alice', From 7e5974317e14310a615af3ccde359677c2b2a053 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 12 Oct 2020 16:57:42 -0400 Subject: [PATCH 02/26] fix(select-popover): allow number comparison --- core/src/components/select-popover/select-popover.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx index daeaa736e24..2118d4a2cce 100644 --- a/core/src/components/select-popover/select-popover.tsx +++ b/core/src/components/select-popover/select-popover.tsx @@ -57,7 +57,7 @@ export class SelectPopover implements ComponentInterface { */ private async callOptionHandler(ev: any) { const { options } = this; - const option = await options.find(o => o.value === ev.target.value); + const option = await options.find(o => this.getValue(o.value) === ev.target.value); const values = this.getValues(ev); @@ -77,7 +77,7 @@ export class SelectPopover implements ComponentInterface { private setChecked(ev: any): void { const { multiple, options } = this; - const option = options.find(o => o.value === ev.target.value); + const option = options.find(o => this.getValue(o.value) === ev.target.value); // this is a popover with checkboxes (multiple value select) // we need to set the checked value for this option @@ -97,10 +97,14 @@ export class SelectPopover implements ComponentInterface { // this is a popover with radio buttons (single value select) // return the value that was clicked, otherwise undefined - const option = options.find(i => i.value === ev.target.value); + const option = options.find(o => this.getValue(o.value) === ev.target.value); return option ? option.value : undefined; } + private getValue(value: any): any { + return typeof value === 'number' ? value.toString() : value; + } + renderOptions(options: SelectPopoverOption[]) { const { multiple } = this; From 69161e4578e4802c2210a4f45e879e00b2a843ec Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 12 Oct 2020 17:23:04 -0400 Subject: [PATCH 03/26] chore: remove await --- core/src/components/select-popover/select-popover.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx index 2118d4a2cce..3b1aaa5569b 100644 --- a/core/src/components/select-popover/select-popover.tsx +++ b/core/src/components/select-popover/select-popover.tsx @@ -55,9 +55,9 @@ export class SelectPopover implements ComponentInterface { * of the selected option(s) and return it in the option * handler */ - private async callOptionHandler(ev: any) { + private callOptionHandler(ev: any) { const { options } = this; - const option = await options.find(o => this.getValue(o.value) === ev.target.value); + const option = options.find(o => this.getValue(o.value) === ev.target.value); const values = this.getValues(ev); From e619262332115abff4293b8d7645dcc154cb7da4 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 12 Oct 2020 18:44:59 -0400 Subject: [PATCH 04/26] fix(select): update popover interface for MD spec references #12310 --- .../select-popover/select-popover.ios.scss | 1 + .../select-popover.ios.vars.scss | 5 +++++ .../select-popover/select-popover.md.scss | 21 +++++++++++++++---- .../select-popover.md.vars.scss | 5 +++++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 core/src/components/select-popover/select-popover.ios.vars.scss create mode 100644 core/src/components/select-popover/select-popover.md.vars.scss diff --git a/core/src/components/select-popover/select-popover.ios.scss b/core/src/components/select-popover/select-popover.ios.scss index 48b965739f2..22dc63cc3a2 100644 --- a/core/src/components/select-popover/select-popover.ios.scss +++ b/core/src/components/select-popover/select-popover.ios.scss @@ -1,2 +1,3 @@ @import "./select-popover"; +@import "./select-popover.ios.vars"; diff --git a/core/src/components/select-popover/select-popover.ios.vars.scss b/core/src/components/select-popover/select-popover.ios.vars.scss new file mode 100644 index 00000000000..05ef1b1b63e --- /dev/null +++ b/core/src/components/select-popover/select-popover.ios.vars.scss @@ -0,0 +1,5 @@ +@import "../../themes/ionic.globals.ios"; +@import "../item/item.ios.vars"; + +// iOS Select Popover +// -------------------------------------------------- diff --git a/core/src/components/select-popover/select-popover.md.scss b/core/src/components/select-popover/select-popover.md.scss index 9cb4d378e44..43b2b526544 100644 --- a/core/src/components/select-popover/select-popover.md.scss +++ b/core/src/components/select-popover/select-popover.md.scss @@ -1,13 +1,26 @@ @import "./select-popover"; +@import "./select-popover.md.vars"; :host ion-list { - padding: 0; + @include padding(0); } -// :host ion-list ion-radio { -// opacity: 0; -// } +:host ion-list ion-radio { + opacity: 0; +} :host ion-item { --inner-border-width: 0; } + +:host .item-radio-checked { + --background: rgba(0, 0, 0, .12); + --color: #{ion-color(primary, base)}; +} + +:host .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-popover/select-popover.md.vars.scss b/core/src/components/select-popover/select-popover.md.vars.scss new file mode 100644 index 00000000000..a0ea2826b90 --- /dev/null +++ b/core/src/components/select-popover/select-popover.md.vars.scss @@ -0,0 +1,5 @@ +@import "../../themes/ionic.globals.md"; +@import "../item/item.md.vars"; + +// Material Design Select Popover +// -------------------------------------------------- From 0f55bfa669e794d62960ae1ed2079ad98c50d956 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 12 Oct 2020 18:51:22 -0400 Subject: [PATCH 05/26] chore: remove unused code, code cleanup --- core/src/components/select-popover/select-popover.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx index 3b1aaa5569b..4ef85201a0a 100644 --- a/core/src/components/select-popover/select-popover.tsx +++ b/core/src/components/select-popover/select-popover.tsx @@ -1,4 +1,4 @@ -import { Component, ComponentInterface, Element, Host, Listen, Prop, h } from '@stencil/core'; +import { Component, ComponentInterface, Host, Listen, Prop, h } from '@stencil/core'; import { getIonMode } from '../../global/ionic-global'; import { SelectPopoverOption } from '../../interface'; @@ -17,8 +17,6 @@ import { getClassMap } from '../../utils/theme'; scoped: true }) export class SelectPopover implements ComponentInterface { - @Element() el!: HTMLIonSelectPopoverElement; - /** * The header text of the popover */ @@ -92,7 +90,7 @@ export class SelectPopover implements ComponentInterface { if (multiple) { // this is a popover with checkboxes (multiple value select) // return an array of all the checked values - return options.filter(i => i.checked).map(i => i.value); + return options.filter(o => o.checked).map(o => o.value); } // this is a popover with radio buttons (single value select) @@ -134,11 +132,10 @@ export class SelectPopover implements ComponentInterface { } renderRadioOptions(options: SelectPopoverOption[]) { - const checkedOption = options.find(o => o.checked); - const checkedValue = checkedOption ? checkedOption.value : undefined; + const checked = options.filter(o => o.checked).map(o => o.value); return ( - + {options.map(option => From a77c57b644c03c0e8ae2d613996c83a6a74bb585 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 29 Jun 2021 11:38:00 -0400 Subject: [PATCH 06/26] fix(tap-click): call pointerUp to remove ripple when right clicking --- core/src/utils/tap-click.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/utils/tap-click.ts b/core/src/utils/tap-click.ts index 02cd574b49d..650788f3a87 100644 --- a/core/src/utils/tap-click.ts +++ b/core/src/utils/tap-click.ts @@ -43,6 +43,10 @@ export const startTapClick = (config: Config) => { } }; + const onContextMenu = (ev: MouseEvent) => { + pointerUp(ev); + }; + const cancelActive = () => { clearTimeout(activeDefer); activeDefer = undefined; @@ -155,6 +159,8 @@ export const startTapClick = (config: Config) => { doc.addEventListener('mousedown', onMouseDown, true); doc.addEventListener('mouseup', onMouseUp, true); + + doc.addEventListener('contextmenu', onContextMenu, true); }; const getActivatableTarget = (ev: any): any => { From ebda4404f36fc3602ea5d1542527d65a7f65269c Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 29 Jun 2021 11:39:32 -0400 Subject: [PATCH 07/26] fix(select-popover): style updates and checking proper radio --- core/src/components/item/item.scss | 13 +++++++++---- .../select-popover/select-popover.md.scss | 17 ++++++++++------- .../select-popover/select-popover.scss | 6 +++--- .../select-popover/select-popover.tsx | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/core/src/components/item/item.scss b/core/src/components/item/item.scss index a0c7fb3eb74..5a56dfeab2d 100644 --- a/core/src/components/item/item.scss +++ b/core/src/components/item/item.scss @@ -154,7 +154,7 @@ // -------------------------------------------------- @media (any-hover: hover) { - :host(.ion-activatable:hover) .item-native { + :host(.ion-activatable:not(.ion-focused):hover) .item-native { color: var(--color-hover); &::after { @@ -164,7 +164,7 @@ } } - :host(.ion-color.ion-activatable:hover) .item-native { + :host(.ion-color.ion-activatable:not(.ion-focused):hover) .item-native { color: #{current-color(contrast)}; &::after { @@ -173,6 +173,7 @@ } } + // Item: Disabled // -------------------------------------------------- @@ -308,7 +309,11 @@ button, a { z-index: 1; } +// Setting pointer-events to none allows the label +// to be clicked to open the select interface ::slotted(ion-label) { + pointer-events: none; + flex: 1; } @@ -359,7 +364,7 @@ button, a { width: 100%; height: 100%; - + transform: scaleX(0); transition: transform 200ms, border-bottom-width 200ms; @@ -553,4 +558,4 @@ ion-ripple-effect { display: none; color: var(--highlight-color-invalid); -} \ No newline at end of file +} diff --git a/core/src/components/select-popover/select-popover.md.scss b/core/src/components/select-popover/select-popover.md.scss index 43b2b526544..e8a41d5ec26 100644 --- a/core/src/components/select-popover/select-popover.md.scss +++ b/core/src/components/select-popover/select-popover.md.scss @@ -1,24 +1,27 @@ @import "./select-popover"; @import "./select-popover.md.vars"; -:host ion-list { +ion-list { @include padding(0); } -:host ion-list ion-radio { +ion-list ion-radio { opacity: 0; } -:host ion-item { +ion-item { --inner-border-width: 0; } -:host .item-radio-checked { - --background: rgba(0, 0, 0, .12); - --color: #{ion-color(primary, base)}; +.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; } -:host .item-checkbox-checked { +.item-checkbox-checked { --background-activated: #{$item-md-color}; --background-focused: #{$item-md-color}; --background-hover: #{$item-md-color}; diff --git a/core/src/components/select-popover/select-popover.scss b/core/src/components/select-popover/select-popover.scss index 5b76901dbf3..0c4d9782b4b 100644 --- a/core/src/components/select-popover/select-popover.scss +++ b/core/src/components/select-popover/select-popover.scss @@ -1,10 +1,10 @@ @import "./select-popover.vars"; -:host ion-list { +ion-list { @include margin($select-popover-list-margin-top, $select-popover-list-margin-end, $select-popover-list-margin-bottom, $select-popover-list-margin-start); } -:host ion-list-header, -:host ion-label { +ion-list-header, +ion-label { @include margin(0); } diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx index 4ef85201a0a..21cf7685682 100644 --- a/core/src/components/select-popover/select-popover.tsx +++ b/core/src/components/select-popover/select-popover.tsx @@ -132,7 +132,7 @@ export class SelectPopover implements ComponentInterface { } renderRadioOptions(options: SelectPopoverOption[]) { - const checked = options.filter(o => o.checked).map(o => o.value); + const checked = options.filter(o => o.checked).map(o => o.value)[0]; return ( From 8b29b986092febbc6d8316eccf8f7f8bcdaf2af7 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 29 Jun 2021 11:54:57 -0400 Subject: [PATCH 08/26] fix(select): hide backdrop on md, set size to cover --- core/src/components/select/select.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index 6e5bcdbe17d..6caec5cc6ed 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -125,7 +125,8 @@ export class Select implements ComponentInterface { @Watch('disabled') @Watch('placeholder') - disabledChanged() { + @Watch('isExpanded') + styleChanged() { this.emitStyle(); } @@ -306,15 +307,22 @@ export class Select implements ComponentInterface { private async openPopover(ev: UIEvent) { const interfaceOptions = this.interfaceOptions; const mode = getIonMode(this); + const showBackdrop = mode === 'md' ? false : true; const multiple = this.multiple; const value = this.value; + + // TODO make sure users can override showBackdrop / size + // and change size from cover when inline + const popoverOpts: PopoverOptions = { mode, + showBackdrop, ...interfaceOptions, component: 'ion-select-popover', cssClass: ['select-popover', interfaceOptions.cssClass], event: ev, + size: 'cover', componentProps: { header: interfaceOptions.header, subHeader: interfaceOptions.subHeader, @@ -415,11 +423,12 @@ export class Select implements ComponentInterface { private emitStyle() { this.ionStyle.emit({ 'interactive': true, + 'interactive-disabled': this.disabled, 'select': true, + 'select-disabled': this.disabled, 'has-placeholder': this.placeholder != null, 'has-value': this.hasValue(), - 'interactive-disabled': this.disabled, - 'select-disabled': this.disabled + 'has-focus': this.isExpanded, }); } @@ -427,6 +436,7 @@ export class Select implements ComponentInterface { this.setFocus(); this.open(ev); } + private onFocus = () => { this.ionFocus.emit(); } From 3dddb6d1de7438a21b77a7d071a08cc502f46773 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 29 Jun 2021 11:55:38 -0400 Subject: [PATCH 09/26] test(select): add spec test for new popover styles --- core/src/components/select/test/spec/e2e.ts | 10 ++ .../components/select/test/spec/index.html | 148 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 core/src/components/select/test/spec/e2e.ts create mode 100644 core/src/components/select/test/spec/index.html diff --git a/core/src/components/select/test/spec/e2e.ts b/core/src/components/select/test/spec/e2e.ts new file mode 100644 index 00000000000..c7f99b15dc9 --- /dev/null +++ b/core/src/components/select/test/spec/e2e.ts @@ -0,0 +1,10 @@ +import { newE2EPage } from '@stencil/core/testing'; + +test('select: spec', async () => { + const page = await newE2EPage({ + url: '/src/components/select/test/spec?ionic:_testing=true' + }); + + const compare = await page.compareScreenshot(); + expect(compare).toMatchScreenshot(); +}); diff --git a/core/src/components/select/test/spec/index.html b/core/src/components/select/test/spec/index.html new file mode 100644 index 00000000000..a35bc74a8a6 --- /dev/null +++ b/core/src/components/select/test/spec/index.html @@ -0,0 +1,148 @@ + + + + + + Select - Spec + + + + + + + + + + + + + Select - Spec + + + + +

Floating Selects

+ +
+
+

Default

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Default Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+ +
+
+ + + + From 20ef74f1a27f0f8431736fa79702054d748c3e3e Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 30 Jun 2021 12:27:58 -0400 Subject: [PATCH 10/26] test(select): test updates --- .../components/select/test/spec/index.html | 92 ++++++++++++++++++- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/core/src/components/select/test/spec/index.html b/core/src/components/select/test/spec/index.html index a35bc74a8a6..24aa058e99c 100644 --- a/core/src/components/select/test/spec/index.html +++ b/core/src/components/select/test/spec/index.html @@ -38,7 +38,7 @@

Default

-

Default Focused

+

Default: Focused

Fruit @@ -63,6 +63,19 @@

Filled

+
+

Filled: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +

Outlined

@@ -76,11 +89,83 @@

Outlined

+
+

Outlined: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+ + +
+ +

Stacked Selects

+ +
+
+

Default

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Default: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled

+ + + Fruit + + + Apple + Orange + Banana + + +

Filled: Focused

- Fruit + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined

+ + + Fruit Apple @@ -93,7 +178,7 @@

Filled: Focused

Outlined: Focused

- Fruit + Fruit Apple @@ -103,7 +188,6 @@

Outlined: Focused

- From eb6f93b9ec2d0c20b12069cee3446d194604a49d Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 30 Jun 2021 15:56:44 -0400 Subject: [PATCH 11/26] fix(item): move label and highlight item when focused with tab --- core/src/components/item/item.ios.scss | 1 + core/src/components/item/item.md.scss | 39 +++++++++++++++++-------- core/src/components/item/item.scss | 9 ++++++ core/src/components/label/label.md.scss | 14 ++++++++- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/core/src/components/item/item.ios.scss b/core/src/components/item/item.ios.scss index 89239023644..27c244a4a61 100644 --- a/core/src/components/item/item.ios.scss +++ b/core/src/components/item/item.ios.scss @@ -24,6 +24,7 @@ --highlight-color-valid: #{$item-ios-input-highlight-color-valid}; --highlight-color-invalid: #{$item-ios-input-highlight-color-invalid}; --bottom-padding-start: 0px; + font-size: $item-ios-font-size; } diff --git a/core/src/components/item/item.md.scss b/core/src/components/item/item.md.scss index 900d263bb16..b9715c27c4f 100644 --- a/core/src/components/item/item.md.scss +++ b/core/src/components/item/item.md.scss @@ -99,6 +99,7 @@ transition: none; } +:host(.item-fill-outline.ion-focused) .item-native, :host(.item-fill-outline.item-has-focus) .item-native { border-color: transparent; } @@ -308,6 +309,8 @@ --padding-start: 0; } +:host(.ion-focused:not(.ion-color)) ::slotted(.label-stacked), +:host(.ion-focused:not(.ion-color)) ::slotted(.label-floating), :host(.item-has-focus:not(.ion-color)) ::slotted(.label-stacked), :host(.item-has-focus:not(.ion-color)) ::slotted(.label-floating) { color: $label-md-text-color-focused; @@ -347,13 +350,10 @@ --border-color: #{$item-md-input-fill-border-color}; } -:host(.item-fill-solid) .item-native:hover { - --background: var(--background-hover); - --border-color: #{$item-md-input-fill-border-color-hover}; -} - +:host(.item-fill-solid.ion-focused) .item-native, :host(.item-fill-solid.item-has-focus) .item-native { --background: var(--background-focused); + border-bottom-color: var(--highlight-color-focused); } @@ -361,10 +361,21 @@ @include border-radius(16px, 16px, 0, 0); } +@media (any-hover: hover) { + :host(.item-fill-solid:hover) .item-native { + --background: var(--background-hover); + --border-color: #{$item-md-input-fill-border-color-hover}; + } +} + // Material Design Item: Fill Outline // -------------------------------------------------- :host(.item-fill-outline) { + --ripple-color: transparent; + --background-focused: transparent; + --background-hover: transparent; + --border-color: #{$item-md-input-fill-border-color}; --border-width: #{$item-md-border-bottom-width}; @@ -379,10 +390,6 @@ @include border-radius(4px); } -:host(.item-fill-outline) .item-native:hover { - --border-color: #{$item-md-input-fill-border-color-hover}; -} - :host(.item-fill-outline.item-shape-round) .item-native { --inner-padding-start: 16px; @@ -393,14 +400,22 @@ @include padding-horizontal(32px, null); } - +:host(.item-fill-outline.item-label-floating.ion-focused) .item-native ::slotted(ion-input:not(:first-child)), +:host(.item-fill-outline.item-label-floating.ion-focused) .item-native ::slotted(ion-textarea:not(:first-child)), :host(.item-fill-outline.item-label-floating.item-has-focus) .item-native ::slotted(ion-input:not(:first-child)), +:host(.item-fill-outline.item-label-floating.item-has-focus) .item-native ::slotted(ion-textarea:not(:first-child)), :host(.item-fill-outline.item-label-floating.item-has-value) .item-native ::slotted(ion-input:not(:first-child)), -:host(.item-fill-outline.item-label-floating.item-has-focus) .item-native ::slotted(ion-textarea:not(:first-child)), :host(.item-fill-outline.item-label-floating.item-has-value) .item-native ::slotted(ion-textarea:not(:first-child)) { transform: translateY(-25%); } +@media (any-hover: hover) { + :host(.item-fill-outline:hover) .item-native { + --border-color: #{$item-md-input-fill-border-color-hover}; + } +} + + // Material Design Item: Invalid // -------------------------------------------------- @@ -416,4 +431,4 @@ :host(.item-fill-solid.ion-invalid:not(.ion-color)) .item-native, :host(.item-fill-solid.ion-invalid:not(.ion-color)) .item-highlight { border-color: var(--highlight-color-invalid); -} \ No newline at end of file +} diff --git a/core/src/components/item/item.scss b/core/src/components/item/item.scss index 5a56dfeab2d..1ebe1bc1014 100644 --- a/core/src/components/item/item.scss +++ b/core/src/components/item/item.scss @@ -375,6 +375,8 @@ button, a { pointer-events: none; } +:host(.ion-focused) .item-highlight, +:host(.ion-focused) .item-inner-highlight, :host(.item-has-focus) .item-highlight, :host(.item-has-focus) .item-inner-highlight { transform: scaleX(1); @@ -383,22 +385,27 @@ button, a { border-color: var(--highlight-background); } +:host(.ion-focused) .item-highlight, :host(.item-has-focus) .item-highlight { border-width: var(--full-highlight-height); opacity: var(--show-full-highlight); } +:host(.ion-focused) .item-inner-highlight, :host(.item-has-focus) .item-inner-highlight { border-bottom-width: var(--inset-highlight-height); opacity: var(--show-inset-highlight); } +:host(.ion-focused.item-fill-solid) .item-highlight, :host(.item-has-focus.item-fill-solid) .item-highlight { border-width: calc(var(--full-highlight-height) - 1px); } +:host(.ion-focused) .item-inner-highlight, +:host(.ion-focused:not(.item-fill-outline)) .item-highlight, :host(.item-has-focus) .item-inner-highlight, :host(.item-has-focus:not(.item-fill-outline)) .item-highlight { border-top: none; @@ -410,6 +417,7 @@ button, a { // Item Input Focused // -------------------------------------------------- +:host(.item-interactive.ion-focused), :host(.item-interactive.item-has-focus), :host(.item-interactive.ion-touched.ion-invalid) { // If the item has a full border and highlight is enabled, show the full item highlight @@ -422,6 +430,7 @@ button, a { // Item Input Focus // -------------------------------------------------- +:host(.item-interactive.ion-focused), :host(.item-interactive.item-has-focus) { --highlight-background: var(--highlight-color-focused); } diff --git a/core/src/components/label/label.md.scss b/core/src/components/label/label.md.scss index 05bf329cef5..b163d60e39b 100644 --- a/core/src/components/label/label.md.scss +++ b/core/src/components/label/label.md.scss @@ -47,6 +47,7 @@ transform 150ms $label-md-transition-timing-function; } +:host-context(.ion-focused).label-floating, :host-context(.item-has-focus).label-floating, :host-context(.item-has-placeholder:not(.item-input)).label-floating, :host-context(.item-has-value).label-floating { @@ -54,9 +55,10 @@ } /** - * When translating the label inside of an ion-item with `fill="outline"`, + * When translating the label inside of an ion-item with `fill="outline"`, * add pseudo-elements to imitate fieldset-like padding without shifting the label */ +:host-context(.item-fill-outline.ion-focused).label-floating, :host-context(.item-fill-outline.item-has-focus).label-floating, :host-context(.item-fill-outline.item-has-placeholder:not(.item-input)).label-floating, :host-context(.item-fill-outline.item-has-value).label-floating { @@ -96,28 +98,38 @@ } } +:host-context(.item-fill-outline.ion-focused.item-has-start-slot).label-floating, :host-context(.item-fill-outline.item-has-focus.item-has-start-slot).label-floating, :host-context(.item-fill-outline.item-has-placeholder:not(.item-input).item-has-start-slot).label-floating, :host-context(.item-fill-outline.item-has-value.item-has-start-slot).label-floating { @include transform(translateX(#{$item-md-fill-outline-label-translate-x}), translateY(-6px), scale(.75)); } +:host-context(.item-fill-outline.ion-focused.item-has-start-slot).label-floating.label-rtl, :host-context(.item-fill-outline.item-has-focus.item-has-start-slot).label-floating.label-rtl, :host-context(.item-fill-outline.item-has-placeholder:not(.item-input).item-has-start-slot).label-floating.label-rtl, :host-context(.item-fill-outline.item-has-value.item-has-start-slot).label-floating.label-rtl { @include transform(translateX(calc(-1 * #{$item-md-fill-outline-label-translate-x})), translateY(-6px), scale(.75)); } +:host-context(.ion-focused).label-stacked:not(.ion-color), +:host-context(.ion-focused).label-floating:not(.ion-color), :host-context(.item-has-focus).label-stacked:not(.ion-color), :host-context(.item-has-focus).label-floating:not(.ion-color) { color: $label-md-text-color-focused; } +:host-context(.ion-focused.ion-color).label-stacked:not(.ion-color), +:host-context(.ion-focused.ion-color).label-floating:not(.ion-color), :host-context(.item-has-focus.ion-color).label-stacked:not(.ion-color), :host-context(.item-has-focus.ion-color).label-floating:not(.ion-color) { color: #{current-color(contrast)}; } +:host-context(.item-fill-solid.ion-focused.ion-color).label-stacked:not(.ion-color), +:host-context(.item-fill-solid.ion-focused.ion-color).label-floating:not(.ion-color), +:host-context(.item-fill-outline.ion-focused.ion-color).label-stacked:not(.ion-color), +:host-context(.item-fill-outline.ion-focused.ion-color).label-floating:not(.ion-color), :host-context(.item-fill-solid.item-has-focus.ion-color).label-stacked:not(.ion-color), :host-context(.item-fill-solid.item-has-focus.ion-color).label-floating:not(.ion-color), :host-context(.item-fill-outline.item-has-focus.ion-color).label-stacked:not(.ion-color), From 70f14bb94eb38b5918e093ed842f40c1347ab5d0 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 30 Jun 2021 15:58:46 -0400 Subject: [PATCH 12/26] fix(select): design updates to match spec and cover on popover --- core/src/components/select/select.ios.scss | 2 ++ core/src/components/select/select.md.scss | 14 +++++++++++++ core/src/components/select/select.scss | 2 -- core/src/components/select/select.tsx | 24 ++++++++++++++++++---- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/core/src/components/select/select.ios.scss b/core/src/components/select/select.ios.scss index 2e88ff47a83..286bce173ca 100644 --- a/core/src/components/select/select.ios.scss +++ b/core/src/components/select/select.ios.scss @@ -14,4 +14,6 @@ .select-icon { width: 12px; height: 18px; + + opacity: .33; } diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index cd128593027..a2edc075789 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -14,8 +14,22 @@ .select-icon { width: 19px; height: 19px; + + opacity: .55; } :host-context(.item-label-floating) .select-icon { @include transform(translate3d(0, -9px, 0)); } + +:host-context(.item-label-floating.item-has-focus) .select-icon { + transition: transform .15s cubic-bezier(.4,0,.2,1),-webkit-transform .15s cubic-bezier(.4,0,.2,1); + + @include transform(rotate(180deg), translate3d(0, 0px, 0)); +} + +:host-context(.item-label-floating.ion-focused) .select-icon, +:host-context(.item-label-floating.item-has-focus) .select-icon { + color: var(--highlight-color-focused); + opacity: 1; +} diff --git a/core/src/components/select/select.scss b/core/src/components/select/select.scss index dd31ef52fa7..7aed200eb31 100644 --- a/core/src/components/select/select.scss +++ b/core/src/components/select/select.scss @@ -66,8 +66,6 @@ button { .select-icon { position: relative; - - opacity: .33; } .select-text { diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index 6caec5cc6ed..693fc4dc052 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -311,18 +311,34 @@ export class Select implements ComponentInterface { const multiple = this.multiple; const value = this.value; - // TODO make sure users can override showBackdrop / size - // and change size from cover when inline + let event: Event | CustomEvent = ev; + let size = 'auto'; + + const item = this.el.closest('ion-item'); + + // If the select is inside of an item containing a floating + // or stacked label then the popover should take up the + // full width of the item when it presents + if (item && (item.classList.contains('item-label-floating') || item.classList.contains('item-label-stacked'))) { + event = { + ...ev, + detail: { + ionShadowTarget: item + } + } + size = 'cover'; + } const popoverOpts: PopoverOptions = { mode, + event, + alignment: 'center', + size, showBackdrop, ...interfaceOptions, component: 'ion-select-popover', cssClass: ['select-popover', interfaceOptions.cssClass], - event: ev, - size: 'cover', componentProps: { header: interfaceOptions.header, subHeader: interfaceOptions.subHeader, From 630cf84ba2e7c20a08b0eef962e557ea06624521 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 30 Jun 2021 16:27:42 -0400 Subject: [PATCH 13/26] fix(select-popover): padding should be in md --- core/src/components/select-popover/select-popover.md.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/components/select-popover/select-popover.md.scss b/core/src/components/select-popover/select-popover.md.scss index e8a41d5ec26..feba89b0fe4 100644 --- a/core/src/components/select-popover/select-popover.md.scss +++ b/core/src/components/select-popover/select-popover.md.scss @@ -1,10 +1,6 @@ @import "./select-popover"; @import "./select-popover.md.vars"; -ion-list { - @include padding(0); -} - ion-list ion-radio { opacity: 0; } From 2df47cb47fa18ff3bdf7d8615f66227843896887 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 6 Jul 2021 13:21:07 -0400 Subject: [PATCH 14/26] fix(popover): ignore body padding when the size is cover --- core/src/components/popover/animations/ios.enter.ts | 4 +++- core/src/components/popover/animations/md.enter.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/components/popover/animations/ios.enter.ts b/core/src/components/popover/animations/ios.enter.ts index ccbe34f481a..752346367ea 100644 --- a/core/src/components/popover/animations/ios.enter.ts +++ b/core/src/components/popover/animations/ios.enter.ts @@ -32,7 +32,9 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev); - const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass } = calculateWindowAdjustment(side, results.top, results.left, POPOVER_IOS_BODY_PADDING, bodyWidth, bodyHeight, contentWidth, contentHeight, 25, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight); + const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING; + + const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 25, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight); const baseAnimation = createAnimation(); const backdropAnimation = createAnimation(); diff --git a/core/src/components/popover/animations/md.enter.ts b/core/src/components/popover/animations/md.enter.ts index 985d2af030b..04c503676a8 100644 --- a/core/src/components/popover/animations/md.enter.ts +++ b/core/src/components/popover/animations/md.enter.ts @@ -31,7 +31,9 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev); - const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, POPOVER_MD_BODY_PADDING, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates); + const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING; + + const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates); const baseAnimation = createAnimation(); const backdropAnimation = createAnimation(); From 4eaf809e61210d218b688566de3450c707be1123 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 6 Jul 2021 13:21:27 -0400 Subject: [PATCH 15/26] fix(select): highlight and rotate dropdown arrow for other labels --- core/src/components/select/select.md.scss | 6 +- .../components/select/test/spec/index.html | 229 +++++++++++++++++- 2 files changed, 229 insertions(+), 6 deletions(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index a2edc075789..0617b8a9324 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -22,14 +22,14 @@ @include transform(translate3d(0, -9px, 0)); } -:host-context(.item-label-floating.item-has-focus) .select-icon { +:host-context(.item-has-focus) .select-icon { transition: transform .15s cubic-bezier(.4,0,.2,1),-webkit-transform .15s cubic-bezier(.4,0,.2,1); @include transform(rotate(180deg), translate3d(0, 0px, 0)); } -:host-context(.item-label-floating.ion-focused) .select-icon, -:host-context(.item-label-floating.item-has-focus) .select-icon { +:host-context(ion-item.ion-focused) .select-icon, +:host-context(.item-has-focus) .select-icon { color: var(--highlight-color-focused); opacity: 1; } diff --git a/core/src/components/select/test/spec/index.html b/core/src/components/select/test/spec/index.html index 24aa058e99c..0f1ab76e2de 100644 --- a/core/src/components/select/test/spec/index.html +++ b/core/src/components/select/test/spec/index.html @@ -188,6 +188,225 @@

Outlined: Focused

+ +
+ +

Inline Selects

+ +
+
+

Default

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Default: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+ +
+ +

Fixed Selects

+ +
+
+

Default

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Default: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Filled: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+

Outlined: Focused

+ + + Fruit + + + Apple + Orange + Banana + + +
+
+ +
+ +

Full Width Selects

+ + + + Inline + + + Apple + Orange + Banana + + + + + + Fixed + + + Apple + Orange + Banana + + + + + Floating + + + Apple + Orange + Banana + + + + + Stacked + + + Apple + Orange + Banana + + + + +
@@ -214,10 +433,10 @@

Outlined: Focused

} hr { + border: none; background: #eff1f3; - - margin-top: 18px; - margin-bottom: 25px; + height: 1px; + margin: 18px 16px 25px 16px; } .grid { @@ -227,6 +446,10 @@

Outlined: Focused

column-gap: 20px; padding: 0 20px 20px; } + + .margin-bottom-extra { + margin-bottom: 300px; + } From f3df2f83d2b82c7374aa4e1965c7e13684a57119 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 6 Jul 2021 14:00:04 -0400 Subject: [PATCH 16/26] style: lint --- core/src/components/item/item.md.scss | 1 - core/src/components/select/select.md.scss | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/components/item/item.md.scss b/core/src/components/item/item.md.scss index b9715c27c4f..ff7ea706b72 100644 --- a/core/src/components/item/item.md.scss +++ b/core/src/components/item/item.md.scss @@ -375,7 +375,6 @@ --ripple-color: transparent; --background-focused: transparent; --background-hover: transparent; - --border-color: #{$item-md-input-fill-border-color}; --border-width: #{$item-md-border-bottom-width}; diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 0617b8a9324..80d03215dd3 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -23,13 +23,14 @@ } :host-context(.item-has-focus) .select-icon { - transition: transform .15s cubic-bezier(.4,0,.2,1),-webkit-transform .15s cubic-bezier(.4,0,.2,1); - @include transform(rotate(180deg), translate3d(0, 0px, 0)); + + transition: transform .15s cubic-bezier(.4,0,.2,1),-webkit-transform .15s cubic-bezier(.4,0,.2,1); } :host-context(ion-item.ion-focused) .select-icon, :host-context(.item-has-focus) .select-icon { color: var(--highlight-color-focused); + opacity: 1; } From afbadd64ef0e2ef3675b008f735d3a485676e190 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 9 Jul 2021 19:37:42 +0000 Subject: [PATCH 17/26] fix margin with ios cover popover --- core/src/components/popover/animations/ios.enter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/components/popover/animations/ios.enter.ts b/core/src/components/popover/animations/ios.enter.ts index 752346367ea..f30364b7a4d 100644 --- a/core/src/components/popover/animations/ios.enter.ts +++ b/core/src/components/popover/animations/ios.enter.ts @@ -33,8 +33,9 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev); const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING; + const margin = size === 'cover' ? 0 : 25; - const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 25, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight); + const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight); const baseAnimation = createAnimation(); const backdropAnimation = createAnimation(); From a161e8e4a5cc68ad1235d2f65d057ca4da0e217d Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 19 Jul 2021 15:07:56 -0400 Subject: [PATCH 18/26] arrow rotation is now correct --- core/src/components/select/select.md.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 80d03215dd3..31f4cb63380 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -20,12 +20,12 @@ :host-context(.item-label-floating) .select-icon { @include transform(translate3d(0, -9px, 0)); + + transition: transform .15s cubic-bezier(.4, 0, .2, 1); } :host-context(.item-has-focus) .select-icon { - @include transform(rotate(180deg), translate3d(0, 0px, 0)); - - transition: transform .15s cubic-bezier(.4,0,.2,1),-webkit-transform .15s cubic-bezier(.4,0,.2,1); + @include transform(rotate(180deg), translate3d(0, -9px, 0)); } :host-context(ion-item.ion-focused) .select-icon, From 1b817dec550a51490b942860bfae81710f431313 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 19 Jul 2021 15:18:34 -0400 Subject: [PATCH 19/26] fix alignment for other selects --- core/src/components/select/select.md.scss | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 31f4cb63380..007028c6db8 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -16,16 +16,21 @@ height: 19px; opacity: .55; + + transition: transform .15s cubic-bezier(.4, 0, .2, 1); + } :host-context(.item-label-floating) .select-icon { @include transform(translate3d(0, -9px, 0)); +} - transition: transform .15s cubic-bezier(.4, 0, .2, 1); +:host-context(.item-label-floating.item-has-focus) .select-icon { + @include transform(rotate(180deg), translate3d(0, -9px, 0)); } :host-context(.item-has-focus) .select-icon { - @include transform(rotate(180deg), translate3d(0, -9px, 0)); + @include transform(rotate(180deg), translate3d(0, 0px, 0)); } :host-context(ion-item.ion-focused) .select-icon, From 96634134410b0084c4e277ce9610e1e794c4f815 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 19 Jul 2021 15:34:12 -0400 Subject: [PATCH 20/26] ensure select is adjusted for floating label --- core/src/components/select/select.md.scss | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 007028c6db8..2f992744396 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -21,16 +21,12 @@ } -:host-context(.item-label-floating) .select-icon { +:host-context(.item-label-floating) { @include transform(translate3d(0, -9px, 0)); } -:host-context(.item-label-floating.item-has-focus) .select-icon { - @include transform(rotate(180deg), translate3d(0, -9px, 0)); -} - :host-context(.item-has-focus) .select-icon { - @include transform(rotate(180deg), translate3d(0, 0px, 0)); + @include transform(rotate(180deg)); } :host-context(ion-item.ion-focused) .select-icon, From 34f045ffc75c84acd4ead01c7f6f2833240292e8 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 19 Jul 2021 15:42:42 -0400 Subject: [PATCH 21/26] arrow is now positioned correctly with stacked labels --- core/src/components/select/select.md.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 2f992744396..4a7e4c86c5f 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -21,10 +21,15 @@ } -:host-context(.item-label-floating) { +:host-context(.item-label-floating), +:host-context(.item-label-stacked) .select-icon { @include transform(translate3d(0, -9px, 0)); } +:host-context(.item-has-focus.item-label-stacked) .select-icon { + @include transform(rotate(180deg), translate3d(0, -9px, 0)); +} + :host-context(.item-has-focus) .select-icon { @include transform(rotate(180deg)); } From e08a65f1a1b09d25680e72c6f49f426371364ce2 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 19 Jul 2021 16:23:44 -0400 Subject: [PATCH 22/26] lint --- core/src/components/select/select.md.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 4a7e4c86c5f..341a6702e94 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -15,10 +15,10 @@ width: 19px; height: 19px; - opacity: .55; - transition: transform .15s cubic-bezier(.4, 0, .2, 1); + opacity: .55; + } :host-context(.item-label-floating), From 91327066330d9fe12352de97c932e44de261c010 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 19 Jul 2021 16:42:11 -0400 Subject: [PATCH 23/26] improve arrow positioning logic --- core/src/components/select/select.md.scss | 24 +++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss index 341a6702e94..42451dbfafb 100644 --- a/core/src/components/select/select.md.scss +++ b/core/src/components/select/select.md.scss @@ -21,19 +21,31 @@ } -:host-context(.item-label-floating), -:host-context(.item-label-stacked) .select-icon { +/** + * Adjust the arrow so that it appears in the middle + * of the item. If the item has fill="outline" then + * we should adjust the entire ion-select rather than + * just the outline so the selected value appears centered too. + */ +:host-context(.item-label-stacked) .select-icon, +:host-context(.item-label-floating:not(.item-fill-outline)) .select-icon, +:host-context(.item-label-floating.item-fill-outline) { @include transform(translate3d(0, -9px, 0)); } -:host-context(.item-has-focus.item-label-stacked) .select-icon { - @include transform(rotate(180deg), translate3d(0, -9px, 0)); -} - :host-context(.item-has-focus) .select-icon { @include transform(rotate(180deg)); } +/** + * Ensure that the translation we did + * above is preserved when we rotate the select icon. + */ +:host-context(.item-has-focus.item-label-stacked) .select-icon, +:host-context(.item-has-focus.item-label-floating:not(.item-fill-outline)) .select-icon { + @include transform(rotate(180deg), translate3d(0, -9px, 0)); +} + :host-context(ion-item.ion-focused) .select-icon, :host-context(.item-has-focus) .select-icon { color: var(--highlight-color-focused); From 8be9ae1f7e218e229d1e5a9981bec26f644e0b1f Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 20 Jul 2021 10:02:26 -0400 Subject: [PATCH 24/26] pass focus --- core/src/components.d.ts | 1 + core/src/components/app/app.tsx | 23 +++++++++++++++++++++-- core/src/components/datetime/datetime.tsx | 2 +- core/src/components/select/select.tsx | 7 ++++++- core/src/utils/focus-visible.ts | 7 ++++++- core/src/utils/overlays.ts | 15 +++++++++++++++ 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 25fe5ad9c5f..add31fa527e 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -207,6 +207,7 @@ export namespace Components { "translucent": boolean; } interface IonApp { + "setFocus": (elements: HTMLElement[]) => Promise; } interface IonAvatar { } diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index e84ffbbaca1..809116d41ec 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -1,4 +1,4 @@ -import { Build, Component, ComponentInterface, Element, Host, h } from '@stencil/core'; +import { Build, Component, ComponentInterface, Element, Host, Method, h } from '@stencil/core'; import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; @@ -9,6 +9,8 @@ import { isPlatform } from '../../utils/platform'; styleUrl: 'app.scss', }) export class App implements ComponentInterface { + private focusVisible?: any; + @Element() el!: HTMLElement; componentDidLoad() { @@ -33,11 +35,28 @@ export class App implements ComponentInterface { if (typeof (window as any) !== 'undefined') { import('../../utils/keyboard/keyboard').then(module => module.startKeyboardAssist(window)); } - import('../../utils/focus-visible').then(module => module.startFocusVisible()); + import('../../utils/focus-visible').then(module => this.focusVisible = module.startFocusVisible()); }); } } + /** + * @internal + * Used to set focus on an element that uses `ion-focusable`. + * Do not use this if focusing the element as a result of a keyboard + * event as the focus utility should handle this for us. This method + * should be used when we want to programmatically focus an element as + * a result of another user action. (Ex: We focus the first element + * inside of a popover when the user presents it, but the popover is not always + * presented as a result of keyboard action.) + */ + @Method() + async setFocus(elements: HTMLElement[]) { + if (this.focusVisible) { + this.focusVisible.setFocus(elements); + } + } + render() { const mode = getIonMode(this); return ( diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 2e28491f3ae..f07be06238d 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -747,7 +747,7 @@ export class Datetime implements ComponentInterface { } connectedCallback() { - this.clearFocusVisible = startFocusVisible(this.el); + this.clearFocusVisible = startFocusVisible(this.el).destroy; } disconnectedCallback() { diff --git a/core/src/components/select/select.tsx b/core/src/components/select/select.tsx index 6d5e4f1c761..592664e9c05 100644 --- a/core/src/components/select/select.tsx +++ b/core/src/components/select/select.tsx @@ -178,7 +178,12 @@ export class Select implements ComponentInterface { this.isExpanded = false; this.setFocus(); }); - await overlay.present(); + + if (this.interface === 'popover') { + await (overlay as HTMLIonPopoverElement).presentFromTrigger(event, true); + } else { + await overlay.present(); + } return overlay; } diff --git a/core/src/utils/focus-visible.ts b/core/src/utils/focus-visible.ts index 6077d61bc70..f058dd3a57f 100644 --- a/core/src/utils/focus-visible.ts +++ b/core/src/utils/focus-visible.ts @@ -50,11 +50,16 @@ export const startFocusVisible = (rootEl?: HTMLElement) => { ref.addEventListener('touchstart', pointerDown); ref.addEventListener('mousedown', pointerDown); - return () => { + const destroy = () => { ref.removeEventListener('keydown', onKeydown); ref.removeEventListener('focusin', onFocusin); ref.removeEventListener('focusout', onFocusout); ref.removeEventListener('touchstart', pointerDown); ref.removeEventListener('mousedown', pointerDown); } + + return { + destroy, + setFocus + } }; diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 7683bdca67d..f832b566614 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -79,6 +79,21 @@ export const focusFirstDescendant = (ref: Element, overlay: HTMLIonOverlayElemen if (firstInput) { firstInput.focus(); + + /** + * When programmatically focusing an element, + * the focus-visible utility will not run because + * it is expecting a keyboard event to have triggered this; + * however, there are times when we need to manually control + * this behavior so we call the `setFocus` method on ion-app + * which will let us explicitly set the elements to focus. + */ + if (firstInput.classList.contains('ion-focusable')) { + const app = overlay.closest('ion-app'); + if (app) { + app.setFocus([firstInput]); + } + } } else { // Focus overlay instead of letting focus escape overlay.focus(); From 4c2739d8d5efc33c132d0a3d7ad891def6d1f99f Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 20 Jul 2021 10:28:46 -0400 Subject: [PATCH 25/26] update test --- core/src/components/select/test/basic/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/select/test/basic/index.html b/core/src/components/select/test/basic/index.html index e4f3ff929af..54d335a0503 100644 --- a/core/src/components/select/test/basic/index.html +++ b/core/src/components/select/test/basic/index.html @@ -135,7 +135,7 @@ Favorite food - + Steak Pizza Tacos From f47e2032548f176efd43f684a8bc68ab4b04d9ec Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Tue, 20 Jul 2021 10:53:47 -0400 Subject: [PATCH 26/26] improve readability --- core/src/components/select-popover/select-popover.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/components/select-popover/select-popover.tsx b/core/src/components/select-popover/select-popover.tsx index 21cf7685682..0f123f0d5c3 100644 --- a/core/src/components/select-popover/select-popover.tsx +++ b/core/src/components/select-popover/select-popover.tsx @@ -155,12 +155,13 @@ export class SelectPopover implements ComponentInterface { render() { const { header, message, options, subHeader } = this; + const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined; return ( {header !== undefined && {header}} - { (subHeader !== undefined || message !== undefined) && + { hasSubHeaderOrMessage && {subHeader !== undefined &&

{subHeader}

}