Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d2bb3e4
update slotMutationController to allow multiple slot names
Oct 20, 2023
4193ff8
Merge branch 'FW-2853' into FW-2853-base
Nov 9, 2023
1921f8a
add slots to a11y test markup
Nov 16, 2023
c4eba46
add test markup
Nov 16, 2023
527fc70
condense label floating behavior into one class
Nov 16, 2023
6c44b5e
add slots
Nov 16, 2023
e993125
prevent slot clicks from opening select
Nov 16, 2023
6087d15
vertically align slots with selected item/label, not toggle icon
Nov 20, 2023
46989f4
add e2e test
Nov 20, 2023
a02bbaf
add more slots styles
Nov 20, 2023
726b60b
lint
Nov 20, 2023
086d906
move listener back to host so select can be programmatically clicked
Nov 21, 2023
d1723ad
chore(): add updated snapshots
Ionitron Nov 21, 2023
2ac5709
fix extra height on some selects
Nov 21, 2023
3c4bce3
Revert "chore(): add updated snapshots"
Nov 21, 2023
ac67043
chore(): add updated snapshots
Ionitron Nov 21, 2023
ddc8b51
fix inner wrapper not being aligned with stacked/floating label place…
Nov 21, 2023
5ad99f4
Revert "chore(): add updated snapshots"
Nov 21, 2023
9e19c1f
chore(): add updated snapshots
Ionitron Nov 21, 2023
abb56c5
fix ellipsis truncation
Nov 21, 2023
f48e1b2
Revert "chore(): add updated snapshots"
Nov 21, 2023
8d10b3e
lint
Nov 21, 2023
91e4b12
chore(): add updated snapshots
Ionitron Nov 21, 2023
4554b30
Revert "chore(): add updated snapshots"
Nov 21, 2023
be7f603
fix ios toggle icon shrinking
Nov 21, 2023
ac339f1
chore(): add updated snapshots
Ionitron Nov 21, 2023
7849d31
Revert "chore(): add updated snapshots"
Nov 21, 2023
eaef271
fix test
Nov 21, 2023
89ee1bc
fix content overlapping toggle icon with stacked/floating labels
Nov 21, 2023
55fc37e
chore(): add updated snapshots
Ionitron Nov 21, 2023
b2d5757
remove unneeded CSS
Nov 21, 2023
4db0ca7
clean up comments
Nov 21, 2023
23cb0f9
change a11y to use icon in start slot instead of button
Nov 22, 2023
8c0269b
add more comments to styles
Nov 22, 2023
600af15
fix slot clicks still going through if button isn't directly slotted
Nov 22, 2023
ea9f12b
add comments about label-floating usage
Nov 22, 2023
97123d8
lint
Nov 22, 2023
68763ef
clean up click listener slot check
Nov 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/src/components/select/select.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
color: #{$text-color-step-350};
}

// Select Native Wrapper
// Select Inner Wrapper
// ----------------------------------------------------------------

:host(.select-label-placement-stacked) .native-wrapper,
:host(.select-label-placement-floating) .native-wrapper {
:host(.select-label-placement-stacked) .select-wrapper-inner,
:host(.select-label-placement-floating) .select-wrapper-inner {
width: calc(100% - $select-ios-icon-size - $select-icon-margin-start);
}

Expand Down
10 changes: 2 additions & 8 deletions core/src/components/select/select.md.outline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@
/**
* 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 {
:host(.label-floating.select-fill-outline) .label-text-wrapper {
@include transform(translateY(-32%), scale(#{$form-control-label-stacked-scale}));
@include margin(0);

Expand Down Expand Up @@ -252,9 +249,6 @@
* 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 {
:host(.label-floating.select-fill-outline) .select-outline-notch {
border-top: none;
}
6 changes: 3 additions & 3 deletions core/src/components/select/select.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@
--border-radius: 16px;
}

// Select Native Wrapper
// Select Inner Wrapper
// ----------------------------------------------------------------

:host(.select-label-placement-stacked) .native-wrapper,
:host(.select-label-placement-floating) .native-wrapper {
:host(.select-label-placement-stacked) .select-wrapper-inner,
:host(.select-label-placement-floating) .select-wrapper-inner {
width: calc(100% - $select-md-icon-size - $select-icon-margin-start);
}

Expand Down
5 changes: 1 addition & 4 deletions core/src/components/select/select.md.solid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@
// 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 {
:host(.label-floating.select-fill-solid) .label-text-wrapper {
/**
* Label text should not extend
* beyond the bounds of the select.
Expand Down
65 changes: 60 additions & 5 deletions core/src/components/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ button {
@include margin(0, 0, 0, $select-icon-margin-start);

position: relative;

/**
* Prevent the icon from shrinking when the label and/or
* selected item text is long enough to fill the rest of
* the container.
*/
flex-shrink: 0;
}

