Skip to content

Commit

Permalink
fix(datetime): allow disabling datetime with prefer-wheel (#28511)
Browse files Browse the repository at this point in the history
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->
It is possible to navigate the columns of a disabled Datetime with
`prefer-wheel` via the keyboard.



https://github.com/ionic-team/ionic-framework/assets/14926794/9c9dafc4-4b77-45a6-a276-70201c5c3ea5



## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Picker Column Internal has a disabled state that disables the full
column
- When a Datetime is disabled with `prefer-wheel`, the columns in the
Datetime will be disabled
- It is no longer possible to navigate the wheels in a disabled Datetime
via the keyboard


Comparison of native & Ionic components:

![Screenshot 2023-11-10 at 10 58
25 AM](https://github.com/ionic-team/ionic-framework/assets/14926794/e2bec1b3-30f8-4f64-8658-27b971884b7a)


## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->

---------

Co-authored-by: ionitron <[email protected]>
Co-authored-by: Liam DeBeasi <[email protected]>
  • Loading branch information
3 people authored Nov 22, 2023
1 parent b833f0e commit 01130e1
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 31 deletions.
8 changes: 8 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2043,6 +2043,10 @@ export namespace Components {
* 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).
*/
"color"?: Color;
/**
* If `true`, the user cannot interact with the picker.
*/
"disabled": boolean;
/**
* A list of options to be displayed in the picker
*/
Expand Down Expand Up @@ -6683,6 +6687,10 @@ declare namespace LocalJSX {
* 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).
*/
"color"?: Color;
/**
* If `true`, the user cannot interact with the picker.
*/
"disabled"?: boolean;
/**
* A list of options to be displayed in the picker
*/
Expand Down
21 changes: 14 additions & 7 deletions core/src/components/datetime/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1538,7 +1538,7 @@ export class Datetime implements ComponentInterface {
}

private renderCombinedDatePickerColumn() {
const { defaultParts, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
const { defaultParts, disabled, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;

const activePart = this.getActivePartsWithFallback();

Expand Down Expand Up @@ -1617,6 +1617,7 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
class="date-column"
color={this.color}
disabled={disabled}
items={items}
value={todayString}
onIonChange={(ev: CustomEvent) => {
Expand Down Expand Up @@ -1728,14 +1729,15 @@ export class Datetime implements ComponentInterface {
return [];
}

const { workingParts } = this;
const { disabled, workingParts } = this;

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column-internal
class="day-column"
color={this.color}
disabled={disabled}
items={days}
value={(workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined}
onIonChange={(ev: CustomEvent) => {
Expand Down Expand Up @@ -1772,14 +1774,15 @@ export class Datetime implements ComponentInterface {
return [];
}

const { workingParts } = this;
const { disabled, workingParts } = this;

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column-internal
class="month-column"
color={this.color}
disabled={disabled}
items={months}
value={workingParts.month}
onIonChange={(ev: CustomEvent) => {
Expand Down Expand Up @@ -1815,14 +1818,15 @@ export class Datetime implements ComponentInterface {
return [];
}

const { workingParts } = this;
const { disabled, workingParts } = this;

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column-internal
class="year-column"
color={this.color}
disabled={disabled}
items={years}
value={workingParts.year}
onIonChange={(ev: CustomEvent) => {
Expand Down Expand Up @@ -1888,14 +1892,15 @@ export class Datetime implements ComponentInterface {
}

private renderHourPickerColumn(hoursData: PickerColumnItem[]) {
const { workingParts } = this;
const { disabled, workingParts } = this;
if (hoursData.length === 0) return [];

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column-internal
color={this.color}
disabled={disabled}
value={activePart.hour}
items={hoursData}
numericInput
Expand All @@ -1916,14 +1921,15 @@ export class Datetime implements ComponentInterface {
);
}
private renderMinutePickerColumn(minutesData: PickerColumnItem[]) {
const { workingParts } = this;
const { disabled, workingParts } = this;
if (minutesData.length === 0) return [];

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column-internal
color={this.color}
disabled={disabled}
value={activePart.minute}
items={minutesData}
numericInput
Expand All @@ -1944,7 +1950,7 @@ export class Datetime implements ComponentInterface {
);
}
private renderDayPeriodPickerColumn(dayPeriodData: PickerColumnItem[]) {
const { workingParts } = this;
const { disabled, workingParts } = this;
if (dayPeriodData.length === 0) {
return [];
}
Expand All @@ -1956,6 +1962,7 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
style={isDayPeriodRTL ? { order: '-1' } : {}}
color={this.color}
disabled={disabled}
value={activePart.ampm}
items={dayPeriodData}
onIonChange={(ev: CustomEvent) => {
Expand Down
39 changes: 39 additions & 0 deletions core/src/components/datetime/test/disabled/datetime.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';

import { Datetime } from '../../../datetime/datetime';
import { PickerColumnInternal } from '../../../picker-column-internal/picker-column-internal';
import { PickerInternal } from '../../../picker-internal/picker-internal';

describe('ion-datetime disabled', () => {
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null,
});
global.IntersectionObserver = mockIntersectionObserver;
});

it('picker should be disabled in prefer wheel mode', async () => {
const page = await newSpecPage({
components: [Datetime, PickerColumnInternal, PickerInternal],
template: () => (
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
),
});

await page.waitForChanges();

const datetime = page.body.querySelector('ion-datetime')!;
const columns = datetime.shadowRoot!.querySelectorAll('ion-picker-column-internal');

await expect(columns.length).toEqual(4);

columns.forEach((column) => {
expect(column.disabled).toBe(true);
});
});
});
5 changes: 5 additions & 0 deletions core/src/components/datetime/test/disabled/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ <h2>Inline</h2>
<h2>Inline - No Default Value</h2>
<ion-datetime id="inline-datetime-no-value" disabled></ion-datetime>
</div>

<div class="grid-item">
<h2>Inline - Prefer Wheel</h2>
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
</div>
</div>
</ion-content>
<script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,20 @@
}

:host .picker-item-empty,
:host .picker-item.picker-item-disabled {
:host .picker-item[disabled] {
cursor: default;
}

:host .picker-item-empty,
:host(:not([disabled])) .picker-item[disabled] {
scroll-snap-align: none;
}

cursor: default;
:host([disabled]) {
overflow-y: hidden;
}

:host .picker-item.picker-item-disabled {
:host .picker-item[disabled] {
opacity: 0.4;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export class PickerColumnInternal implements ComponentInterface {

@Element() el!: HTMLIonPickerColumnInternalElement;

/**
* If `true`, the user cannot interact with the picker.
*/
@Prop() disabled = false;

/**
* A list of options to be displayed in the picker
*/
Expand Down Expand Up @@ -408,13 +413,15 @@ export class PickerColumnInternal implements ComponentInterface {
};

get activeItem() {
return getElementRoot(this.el).querySelector(
`.picker-item[data-value="${this.value}"]:not([disabled])`
) as HTMLElement | null;
// If the whole picker column is disabled, the current value should appear active
// If the current value item is specifically disabled, it should not appear active
const selector = `.picker-item[data-value="${this.value}"]${this.disabled ? '' : ':not([disabled])'}`;

return getElementRoot(this.el).querySelector(selector) as HTMLElement | null;
}

render() {
const { items, color, isActive, numericInput } = this;
const { items, color, disabled: pickerDisabled, isActive, numericInput } = this;
const mode = getIonMode(this);

/**
Expand All @@ -423,10 +430,12 @@ export class PickerColumnInternal implements ComponentInterface {
* the attribute can be moved to datetime.tsx and set on every
* instance of ion-picker-column-internal there instead.
*/

return (
<Host
exportparts={`${PICKER_ITEM_PART}, ${PICKER_ITEM_ACTIVE_PART}`}
tabindex={0}
disabled={pickerDisabled}
tabindex={pickerDisabled ? null : 0}
class={createColorClasses(color, {
[mode]: true,
['picker-column-active']: isActive,
Expand All @@ -443,6 +452,8 @@ export class PickerColumnInternal implements ComponentInterface {
&nbsp;
</div>
{items.map((item, index) => {
const isItemDisabled = pickerDisabled || item.disabled || false;

{
/*
Users should be able to tab
Expand All @@ -458,14 +469,13 @@ export class PickerColumnInternal implements ComponentInterface {
tabindex="-1"
class={{
'picker-item': true,
'picker-item-disabled': item.disabled || false,
}}
data-value={item.value}
data-index={index}
onClick={(ev: Event) => {
this.centerPickerItemInView(ev.target as HTMLElement, true);
}}
disabled={item.disabled}
disabled={isItemDisabled}
part={PICKER_ITEM_PART}
>
{item.text}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,39 @@
<ion-content class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Default</h2>
<h2>Even items disabled</h2>
<ion-picker-internal>
<ion-picker-column-internal id="default"></ion-picker-column-internal>
<ion-picker-column-internal id="half-disabled"></ion-picker-column-internal>
</ion-picker-internal>
</div>
<div class="grid-item">
<h2>Column disabled</h2>
<ion-picker-internal>
<ion-picker-column-internal id="column-disabled" value="11" disabled></ion-picker-column-internal>
</ion-picker-internal>
</div>
</div>
</ion-content>
<script>
const defaultPickerColumn = document.getElementById('default');

const items = Array(24)
const halfDisabledPicker = document.getElementById('half-disabled');
const halfDisabledItems = Array(24)
.fill()
.map((_, i) => ({
text: `${i}`,
value: i,
disabled: i % 2 === 0,
}));
halfDisabledPicker.items = halfDisabledItems;
halfDisabledPicker.value = 12;

defaultPickerColumn.items = items;
defaultPickerColumn.value = 12;
const fullDisabledPicker = document.getElementById('column-disabled');
const items = Array(24)
.fill()
.map((_, i) => ({
text: `${i}`,
value: i,
}));
fullDisabledPicker.items = items;
</script>
</ion-app>
</body>
Expand Down
Loading

0 comments on commit 01130e1

Please sign in to comment.