diff --git a/BREAKING.md b/BREAKING.md
index aec8b18c6a..bdc5c87e36 100644
--- a/BREAKING.md
+++ b/BREAKING.md
@@ -228,7 +228,6 @@ Ionic now listens on the `keydown` event instead of the `keyup` event when deter
| ----------------------- | -------------- | --------- |
| `--placeholder-opacity` | `0.33` | `0.6` |
-
Slides
`ion-slides`, `ion-slide`, and the `IonicSwiper` plugin have been removed from Ionic.
diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts
index fa9f973520..ea84ae9a55 100644
--- a/angular/src/directives/proxies.ts
+++ b/angular/src/directives/proxies.ts
@@ -1903,14 +1903,14 @@ export declare interface IonSelect extends Components.IonSelect {
@ProxyCmp({
defineCustomElementFn: undefined,
- inputs: ['cancelText', 'compareWith', 'disabled', 'interface', 'interfaceOptions', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'value'],
+ inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'legacy', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'value'],
methods: ['open']
})
@Component({
selector: 'ion-select',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '',
- inputs: ['cancelText', 'compareWith', 'disabled', 'interface', 'interfaceOptions', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'value']
+ inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'legacy', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'value']
})
export class IonSelect {
protected el: HTMLElement;
diff --git a/core/api.txt b/core/api.txt
index 846141e229..05c9700dac 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -1228,16 +1228,23 @@ ion-segment-button,part,native
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
ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
ion-select,prop,disabled,boolean,false,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,interfaceOptions,any,{},false,false
+ion-select,prop,justify,"end" | "space-between" | "start",'space-between',false,false
+ion-select,prop,label,string | undefined,undefined,false,false
+ion-select,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start" | undefined,'start',false,false
+ion-select,prop,legacy,boolean | undefined,undefined,false,false
ion-select,prop,mode,"ios" | "md",undefined,false,false
ion-select,prop,multiple,boolean,false,false,false
ion-select,prop,name,string,this.inputId,false,false
ion-select,prop,okText,string,'OK',false,false
ion-select,prop,placeholder,string | undefined,undefined,false,false
ion-select,prop,selectedText,null | string | undefined,undefined,false,false
+ion-select,prop,shape,"round" | undefined,undefined,false,false
ion-select,prop,value,any,undefined,false,false
ion-select,method,open,open(event?: UIEvent) => Promise
ion-select,event,ionBlur,void,true
@@ -1245,12 +1252,21 @@ ion-select,event,ionCancel,void,true
ion-select,event,ionChange,SelectChangeEventDetail,true
ion-select,event,ionDismiss,void,true
ion-select,event,ionFocus,void,true
+ion-select,css-prop,--background
+ion-select,css-prop,--border-color Color of the select border
+ion-select,css-prop,--border-radius Radius of the select border
+ion-select,css-prop,--border-style Style of the select border
+ion-select,css-prop,--border-width Width of the select border
+ion-select,css-prop,--highlight-color-focused The color of the highlight on the select when focused
+ion-select,css-prop,--highlight-color-invalid The color of the highlight on the select when invalid
+ion-select,css-prop,--highlight-color-valid The color of the highlight on the select when valid
ion-select,css-prop,--padding-bottom
ion-select,css-prop,--padding-end
ion-select,css-prop,--padding-start
ion-select,css-prop,--padding-top
ion-select,css-prop,--placeholder-color
ion-select,css-prop,--placeholder-opacity
+ion-select,css-prop,--ripple-color
ion-select,part,icon
ion-select,part,placeholder
ion-select,part,text
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 360df54fde..0ef32926a5 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -2595,6 +2595,10 @@ export namespace Components {
* The text to display on the cancel button.
*/
"cancelText": string;
+ /**
+ * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). This property is only available when using the modern select syntax.
+ */
+ "color"?: Color;
/**
* A property name or function used to compare object values
*/
@@ -2603,6 +2607,10 @@ export namespace Components {
* If `true`, the user cannot interact with the select.
*/
"disabled": boolean;
+ /**
+ * The fill for the item. If `'solid'` the item will have a background. If `'outline'` the item will be transparent with a border. Only available in `md` mode.
+ */
+ "fill"?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover` or `alert`.
*/
@@ -2611,6 +2619,22 @@ export namespace Components {
* 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.
*/
"interfaceOptions": any;
+ /**
+ * How to pack the label and select within a line. `justify` does not apply when the label and select are on different lines when `labelPlacement` is set to `'floating'` or `'stacked'`. `'start'`: The label and select will appear on the left in LTR and on the right in RTL. `'end'`: The label and select will appear on the right in LTR and on the left in RTL. `'space-between'`: The label and select will appear on opposite ends of the line with space between the two elements.
+ */
+ "justify": 'start' | 'end' | 'space-between';
+ /**
+ * The visible label associated with the select.
+ */
+ "label"?: string;
+ /**
+ * Where to place the label relative to the select. `'start'`: The label will appear to the left of the select in LTR and to the right in RTL. `'end'`: The label will appear to the right of the select in LTR and to the left in RTL. `'floating'`: The label will appear smaller and above the select when the select is focused or it has a value. Otherwise it will appear on top of the select. `'stacked'`: The label will appear smaller and above the select regardless even when the select is blurred or has no value. `'fixed'`: The label has the same behavior as `'start'` except it also has a fixed width. Long text will be truncated with ellipses ("..."). When using `'floating'` or `'stacked'` we recommend initializing the select with either a `value` or a `placeholder`.
+ */
+ "labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
+ /**
+ * Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the `label` property. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
+ */
+ "legacy"?: boolean;
/**
* The mode determines which platform styles to use.
*/
@@ -2640,6 +2664,10 @@ export namespace Components {
* The text to display instead of the selected option's value.
*/
"selectedText"?: string | null;
+ /**
+ * The shape of the select. If "round" it will have an increased border radius.
+ */
+ "shape"?: 'round';
/**
* the value of the select.
*/
@@ -6589,6 +6617,10 @@ declare namespace LocalJSX {
* The text to display on the cancel button.
*/
"cancelText"?: string;
+ /**
+ * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). This property is only available when using the modern select syntax.
+ */
+ "color"?: Color;
/**
* A property name or function used to compare object values
*/
@@ -6597,6 +6629,10 @@ declare namespace LocalJSX {
* If `true`, the user cannot interact with the select.
*/
"disabled"?: boolean;
+ /**
+ * The fill for the item. If `'solid'` the item will have a background. If `'outline'` the item will be transparent with a border. Only available in `md` mode.
+ */
+ "fill"?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover` or `alert`.
*/
@@ -6605,6 +6641,22 @@ declare namespace LocalJSX {
* 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.
*/
"interfaceOptions"?: any;
+ /**
+ * How to pack the label and select within a line. `justify` does not apply when the label and select are on different lines when `labelPlacement` is set to `'floating'` or `'stacked'`. `'start'`: The label and select will appear on the left in LTR and on the right in RTL. `'end'`: The label and select will appear on the right in LTR and on the left in RTL. `'space-between'`: The label and select will appear on opposite ends of the line with space between the two elements.
+ */
+ "justify"?: 'start' | 'end' | 'space-between';
+ /**
+ * The visible label associated with the select.
+ */
+ "label"?: string;
+ /**
+ * Where to place the label relative to the select. `'start'`: The label will appear to the left of the select in LTR and to the right in RTL. `'end'`: The label will appear to the right of the select in LTR and to the left in RTL. `'floating'`: The label will appear smaller and above the select when the select is focused or it has a value. Otherwise it will appear on top of the select. `'stacked'`: The label will appear smaller and above the select regardless even when the select is blurred or has no value. `'fixed'`: The label has the same behavior as `'start'` except it also has a fixed width. Long text will be truncated with ellipses ("..."). When using `'floating'` or `'stacked'` we recommend initializing the select with either a `value` or a `placeholder`.
+ */
+ "labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
+ /**
+ * Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the `label` property. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
+ */
+ "legacy"?: boolean;
/**
* The mode determines which platform styles to use.
*/
@@ -6653,6 +6705,10 @@ declare namespace LocalJSX {
* The text to display instead of the selected option's value.
*/
"selectedText"?: string | null;
+ /**
+ * The shape of the select. If "round" it will have an increased border radius.
+ */
+ "shape"?: 'round';
/**
* the value of the select.
*/
diff --git a/core/src/components/item/item.ios.scss b/core/src/components/item/item.ios.scss
index fe9a399d31..9f6440450b 100644
--- a/core/src/components/item/item.ios.scss
+++ b/core/src/components/item/item.ios.scss
@@ -199,8 +199,8 @@
--min-height: 68px;
}
-:host(.item-label-stacked) ::slotted(ion-select),
-:host(.item-label-floating) ::slotted(ion-select) {
+:host(.item-label-stacked) ::slotted(ion-select.legacy-select),
+:host(.item-label-floating) ::slotted(ion-select.legacy-select) {
--padding-top: 8px;
--padding-bottom: 8px;
--padding-start: 0px;
@@ -210,7 +210,7 @@
// iOS Fixed Labels
// --------------------------------------------------
-:host(.item-label-fixed) ::slotted(ion-select),
+:host(.item-label-fixed) ::slotted(ion-select.legacy-select),
:host(.item-label-fixed) ::slotted(ion-datetime) {
--padding-start: 0;
}
diff --git a/core/src/components/item/item.md.scss b/core/src/components/item/item.md.scss
index 66570ae0b4..d78ce0557f 100644
--- a/core/src/components/item/item.md.scss
+++ b/core/src/components/item/item.md.scss
@@ -315,7 +315,7 @@
// Material Design Fixed Labels
// --------------------------------------------------
-:host(.item-label-fixed) ::slotted(ion-select),
+:host(.item-label-fixed) ::slotted(ion-select.legacy-select),
:host(.item-label-fixed) ::slotted(ion-datetime) {
--padding-start: 8px;
}
@@ -361,8 +361,8 @@
--min-height: 55px;
}
-:host(.item-label-stacked) ::slotted(ion-select),
-:host(.item-label-floating) ::slotted(ion-select) {
+:host(.item-label-stacked) ::slotted(ion-select.legacy-select),
+:host(.item-label-floating) ::slotted(ion-select.legacy-select) {
--padding-top: 8px;
--padding-bottom: 8px;
--padding-start: 0;
diff --git a/core/src/components/item/item.scss b/core/src/components/item/item.scss
index 6ccf5054e3..402b8eb6ed 100644
--- a/core/src/components/item/item.scss
+++ b/core/src/components/item/item.scss
@@ -490,14 +490,14 @@ button, a {
// Item Select
// --------------------------------------------------
-:host(:not(.item-label)) ::slotted(ion-select) {
+:host(:not(.item-label)) ::slotted(ion-select.legacy-select) {
--padding-start: 0;
max-width: none;
}
-:host(.item-label-stacked) ::slotted(ion-select),
-:host(.item-label-floating) ::slotted(ion-select) {
+:host(.item-label-stacked) ::slotted(ion-select.legacy-select),
+:host(.item-label-floating) ::slotted(ion-select.legacy-select) {
--padding-top: 8px;
--padding-bottom: 8px;
--padding-start: 0;
@@ -533,7 +533,7 @@ button, a {
:host(.item-multiple-inputs) ::slotted(ion-checkbox),
:host(.item-multiple-inputs) ::slotted(ion-datetime),
:host(.item-multiple-inputs) ::slotted(ion-radio),
-:host(.item-multiple-inputs) ::slotted(ion-select) {
+:host(.item-multiple-inputs) ::slotted(ion-select.legacy-select) {
position: relative;
}
diff --git a/core/src/components/select/select.ios.scss b/core/src/components/select/select.ios.scss
index 25a6645225..fa54e598cc 100644
--- a/core/src/components/select/select.ios.scss
+++ b/core/src/components/select/select.ios.scss
@@ -4,13 +4,27 @@
// iOS Select
// --------------------------------------------------
-:host {
+:host(.legacy-select) {
--padding-top: #{$select-ios-padding-top};
--padding-end: #{$select-ios-padding-end};
--padding-bottom: #{$select-ios-padding-bottom};
--padding-start: #{$select-ios-padding-start};
}
-.select-icon {
- opacity: .33;
+:host(:not(.legacy-select)) {
+ min-height: 44px;
+}
+
+/**
+ * Since the label sits on top of the element,
+ * the component needs to be taller otherwise the
+ * label will appear too close to the select text.
+ */
+:host(.select-label-placement-floating),
+:host(.select-label-placement-stacked) {
+ min-height: 56px;
+}
+
+.select-icon {
+ color: #{$text-color-step-700};
}
diff --git a/core/src/components/select/select.md.outline.scss b/core/src/components/select/select.md.outline.scss
new file mode 100644
index 0000000000..4664152ca0
--- /dev/null
+++ b/core/src/components/select/select.md.outline.scss
@@ -0,0 +1,258 @@
+@import "./select.vars";
+
+// Select Fill: Outline
+// ----------------------------------------------------------------
+
+:host(.select-fill-outline) {
+ --border-color: #{$background-color-step-300};
+ --border-radius: 4px;
+ --padding-start: 16px;
+ --padding-end: 16px;
+}
+
+:host(.select-fill-outline.select-shape-round) {
+ --border-radius: 28px;
+ --padding-start: 32px;
+ --padding-end: 32px;
+}
+
+/**
+ * If the select has a validity state, the
+ * border should reflect that as a color.
+ */
+:host(.select-fill-outline.ion-touched.ion-valid),
+:host(.select-fill-outline.ion-touched.ion-invalid) {
+ --border-color: var(--highlight-color);
+}
+
+/**
+ * Border should be
+ * slightly darker on hover.
+ */
+@media (any-hover: hover) {
+ :host(.select-fill-outline:hover) {
+ --border-color: #{$background-color-step-750};
+ }
+}
+
+/**
+ * The border should get thicker
+ * and take on component color when
+ * the select is focused.
+ */
+:host(.select-fill-outline.select-expanded),
+:host(.select-fill-outline.ion-focused) {
+ --border-width: 2px;
+ --border-color: var(--highlight-color);
+}
+
+/**
+ * The bottom content should never have
+ * a border with the outline style.
+ */
+:host(.select-fill-outline) .select-bottom {
+ border-top: none;
+}
+
+/**
+ * Outline selects do not have a bottom border.
+ * Instead, they have a border that wraps the
+ * select + label.
+ */
+:host(.select-fill-outline) .select-wrapper {
+ border-bottom: none;
+}
+
+:host(.select-ltr.select-fill-outline.select-label-placement-stacked) .label-text-wrapper,
+:host(.select-ltr.select-fill-outline.select-label-placement-floating) .label-text-wrapper {
+ // stylelint-disable-next-line property-disallowed-list
+ transform-origin: left top;
+}
+
+:host(.select-rtl.select-fill-outline.select-label-placement-stacked) .label-text-wrapper,
+:host(.select-rtl.select-fill-outline.select-label-placement-floating) .label-text-wrapper {
+ // stylelint-disable-next-line property-disallowed-list
+ transform-origin: right top;
+}
+
+:host(.select-fill-outline.select-label-placement-stacked) .label-text-wrapper,
+:host(.select-fill-outline.select-label-placement-floating) .label-text-wrapper {
+ position: absolute;
+
+ /**
+ * Label text should not extend
+ * beyond the bounds of the select.
+ */
+ max-width: calc(100% - var(--padding-start) - var(--padding-end));
+}
+
+/**
+ * The label should appear on top of an outline
+ * container that overlaps it so it is always clickable.
+ */
+:host(.select-fill-outline) .label-text-wrapper,
+:host(.select-fill-outline) .label-text-wrapper {
+ position: relative;
+
+ z-index: 1;
+}
+
+/**
+ * This makes the label sit above the select.
+ */
+:host(.select-expanded.select-fill-outline.select-label-placement-floating) .label-text-wrapper,
+:host(.ion-focused.select-fill-outline.select-label-placement-floating) .label-text-wrapper,
+:host(.has-value.select-fill-outline.select-label-placement-floating) .label-text-wrapper,
+:host(.select-fill-outline.select-label-placement-stacked) .label-text-wrapper {
+ @include transform(translateY(-32%), scale(#{$select-floating-label-scale}));
+ @include margin(0);
+
+ /**
+ * Label text should not extend
+ * beyond the bounds of the select.
+ */
+ max-width: calc((100% - var(--padding-start) - var(--padding-end) - #{$select-md-floating-label-padding * 2}) / #{$select-floating-label-scale});
+}
+
+/**
+ * This ensures that the select does not
+ * overlap the floating label while still
+ * remaining visually centered.
+ */
+:host(.select-fill-outline.select-label-placement-stacked) select,
+:host(.select-fill-outline.select-label-placement-floating) select {
+ @include margin(6px, 0, 6px, 0);
+}
+
+// Select Fill: Outline Outline Container
+// ----------------------------------------------------------------
+
+:host(.select-fill-outline) .select-outline-container {
+ @include position(0, 0, 0, 0);
+
+ display: flex;
+
+ position: absolute;
+
+ width: 100%;
+ height: 100%;
+}
+
+:host(.select-fill-outline) .select-outline-start,
+:host(.select-fill-outline) .select-outline-end {
+ pointer-events: none;
+}
+
+/**
+ * By default, each piece of the container should have
+ * a top and bottom border. This gives the appearance
+ * of a unified container with a border.
+ */
+:host(.select-fill-outline) .select-outline-start,
+:host(.select-fill-outline) .select-outline-notch,
+:host(.select-fill-outline) .select-outline-end {
+ border-top: var(--border-width) var(--border-style) var(--border-color);
+ border-bottom: var(--border-width) var(--border-style) var(--border-color);
+
+ /**
+ * `border-box` is applied in the global
+ * Ionic stylesheet, but since this is in
+ * the Shadow DOM, these elements do not
+ * receive the global style. The outline
+ * pieces for `ion-input` do because that
+ * component is in the Light DOM.
+ */
+ box-sizing: border-box;
+}
+
+/**
+ * Ensures long labels do not cause the notch to flow
+ * out of bounds.
+ */
+:host(.select-fill-outline) .select-outline-notch {
+ max-width: calc(100% - var(--padding-start) - var(--padding-end));
+}
+
+/**
+ * This element ensures that the notch used
+ * the size of the scaled text so that the
+ * border cut out is the correct width.
+ * The text in this element should not
+ * be interactive.
+ */
+:host(.select-fill-outline) .notch-spacer {
+ /**
+ * We need $select-md-floating-label-padding of padding on the right.
+ * However, we also subtracted $select-md-floating-label-padding from
+ * the width of .select-outline-start
+ * to create space, so we need to take
+ * that into consideration here.
+ */
+ @include padding(null, #{$select-md-floating-label-padding * 2}, null, null);
+
+ font-size: calc(1em * #{$select-floating-label-scale});
+
+ opacity: 0;
+ pointer-events: none;
+}
+
+:host(.select-ltr.select-fill-outline) .select-outline-start {
+ // stylelint-disable-next-line property-disallowed-list
+ border-left: var(--border-width) var(--border-style) var(--border-color);
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: var(--border-radius) 0px 0px var(--border-radius);
+}
+
+:host(.select-rtl.select-fill-outline) .select-outline-start {
+ // stylelint-disable-next-line property-disallowed-list
+ border-right: var(--border-width) var(--border-style) var(--border-color);
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: 0px var(--border-radius) var(--border-radius) 0px;
+}
+
+:host(.select-fill-outline) .select-outline-start {
+ /**
+ * There should be spacing between the translated text
+ * and .select-outline-start. However, we can't add this
+ * spacing onto the notch because it would cause the
+ * label to look like it is not aligned with the
+ * text select. Instead, we subtract a few pixels from
+ * this element.
+ */
+ width: calc(var(--padding-start) - #{$select-md-floating-label-padding});
+}
+
+:host(.select-ltr.select-fill-outline) .select-outline-end {
+ // stylelint-disable-next-line property-disallowed-list
+ border-right: var(--border-width) var(--border-style) var(--border-color);
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: 0px var(--border-radius) var(--border-radius) 0px;
+}
+
+:host(.select-rtl.select-fill-outline) .select-outline-end {
+ // stylelint-disable-next-line property-disallowed-list
+ border-left: var(--border-width) var(--border-style) var(--border-color);
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: var(--border-radius) 0px 0px var(--border-radius);
+}
+
+:host(.select-fill-outline) .select-outline-end {
+ /**
+ * The ending outline fragment
+ * should take up the remaining free space.
+ */
+ flex-grow: 1;
+}
+
+/**
+ * When the select either has focus or a value,
+ * there should be a "cut out" at the top for
+ * the floating/stacked label. We simulate this "cut out"
+ * by removing the top border from the notch fragment.
+ */
+:host(.select-expanded.select-fill-outline.select-label-placement-floating) .select-outline-notch,
+:host(.ion-focused.select-fill-outline.select-label-placement-floating) .select-outline-notch,
+:host(.has-value.select-fill-outline.select-label-placement-floating) .select-outline-notch,
+:host(.select-fill-outline.select-label-placement-stacked) .select-outline-notch {
+ border-top: none;
+}
diff --git a/core/src/components/select/select.md.scss b/core/src/components/select/select.md.scss
index 36d8dccbf5..b7aba1588a 100644
--- a/core/src/components/select/select.md.scss
+++ b/core/src/components/select/select.md.scss
@@ -1,21 +1,110 @@
@import "./select";
@import "./select.md.vars";
+@import "./select.md.solid.scss";
+@import "./select.md.outline.scss";
// Material Design Select
// --------------------------------------------------
:host {
+ --border-width: 1px;
+ --border-color: #{$item-md-border-color};
+}
+
+:host(.legacy-select) {
--padding-top: #{$select-md-padding-top};
--padding-end: #{$select-md-padding-end};
--padding-bottom: #{$select-md-padding-bottom};
--padding-start: #{$select-md-padding-start};
}
+:host(:not(.legacy-select)) {
+ min-height: 56px;
+}
+
.select-icon {
transition: transform .15s cubic-bezier(.4, 0, .2, 1);
- opacity: .55;
+ color: #{$text-color-step-500};
+}
+// Select Label
+// ----------------------------------------------------------------
+
+/**
+ * When the select is focused the label should
+ * take on the highlight color. This should
+ * only apply to floating or stacked labels.
+ */
+:host(.select-label-placement-floating.select-expanded) .label-text-wrapper,
+:host(.select-label-placement-floating.ion-focused) .label-text-wrapper,
+:host(.select-label-placement-stacked.select-expanded) .label-text-wrapper,
+:host(.select-label-placement-stacked.ion-focused) .label-text-wrapper {
+ color: var(--highlight-color);
+}
+
+:host(.select-label-placement-floating.ion-touched.ion-valid) .label-text-wrapper,
+:host(.select-label-placement-floating.ion-touched.ion-invalid) .label-text-wrapper,
+:host(.select-label-placement-stacked.ion-touched.ion-valid) .label-text-wrapper,
+:host(.select-label-placement-stacked.ion-touched.ion-invalid) .label-text-wrapper {
+ color: var(--highlight-color);
+}
+
+// Select Highlight
+// ----------------------------------------------------------------
+
+.select-highlight {
+ @include position(null, null, -1px, 0);
+
+ position: absolute;
+
+ width: 100%;
+ height: 2px;
+
+ transform: scale(0);
+
+ transition: transform 200ms;
+
+ background: var(--highlight-color);
+}
+
+:host(.select-expanded) .select-highlight,
+:host(.ion-focused) .select-highlight {
+ transform: scale(1);
+}
+
+/**
+ * Adjust the highlight up by 1px
+ * so it is not cut off by the
+ * the item's line (if one is present).
+ */
+:host(.in-item) .select-highlight {
+ @include position(null, null, 0, 0);
+}
+
+// Select Icon
+// ----------------------------------------------------------------
+
+/**
+ * This rotates the chevron icon
+ * when the select is activated.
+ * This should only happen on MD.
+ */
+:host(.select-expanded:not(.legacy-select)) .select-icon {
+ @include transform(rotate(180deg));
+}
+
+/**
+ * When the select is focused the icon should
+ * take on the highlight color.
+ * The icon should also take on the highlight
+ * color if there is a validation state.
+ */
+:host(.select-expanded) .select-wrapper .select-icon,
+:host(.ion-touched.ion-valid) .select-wrapper .select-icon,
+:host(.ion-touched.ion-invalid) .select-wrapper .select-icon,
+:host(.ion-focused) .select-wrapper .select-icon {
+ color: var(--highlight-color);
}
/**
@@ -43,9 +132,9 @@
@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);
+// Select Shape Rounded
+// ----------------------------------------------------------------
- opacity: 1;
+:host(.select-shape-round) {
+ --border-radius: 16px;
}
diff --git a/core/src/components/select/select.md.solid.scss b/core/src/components/select/select.md.solid.scss
new file mode 100644
index 0000000000..9af5003b60
--- /dev/null
+++ b/core/src/components/select/select.md.solid.scss
@@ -0,0 +1,78 @@
+@import "./select.vars";
+
+// Select Fill: Solid
+// ----------------------------------------------------------------
+
+:host(.select-fill-solid) {
+ --background: #{$background-color-step-50};
+ --border-color: #{$background-color-step-500};
+ --border-radius: 4px;
+ --padding-start: 16px;
+ --padding-end: 16px;
+}
+
+/**
+ * The solid fill style has a border
+ * at the bottom of the select wrapper.
+ * As a result, the border on the "bottom
+ * content" is not needed.
+ */
+:host(.select-fill-solid) .select-wrapper {
+ border-bottom: var(--border-width) var(--border-style) var(--border-color);
+}
+
+/**
+ * If the select has a validity state, the
+ * border should reflect that as a color.
+ */
+:host(.select-fill-solid.ion-touched.ion-valid) .select-wrapper,
+:host(.select-fill-solid.ion-touched.ion-invalid) .select-wrapper {
+ --border-color: var(--highlight-color);
+}
+
+:host(.select-fill-solid) .select-bottom {
+ border-top: none;
+}
+
+/**
+ * Background and border should be
+ * slightly darker on hover.
+ */
+@media (any-hover: hover) {
+ :host(.select-fill-solid:hover) {
+ --background: #{$background-color-step-100};
+ --border-color: #{$background-color-step-750};
+ }
+}
+
+/**
+ * Background and border should be
+ * much darker on focus.
+ */
+:host(.select-fill-solid.select-expanded),
+:host(.select-fill-solid.ion-focused) {
+ --background: #{$background-color-step-150};
+ --border-color: #{$background-color-step-750};
+}
+
+:host(.select-fill-solid) .select-wrapper {
+ /**
+ * Only the top left and top right borders should.
+ * have a radius when using a solid fill.
+ */
+ @include border-radius(var(--border-radius), var(--border-radius), 0px, 0px);
+}
+
+// Select Label
+// ----------------------------------------------------------------
+
+:host(.select-fill-solid.select-label-placement-stacked) .label-text-wrapper,
+:host(.select-expanded.select-fill-solid.select-label-placement-floating) .label-text-wrapper,
+:host(.ion-focused.select-fill-solid.select-label-placement-floating) .label-text-wrapper,
+:host(.has-value.select-fill-solid.select-label-placement-floating) .label-text-wrapper {
+ /**
+ * Label text should not extend
+ * beyond the bounds of the select.
+ */
+ max-width: calc(100% / #{$select-floating-label-scale});
+}
diff --git a/core/src/components/select/select.md.vars.scss b/core/src/components/select/select.md.vars.scss
index ef6f37020b..ea20b33d5b 100644
--- a/core/src/components/select/select.md.vars.scss
+++ b/core/src/components/select/select.md.vars.scss
@@ -24,3 +24,6 @@ $select-md-placeholder-color: $select-md-icon-color !default;
/// @prop - Text Color of the selected item
$select-md-text-color: $text-color !default;
+
+/// @prop - The amount of whitespace to display on either side of the floating label
+$select-md-floating-label-padding: 4px !default;
diff --git a/core/src/components/select/select.scss b/core/src/components/select/select.scss
index 6d2d7a9019..6423398941 100644
--- a/core/src/components/select/select.scss
+++ b/core/src/components/select/select.scss
@@ -5,6 +5,7 @@
:host {
/**
+ * @prop --background: Background of the select
* @prop --padding-top: Top padding of the select
* @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select
* @prop --padding-bottom: Bottom padding of the select
@@ -12,29 +13,75 @@
*
* @prop --placeholder-color: Color of the select placeholder text
* @prop --placeholder-opacity: Opacity of the select placeholder text
+ *
+ * @prop --highlight-color-focused The color of the highlight on the select when focused
+ * @prop --highlight-color-invalid The color of the highlight on the select when invalid
+ * @prop --highlight-color-valid The color of the highlight on the select when valid
+ *
+ * @prop --border-color Color of the select border
+ * @prop --border-radius Radius of the select border
+ * @prop --border-style Style of the select border
+ * @prop --border-width Width of the select border
+ *
+ * @prop --ripple-color: The color of the ripple effect on MD mode.
*/
+ --padding-top: 0px;
+ --padding-end: 0px;
+ --padding-bottom: 0px;
+ --padding-start: 0px;
--placeholder-color: currentColor;
--placeholder-opacity: #{$placeholder-opacity};
+ --background: transparent;
+ --border-style: solid;
+ --highlight-color-focused: #{ion-color(primary, base)};
+ --highlight-color-valid: #{ion-color(success, base)};
+ --highlight-color-invalid: #{ion-color(danger, base)};
- @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
+ /**
+ * This is a private API that is used to switch
+ * out the highlight color based on the state
+ * of the component without having to write
+ * different selectors for different fill variants.
+ */
+ --highlight-color: var(--highlight-color-focused);
- display: flex;
+ display: block;
position: relative;
- align-items: center;
-
font-family: $font-family-base;
- overflow: hidden;
+ cursor: pointer;
+
z-index: $z-index-item-input;
}
-:host(.in-item) {
+:host(.ion-color) {
+ --highlight-color-focused: #{current-color(base)};
+}
+
+:host(.legacy-select) {
+ @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
+
+ display: flex;
+
+ align-items: center;
+
+ overflow: hidden;
+}
+
+// Select Used in ion-item
+// --------------------------------------------------
+:host(.in-item.legacy-select) {
position: static;
max-width: 45%;
}
+:host(.in-item:not(.legacy-select)) {
+ width: 100%;
+ height: 100%;
+}
+
:host(.select-disabled) {
opacity: .4;
pointer-events: none;
@@ -50,7 +97,7 @@
opacity: var(--placeholder-opacity);
}
-label {
+:host(.legacy-select) label {
@include input-cover();
display: flex;
@@ -64,6 +111,9 @@ button {
@include visually-hidden();
}
+// Select Icon
+// --------------------------------------------------
+
.select-icon {
@include margin(0, 0, 0, 4px);
@@ -72,6 +122,47 @@ button {
width: 13px;
}
+/**
+ * Ensure that the select icon has
+ * the correct color contrast when
+ * used inside of an item.
+ */
+:host(.in-item-color) .select-icon {
+ color: inherit;
+}
+
+/**
+ * The select icon should be centered with
+ * the entire container not just the control
+ * with floating/stacked labels.
+ */
+:host(.select-label-placement-stacked) .select-icon,
+:host(.select-label-placement-floating) .select-icon {
+ position: absolute;
+
+ height: 100%;
+}
+
+/**
+ * This positions the icon at the correct
+ * edge of the component with LTR and RTL
+ * text directions. The position mixin cannot be
+ * used here because the icon is in the Shadow DOM.
+ */
+:host(.select-ltr.select-label-placement-stacked) .select-icon,
+:host(.select-ltr.select-label-placement-floating) .select-icon {
+ // stylelint-disable-next-line property-disallowed-list
+ right: var(--padding-end, 0);
+}
+
+:host(.select-rtl.select-label-placement-stacked) .select-icon,
+:host(.select-rtl.select-label-placement-floating) .select-icon {
+ // stylelint-disable-next-line property-disallowed-list
+ left: var(--padding-start, 0);
+}
+
+// Select Text
+// --------------------------------------------------
.select-text {
flex: 1;
@@ -85,3 +176,302 @@ button {
overflow: hidden;
}
+
+// Select Wrapper
+// --------------------------------------------------
+
+.select-wrapper {
+ @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
+
+ display: flex;
+
+ position: relative;
+
+ flex-grow: 1;
+
+ align-items: center;
+
+ height: 100%;
+
+ /**
+ * This allows developers to set the height
+ * on the host of the element but still have
+ * the label take up the full height
+ * of the parent.
+ */
+ min-height: inherit;
+
+ transition: background-color 15ms linear;
+
+ background: var(--background);
+
+ cursor: inherit;
+
+ box-sizing: border-box;
+}
+
+// Select Highlight
+// ----------------------------------------------------------------
+
+:host(.ion-touched.ion-invalid) {
+ --highlight-color: var(--highlight-color-invalid);
+}
+
+:host(.ion-touched.ion-valid) {
+ --highlight-color: var(--highlight-color-valid);
+}
+
+// Select Label
+// ----------------------------------------------------------------
+
+.label-text-wrapper {
+ /**
+ * This causes the label to take up
+ * the entire height of its container
+ * while still keeping the text centered.
+ */
+ display: flex;
+
+ align-items: center;
+
+ /**
+ * Label text should not extend
+ * beyond the bounds of the select.
+ * However, we do not set the max
+ * width to 100% because then
+ * only the label would show and users
+ * would not be able to see what they are typing.
+ */
+ max-width: 200px;
+
+ transition: color 150ms cubic-bezier(.4, 0, .2, 1), transform 150ms cubic-bezier(.4, 0, .2, 1);
+
+ /**
+ * This ensures that double tapping this text
+ * clicks the