/**
Expand Down Expand Up @@ -259,6 +266,25 @@ button {
transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.select-wrapper-inner {
display: flex;

align-items: center;

overflow: hidden;
}

:host(.select-label-placement-stacked) .select-wrapper-inner,
:host(.select-label-placement-floating) .select-wrapper-inner {
/**
* When using a stacked/floating label, the inner wrapper is
* stacked vertically under the label container. This line
* ensures that the inner wrapper fills all the remaining height
* of the component.
*/
flex-grow: 1;
}

// Select Highlight
// ----------------------------------------------------------------

Expand Down Expand Up @@ -519,11 +545,23 @@ button {
* The placeholder should be hidden when the label
* is on top of the select. This prevents the label
* from overlapping any placeholder value.
*
* TODO(FW-5592): Remove :not(.label-floating) piece
*/
:host(.select-label-placement-floating) .native-wrapper .select-placeholder {
:host(.select-label-placement-floating:not(.label-floating)) .native-wrapper .select-placeholder {
opacity: 0;
}

/**
* We don't use .label-floating here because that would
* also include the case where the label is floating due
* to content in the start/end slot. We want the opacity
* to remain at the default in this case, since the select
* isn't being actively interacted with.
*
* TODO(FW-5592): Change entire selector to:
* :host(.label-floating.select-label-placement-floating) .native-wrapper .select-placeholder
*/
:host(.select-expanded.select-label-placement-floating) .native-wrapper .select-placeholder,
:host(.ion-focused.select-label-placement-floating) .native-wrapper .select-placeholder,
:host(.has-value.select-label-placement-floating) .native-wrapper .select-placeholder {
Expand All @@ -533,10 +571,7 @@ button {
/**
* This makes the label sit above the input.
*/
:host(.select-label-placement-stacked) .label-text-wrapper,
:host(.select-expanded.select-label-placement-floating) .label-text-wrapper,
:host(.ion-focused.select-label-placement-floating) .label-text-wrapper,
:host(.has-value.select-label-placement-floating) .label-text-wrapper {
:host(.label-floating) .label-text-wrapper {
@include transform(translateY(50%), scale(#{$form-control-label-stacked-scale}));

/**
Expand All @@ -545,3 +580,23 @@ button {
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}

// Start/End Slots
// ----------------------------------------------------------------

::slotted([slot="start"]), ::slotted([slot="end"]) {
/**
* Prevent the slots from shrinking when the label and/or
* selected item text is long enough to fill the rest of
* the container.
*/
flex-shrink: 0;
}

::slotted([slot="start"]) {
margin-inline-end: $form-control-label-margin;
}

::slotted([slot="end"]) {
margin-inline-start: $form-control-label-margin;
}
64 changes: 55 additions & 9 deletions core/src/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @slot label - The label text to associate with the select. Use the `labelPlacement` property to control where the label is placed relative to the select. Use this if you need to render a label with custom HTML.
* @slot start - Content to display at the leading edge of the select.
* @slot end - Content to display at the trailing edge of the select.
*
* @part placeholder - The text displayed in the select when there is no value.
* @part text - The displayed value of the select.
Expand Down Expand Up @@ -762,8 +764,22 @@ export class Select implements ComponentInterface {
}

private onClick = (ev: UIEvent) => {
this.setFocus();
this.open(ev);
const target = ev.target as HTMLElement;
const closestSlot = target.closest('[slot="start"], [slot="end"]');

if (target === this.el || closestSlot === null) {
this.setFocus();
this.open(ev);
} else {
/**
* Prevent clicks to the start/end slots from opening the select.
* We ensure the target isn't this element in case the select is slotted
* in, for example, an item. This would prevent the select from ever
* being opened since the element itself has slot="start"/"end".
*/
ev.stopPropagation();
ev.preventDefault();
}
};

private onFocus = () => {
Expand Down Expand Up @@ -864,8 +880,31 @@ export class Select implements ComponentInterface {
const inItem = hostContext('ion-item', this.el);
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;

const hasValue = this.hasValue();
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;

renderHiddenInput(true, el, name, parseValue(value), disabled);

/**
* If the label is stacked, it should always sit above the select.
* For floating labels, the label should move above the select if
* the select has a value, is open, or has anything in either
* the start or end slot.
*
* If there is content in the start slot, the label would overlap
* it if not forced to float. This is also applied to the end slot
* because with the default or solid fills, the select is not
* vertically centered in the container, but the label is. This
* causes the slots and label to appear vertically offset from each
* other when the label isn't floating above the input. This doesn't
* apply to the outline fill, but this was not accounted for to keep
* things consistent.
*
* TODO(FW-5592): Remove hasStartEndSlots condition
*/
const labelShouldFloat =
labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));

return (
<Host
onClick={this.onClick}
Expand All @@ -876,7 +915,8 @@ export class Select implements ComponentInterface {
'select-disabled': disabled,
'select-expanded': isExpanded,
'has-expanded-icon': expandedIcon !== undefined,
'has-value': this.hasValue(),
'has-value': hasValue,
'label-floating': labelShouldFloat,
'has-placeholder': placeholder !== undefined,
'ion-focusable': true,
[`select-${rtl}`]: true,
Expand All @@ -888,17 +928,23 @@ export class Select implements ComponentInterface {
>
<label class="select-wrapper" id="select-label">
{this.renderLabelContainer()}
<div class="native-wrapper" ref={(el) => (this.nativeWrapperEl = el)} part="container">
{this.renderSelectText()}
<div class="select-wrapper-inner">
<slot name="start"></slot>
<div class="native-wrapper" ref={(el) => (this.nativeWrapperEl = el)} part="container">
{this.renderSelectText()}
{this.renderListbox()}
</div>
<slot name="end"></slot>
{!hasFloatingOrStackedLabel && this.renderSelectIcon()}
{this.renderListbox()}
</div>
{/**
* The icon in a floating/stacked select
* must be centered with the entire select,
* not just the native control. As a result,
* we need to render the icon outside of
* the native wrapper.
* while the start/end slots and native control
* are vertically offset in the default or
* solid fills. As a result, we render the
* icon outside the inner wrapper, which holds
* those components.
*/}
{hasFloatingOrStackedLabel && this.renderSelectIcon()}
{shouldRenderHighlight && <div class="select-highlight"></div>}
Expand Down
10 changes: 10 additions & 0 deletions core/src/components/select/test/a11y/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ <h1>Select - a11y</h1>
<ion-select-option value="oranges">Oranges</ion-select-option>
</ion-select>

<ion-select label="My Label" label-placement="floating">
<ion-icon slot="start" name="pizza" aria-hidden="true"></ion-icon>
<ion-select-option value="apples">Apples</ion-select-option>
<ion-select-option value="bananas">Bananas</ion-select-option>
<ion-select-option value="oranges">Oranges</ion-select-option>
<ion-button slot="end" aria-label="button">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
</ion-select>

<ion-item>
<ion-select label="My Label" value="apples">
<ion-select-option value="apples">Apples</ion-select-option>
Expand Down
2 changes: 1 addition & 1 deletion core/src/components/select/test/label/select.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ configs().forEach(({ title, screenshot, config }) => {
test('label should appear on top of the select when the select is expanded', async ({ page }) => {
await page.setContent(
`
<ion-select class="select-expanded" label="Label" label-placement="floating" placeholder="Select a Fruit">
<ion-select class="select-expanded label-floating" label="Label" label-placement="floating" placeholder="Select a Fruit">
<ion-select-option value="apples">Apples</ion-select-option>
</ion-select>
`,
Expand Down
Loading