feat(datetime): add showAdjacentDays to display days from the previous and next months (#30262)
Issue number: Internal ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> Adds a new property to datetime, showAdjacentDays, that when true will show the last days of the previous month and the first days of the next month. This will just occupy empty "cells" at the beginning of the month "table" and add rows to the table until a maximum of 6 rows are displayed. ## Changes - add styles for adjacent day button - add `showAdjacentDays` property to datetime component - change month generation to respect new property - add visual tests to new feature ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview](https://ionic-framework-git-rou-11118v2-ionic1.vercel.app/src/components/datetime/test/show-adjacent-days) --------- Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com> Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
@ -534,6 +534,7 @@ ion-datetime,prop,name,string,this.inputId,false,false
|
|||||||
ion-datetime,prop,preferWheel,boolean,false,false,false
|
ion-datetime,prop,preferWheel,boolean,false,false,false
|
||||||
ion-datetime,prop,presentation,"date" | "date-time" | "month" | "month-year" | "time" | "time-date" | "year",'date-time',false,false
|
ion-datetime,prop,presentation,"date" | "date-time" | "month" | "month-year" | "time" | "time-date" | "year",'date-time',false,false
|
||||||
ion-datetime,prop,readonly,boolean,false,false,false
|
ion-datetime,prop,readonly,boolean,false,false,false
|
||||||
|
ion-datetime,prop,showAdjacentDays,boolean,false,false,false
|
||||||
ion-datetime,prop,showClearButton,boolean,false,false,false
|
ion-datetime,prop,showClearButton,boolean,false,false,false
|
||||||
ion-datetime,prop,showDefaultButtons,boolean,false,false,false
|
ion-datetime,prop,showDefaultButtons,boolean,false,false,false
|
||||||
ion-datetime,prop,showDefaultTimeLabel,boolean,true,false,false
|
ion-datetime,prop,showDefaultTimeLabel,boolean,true,false,false
|
||||||
|
8
core/src/components.d.ts
vendored
@ -944,6 +944,10 @@ export namespace Components {
|
|||||||
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
|
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
|
||||||
*/
|
*/
|
||||||
"reset": (startDate?: string) => Promise<void>;
|
"reset": (startDate?: string) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* If `true`, the datetime calendar displays a six-week (42-day) layout, including days from the previous and next months to fill the grid. These adjacent days are selectable unless disabled.
|
||||||
|
*/
|
||||||
|
"showAdjacentDays": boolean;
|
||||||
/**
|
/**
|
||||||
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
|
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
|
||||||
*/
|
*/
|
||||||
@ -5779,6 +5783,10 @@ declare namespace LocalJSX {
|
|||||||
* If `true`, the datetime appears normal but the selected date cannot be changed.
|
* If `true`, the datetime appears normal but the selected date cannot be changed.
|
||||||
*/
|
*/
|
||||||
"readonly"?: boolean;
|
"readonly"?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the datetime calendar displays a six-week (42-day) layout, including days from the previous and next months to fill the grid. These adjacent days are selectable unless disabled.
|
||||||
|
*/
|
||||||
|
"showAdjacentDays"?: boolean;
|
||||||
/**
|
/**
|
||||||
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
|
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
|
||||||
*/
|
*/
|
||||||
|
@ -15,6 +15,7 @@ export interface DatetimeParts {
|
|||||||
hour?: number;
|
hour?: number;
|
||||||
minute?: number;
|
minute?: number;
|
||||||
ampm?: 'am' | 'pm';
|
ampm?: 'am' | 'pm';
|
||||||
|
isAdjacentDay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DatetimePresentation = 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year';
|
export type DatetimePresentation = 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year';
|
||||||
|
@ -267,6 +267,10 @@
|
|||||||
color: current-color(contrast);
|
color: current-color(contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host .calendar-day.calendar-day-adjacent-day {
|
||||||
|
color: $text-color-step-700;
|
||||||
|
}
|
||||||
|
|
||||||
// Time / Header
|
// Time / Header
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
:host .datetime-time {
|
:host .datetime-time {
|
||||||
|
@ -121,12 +121,17 @@
|
|||||||
color: current-color(contrast);
|
color: current-color(contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-day.calendar-day-active {
|
.calendar-day.calendar-day-active,
|
||||||
|
.calendar-day.calendar-day-active:focus {
|
||||||
border: 1px solid current-color(base);
|
border: 1px solid current-color(base);
|
||||||
|
|
||||||
background: current-color(base);
|
background: current-color(base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host .calendar-day.calendar-day-adjacent-day {
|
||||||
|
color: $text-color-step-500;
|
||||||
|
}
|
||||||
|
|
||||||
// Time / Header
|
// Time / Header
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
:host .datetime-time {
|
:host .datetime-time {
|
||||||
|
@ -364,7 +364,8 @@
|
|||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-day:focus {
|
|
||||||
|
.calendar-day:not(.calendar-day-adjacent-day):focus {
|
||||||
background: current-color(base, 0.2);
|
background: current-color(base, 0.2);
|
||||||
|
|
||||||
box-shadow: 0px 0px 0px 4px current-color(base, 0.2);
|
box-shadow: 0px 0px 0px 4px current-color(base, 0.2);
|
||||||
|
@ -139,6 +139,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
hour: 13,
|
hour: 13,
|
||||||
minute: 52,
|
minute: 52,
|
||||||
ampm: 'pm',
|
ampm: 'pm',
|
||||||
|
isAdjacentDay: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Element() el!: HTMLIonDatetimeElement;
|
@Element() el!: HTMLIonDatetimeElement;
|
||||||
@ -207,6 +208,13 @@ export class Datetime implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() isDateEnabled?: (dateIsoString: string) => boolean;
|
@Prop() isDateEnabled?: (dateIsoString: string) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `true`, the datetime calendar displays a six-week (42-day) layout,
|
||||||
|
* including days from the previous and next months to fill the grid.
|
||||||
|
* These adjacent days are selectable unless disabled.
|
||||||
|
*/
|
||||||
|
@Prop() showAdjacentDays = false;
|
||||||
|
|
||||||
@Watch('disabled')
|
@Watch('disabled')
|
||||||
protected disabledChanged() {
|
protected disabledChanged() {
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
@ -805,12 +813,17 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
private focusWorkingDay = (currentMonth: Element) => {
|
private focusWorkingDay = (currentMonth: Element) => {
|
||||||
/**
|
/**
|
||||||
* Get the number of padding days so
|
* Get the number of offset days so
|
||||||
* we know how much to offset our next selector by
|
* we know how much to offset our next selector by
|
||||||
* to grab the correct calendar-day element.
|
* to grab the correct calendar-day element.
|
||||||
*/
|
*/
|
||||||
const padding = currentMonth.querySelectorAll('.calendar-day-padding');
|
|
||||||
const { day } = this.workingParts;
|
const { day, month, year } = this.workingParts;
|
||||||
|
const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
|
||||||
|
const offset =
|
||||||
|
firstOfMonth >= this.firstDayOfWeek
|
||||||
|
? firstOfMonth - this.firstDayOfWeek
|
||||||
|
: 7 - (this.firstDayOfWeek - firstOfMonth);
|
||||||
|
|
||||||
if (day === null) {
|
if (day === null) {
|
||||||
return;
|
return;
|
||||||
@ -821,7 +834,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
* and focus it.
|
* and focus it.
|
||||||
*/
|
*/
|
||||||
const dayEl = currentMonth.querySelector(
|
const dayEl = currentMonth.querySelector(
|
||||||
`.calendar-day-wrapper:nth-of-type(${padding.length + day}) .calendar-day`
|
`.calendar-day-wrapper:nth-of-type(${offset + day}) .calendar-day`
|
||||||
) as HTMLElement | null;
|
) as HTMLElement | null;
|
||||||
if (dayEl) {
|
if (dayEl) {
|
||||||
dayEl.focus();
|
dayEl.focus();
|
||||||
@ -2226,10 +2239,34 @@ export class Datetime implements ComponentInterface {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="calendar-month-grid">
|
<div class="calendar-month-grid">
|
||||||
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
|
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7, this.showAdjacentDays).map((dateObject, index) => {
|
||||||
const { day, dayOfWeek } = dateObject;
|
const { day, dayOfWeek, isAdjacentDay } = dateObject;
|
||||||
const { el, highlightedDates, isDateEnabled, multiple } = this;
|
const { el, highlightedDates, isDateEnabled, multiple, showAdjacentDays } = this;
|
||||||
const referenceParts = { month, day, year };
|
let _month = month;
|
||||||
|
let _year = year;
|
||||||
|
if (showAdjacentDays && isAdjacentDay && day !== null) {
|
||||||
|
if (day > 20) {
|
||||||
|
// Leading with the adjacent day from the previous month
|
||||||
|
// if its a adjacent day and is higher than '20' (last week even in feb)
|
||||||
|
if (month === 1) {
|
||||||
|
_year = year - 1;
|
||||||
|
_month = 12;
|
||||||
|
} else {
|
||||||
|
_month = month - 1;
|
||||||
|
}
|
||||||
|
} else if (day < 15) {
|
||||||
|
// Leading with the adjacent day from the next month
|
||||||
|
// if its a adjacent day and is lower than '15' (first two weeks)
|
||||||
|
if (month === 12) {
|
||||||
|
_year = year + 1;
|
||||||
|
_month = 1;
|
||||||
|
} else {
|
||||||
|
_month = month + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const referenceParts = { month: _month, day, year: _year, isAdjacentDay };
|
||||||
const isCalendarPadding = day === null;
|
const isCalendarPadding = day === null;
|
||||||
const {
|
const {
|
||||||
isActive,
|
isActive,
|
||||||
@ -2284,7 +2321,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
* Custom highlight styles should not override the style for selected dates,
|
* Custom highlight styles should not override the style for selected dates,
|
||||||
* nor apply to "filler days" at the start of the grid.
|
* nor apply to "filler days" at the start of the grid.
|
||||||
*/
|
*/
|
||||||
if (highlightedDates !== undefined && !isActive && day !== null) {
|
if (highlightedDates !== undefined && !isActive && day !== null && !isAdjacentDay) {
|
||||||
dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
|
dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2292,10 +2329,12 @@ export class Datetime implements ComponentInterface {
|
|||||||
|
|
||||||
// "Filler days" at the beginning of the grid should not get the calendar day
|
// "Filler days" at the beginning of the grid should not get the calendar day
|
||||||
// CSS parts added to them
|
// CSS parts added to them
|
||||||
if (!isCalendarPadding) {
|
if (!isCalendarPadding && !isAdjacentDay) {
|
||||||
dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${
|
dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${
|
||||||
isCalDayDisabled ? ' disabled' : ''
|
isCalDayDisabled ? ' disabled' : ''
|
||||||
}`;
|
}`;
|
||||||
|
} else if (isAdjacentDay) {
|
||||||
|
dateParts = `calendar-day${isCalDayDisabled ? ' disabled' : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -2319,8 +2358,8 @@ export class Datetime implements ComponentInterface {
|
|||||||
}}
|
}}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
data-day={day}
|
data-day={day}
|
||||||
data-month={month}
|
data-month={_month}
|
||||||
data-year={year}
|
data-year={_year}
|
||||||
data-index={index}
|
data-index={index}
|
||||||
data-day-of-week={dayOfWeek}
|
data-day-of-week={dayOfWeek}
|
||||||
disabled={isButtonDisabled}
|
disabled={isButtonDisabled}
|
||||||
@ -2330,6 +2369,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
'calendar-day-active': isActive,
|
'calendar-day-active': isActive,
|
||||||
'calendar-day-constrained': isCalDayConstrained,
|
'calendar-day-constrained': isCalDayConstrained,
|
||||||
'calendar-day-today': isToday,
|
'calendar-day-today': isToday,
|
||||||
|
'calendar-day-adjacent-day': isAdjacentDay,
|
||||||
}}
|
}}
|
||||||
part={dateParts}
|
part={dateParts}
|
||||||
aria-hidden={isCalendarPadding ? 'true' : null}
|
aria-hidden={isCalendarPadding ? 'true' : null}
|
||||||
@ -2340,29 +2380,37 @@ export class Datetime implements ComponentInterface {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdjacentDay) {
|
||||||
|
// The user selected a day outside the current month. Ignore this button, as the month will be re-rendered.
|
||||||
|
this.el.blur();
|
||||||
|
}
|
||||||
|
|
||||||
this.setWorkingParts({
|
this.setWorkingParts({
|
||||||
...this.workingParts,
|
...this.workingParts,
|
||||||
month,
|
month: _month,
|
||||||
day,
|
day,
|
||||||
year,
|
year: _year,
|
||||||
|
isAdjacentDay,
|
||||||
});
|
});
|
||||||
|
|
||||||
// multiple only needs date info, so we can wipe out other fields like time
|
// multiple only needs date info, so we can wipe out other fields like time
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
this.setActiveParts(
|
this.setActiveParts(
|
||||||
{
|
{
|
||||||
month,
|
month: _month,
|
||||||
day,
|
day,
|
||||||
year,
|
year: _year,
|
||||||
|
isAdjacentDay,
|
||||||
},
|
},
|
||||||
isActive
|
isActive
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.setActiveParts({
|
this.setActiveParts({
|
||||||
...activePart,
|
...activePart,
|
||||||
month,
|
month: _month,
|
||||||
day,
|
day,
|
||||||
year,
|
year: _year,
|
||||||
|
isAdjacentDay,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,51 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This behavior does not vary across directions
|
||||||
|
*/
|
||||||
|
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||||
|
test.describe(title('datetime: show adjacent days'), () => {
|
||||||
|
test('should not have visual regressions', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#default');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions with a custom styled calendar', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#custom-calendar-days');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-custom-calendar`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions with specific date disabled', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#specificDate');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-specific-date-disabled`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions with weekends disabled', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#weekends');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-weekends-disabled`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions with date range disabled', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#dateRange');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-date-range-disabled`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions with month disabled', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#month');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-month-disabled`));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have visual regressions with display specified', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/datetime/test/show-adjacent-days', config);
|
||||||
|
const datetime = page.locator('#display');
|
||||||
|
await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-display`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 17 KiB |
310
core/src/components/datetime/test/show-adjacent-days/index.html
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Datetime - Show Adjacent Days</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
<style>
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(250px, 1fr));
|
||||||
|
grid-row-gap: 20px;
|
||||||
|
grid-column-gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
color: #6f7378;
|
||||||
|
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom Datetime Day Parts
|
||||||
|
* -------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#custom-calendar-days::part(calendar-day) {
|
||||||
|
background-color: #ffe2e6;
|
||||||
|
|
||||||
|
color: #da5296;
|
||||||
|
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-calendar-days::part(calendar-day today) {
|
||||||
|
color: #8462d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-calendar-days::part(calendar-day):not(.calendar-day-adjacent-day):focus {
|
||||||
|
background-color: rgb(154 209 98 / 0.2);
|
||||||
|
box-shadow: 0px 0px 0px 4px rgb(154 209 98 / 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-calendar-days::part(calendar-day disabled) {
|
||||||
|
background: rgba(0 0 0 / 0.2);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom Material Design Datetime Day Parts
|
||||||
|
* -------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#custom-calendar-days.md::part(calendar-day active),
|
||||||
|
#custom-calendar-days.md::part(calendar-day active):focus {
|
||||||
|
background-color: #9ad162;
|
||||||
|
border-color: #9ad162;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-calendar-days.md::part(calendar-day today) {
|
||||||
|
border-color: #8462d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom iOS Datetime Day Parts
|
||||||
|
* -------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#custom-calendar-days.ios::part(calendar-day active),
|
||||||
|
#custom-calendar-days.ios::part(calendar-day active):focus {
|
||||||
|
background-color: rgb(154 209 98 / 0.2);
|
||||||
|
color: #9ad162;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-datetime {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Datetime - Show Adjacent Days</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Inline Grid</h2>
|
||||||
|
<ion-datetime show-adjacent-days="true" id="default" value="2020-03-14T14:23:00.000Z"></ion-datetime>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Inline Grid: Custom Styles</h2>
|
||||||
|
<ion-datetime
|
||||||
|
show-adjacent-days="true"
|
||||||
|
id="custom-calendar-days"
|
||||||
|
value="2023-06-15"
|
||||||
|
presentation="date"
|
||||||
|
></ion-datetime>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Disable Specific Date</h2>
|
||||||
|
<ion-datetime
|
||||||
|
show-adjacent-days="true"
|
||||||
|
id="specificDate"
|
||||||
|
value="2021-10-01"
|
||||||
|
showAdjacentDays="true"
|
||||||
|
></ion-datetime>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Disable Weekends</h2>
|
||||||
|
<ion-datetime show-adjacent-days="true" id="weekends" value="2021-10-01"></ion-datetime>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Disable Date Range</h2>
|
||||||
|
<ion-datetime show-adjacent-days="true" id="dateRange" value="2021-10-01"></ion-datetime>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Disable Month</h2>
|
||||||
|
<ion-datetime show-adjacent-days="true" id="month" value="2021-10-01"></ion-datetime>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Change firstDayOfWeek</h2>
|
||||||
|
<ion-datetime
|
||||||
|
show-adjacent-days="true"
|
||||||
|
id="firstDayOfWeek"
|
||||||
|
first-day-of-week="1"
|
||||||
|
value="2022-05-03"
|
||||||
|
></ion-datetime>
|
||||||
|
<button onclick="increase()">Increase firstDayOfWeek</button>
|
||||||
|
<div>
|
||||||
|
<span>FirstDayOfWeek: <span id="start-of-week">1</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="presentation">Presentation</label>
|
||||||
|
<select id="presentation" onchange="changePresentation(event)">
|
||||||
|
<option value="date-time" selected>date-time</option>
|
||||||
|
<option value="time-date">time-date</option>
|
||||||
|
<option value="date">date</option>
|
||||||
|
<option value="time">time</option>
|
||||||
|
</select>
|
||||||
|
<label for="size">Size</label>
|
||||||
|
<select id="size" onchange="changeSize(event)">
|
||||||
|
<option value="fixed" selected>fixed</option>
|
||||||
|
<option value="cover">cover</option>
|
||||||
|
</select>
|
||||||
|
<br /><br />
|
||||||
|
<ion-datetime show-adjacent-days="true" id="display" value="2022-02-22T16:30:00"></ion-datetime>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const customDatetime = document.querySelector('#custom-calendar-days');
|
||||||
|
|
||||||
|
// Mock the current day to always have the same screenshots
|
||||||
|
const mockToday = '2023-06-10T16:22';
|
||||||
|
Date = class extends Date {
|
||||||
|
constructor(...args) {
|
||||||
|
if (args.length === 0) {
|
||||||
|
super(mockToday);
|
||||||
|
} else {
|
||||||
|
super(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
customDatetime.highlightedDates = [
|
||||||
|
{
|
||||||
|
date: '2023-06-02',
|
||||||
|
textColor: 'purple',
|
||||||
|
backgroundColor: 'pink',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-06-04',
|
||||||
|
textColor: 'firebrick',
|
||||||
|
backgroundColor: 'salmon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2023-06-06',
|
||||||
|
textColor: 'blue',
|
||||||
|
backgroundColor: 'lightblue',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
customDatetime.isDateEnabled = (date) => {
|
||||||
|
if (date === '2023-06-22') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const specificDateDisabled = document.querySelector('#specificDate');
|
||||||
|
specificDateDisabled.isDateEnabled = (dateIsoString) => {
|
||||||
|
const date = new Date(dateIsoString);
|
||||||
|
// Disables October 10, 2021.
|
||||||
|
if (date.getUTCDate() === 10 && date.getUTCMonth() === 9 && date.getUTCFullYear() === 2021) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const weekendsDisabled = document.querySelector('#weekends');
|
||||||
|
weekendsDisabled.isDateEnabled = (dateIsoString) => {
|
||||||
|
const date = new Date(dateIsoString);
|
||||||
|
// Disables Sunday and Saturday
|
||||||
|
if (date.getUTCDay() === 0 || date.getUTCDay() === 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateRangeDisabled = document.querySelector('#dateRange');
|
||||||
|
dateRangeDisabled.isDateEnabled = (dateIsoString) => {
|
||||||
|
const date = new Date(dateIsoString);
|
||||||
|
// Disables dates between October 10, 2021 and October 20, 2021.
|
||||||
|
if (date.getUTCMonth() === 9 && date.getUTCFullYear() === 2021) {
|
||||||
|
if (date.getUTCDate() >= 10 && date.getUTCDate() <= 20) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const monthDisabled = document.querySelector('#month');
|
||||||
|
monthDisabled.isDateEnabled = (dateIsoString) => {
|
||||||
|
const date = new Date(dateIsoString);
|
||||||
|
// Disables October (every year)
|
||||||
|
if (date.getUTCMonth() === 9) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstDayOfWeek = document.querySelector('#firstDayOfWeek');
|
||||||
|
function increase() {
|
||||||
|
firstDayOfWeek.firstDayOfWeek = firstDayOfWeek.firstDayOfWeek + 1;
|
||||||
|
|
||||||
|
const span = document.getElementById('start-of-week');
|
||||||
|
span.innerText = firstDayOfWeek.firstDayOfWeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = document.querySelector('#display');
|
||||||
|
|
||||||
|
const mutationObserver = new MutationObserver(() => {
|
||||||
|
window.dispatchEvent(new CustomEvent('ionWorkingPartsDidChange'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const initCalendarMonthChangeObserver = async () => {
|
||||||
|
if (!display.componentOnReady) return;
|
||||||
|
await display.componentOnReady();
|
||||||
|
|
||||||
|
// We have to requestAnimationFrame to allow the display to render completely.
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const calendarBody = display.shadowRoot.querySelector('.calendar-body');
|
||||||
|
if (calendarBody) {
|
||||||
|
mutationObserver.observe(calendarBody, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePresentation = (ev) => {
|
||||||
|
mutationObserver.disconnect();
|
||||||
|
display.presentation = ev.target.value;
|
||||||
|
initCalendarMonthChangeObserver();
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSize = (ev) => {
|
||||||
|
display.size = ev.target.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
initCalendarMonthChangeObserver();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -102,8 +102,16 @@ export const getDaysOfWeek = (locale: string, mode: Mode, firstDayOfWeek = 0) =>
|
|||||||
* the firstDayOfWeek value (Sunday by default)
|
* the firstDayOfWeek value (Sunday by default)
|
||||||
* using null values.
|
* using null values.
|
||||||
*/
|
*/
|
||||||
export const getDaysOfMonth = (month: number, year: number, firstDayOfWeek: number) => {
|
export const getDaysOfMonth = (month: number, year: number, firstDayOfWeek: number, showAdjacentDays = false) => {
|
||||||
const numDays = getNumDaysInMonth(month, year);
|
const numDays = getNumDaysInMonth(month, year);
|
||||||
|
let previousNumDays: number; //previous month number of days
|
||||||
|
if (month === 1) {
|
||||||
|
// If the current month is January, the previous month should be December of the previous year.
|
||||||
|
previousNumDays = getNumDaysInMonth(12, year - 1);
|
||||||
|
} else {
|
||||||
|
// Otherwise, the previous month should be the current month - 1 of the same year.
|
||||||
|
previousNumDays = getNumDaysInMonth(month - 1, year);
|
||||||
|
}
|
||||||
const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
|
const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,13 +136,40 @@ export const getDaysOfMonth = (month: number, year: number, firstDayOfWeek: numb
|
|||||||
const offset =
|
const offset =
|
||||||
firstOfMonth >= firstDayOfWeek ? firstOfMonth - (firstDayOfWeek + 1) : 6 - (firstDayOfWeek - firstOfMonth);
|
firstOfMonth >= firstDayOfWeek ? firstOfMonth - (firstDayOfWeek + 1) : 6 - (firstDayOfWeek - firstOfMonth);
|
||||||
|
|
||||||
let days = [];
|
let days: (
|
||||||
|
| {
|
||||||
|
day: number;
|
||||||
|
dayOfWeek: number;
|
||||||
|
isAdjacentDay: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
day: null;
|
||||||
|
dayOfWeek: null;
|
||||||
|
isAdjacentDay: boolean;
|
||||||
|
}
|
||||||
|
)[] = [];
|
||||||
for (let i = 1; i <= numDays; i++) {
|
for (let i = 1; i <= numDays; i++) {
|
||||||
days.push({ day: i, dayOfWeek: (offset + i) % 7 });
|
days.push({ day: i, dayOfWeek: (offset + i) % 7, isAdjacentDay: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i <= offset; i++) {
|
if (showAdjacentDays) {
|
||||||
days = [{ day: null, dayOfWeek: null }, ...days];
|
for (let i = 0; i <= offset; i++) {
|
||||||
|
// Using offset create previous month adjacent day, starting from last day
|
||||||
|
days = [{ day: previousNumDays - i, dayOfWeek: (previousNumDays - i) % 7, isAdjacentDay: true }, ...days];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate positiveOffset
|
||||||
|
// The calendar will display 42 days (6 rows of 7 columns)
|
||||||
|
// Knowing this the offset is 41 (we start at index 0)
|
||||||
|
// minus (the previous offset + the current month days)
|
||||||
|
const positiveOffset = 41 - (numDays + offset);
|
||||||
|
for (let i = 0; i < positiveOffset; i++) {
|
||||||
|
days.push({ day: i + 1, dayOfWeek: (numDays + offset + i) % 7, isAdjacentDay: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i <= offset; i++) {
|
||||||
|
days = [{ day: null, dayOfWeek: null, isAdjacentDay: false }, ...days];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return days;
|
return days;
|
||||||
|
@ -634,7 +634,7 @@ Set `scrollEvents` to `true` to enable.
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showAdjacentDays', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
||||||
methods: ['confirm', 'reset', 'cancel']
|
methods: ['confirm', 'reset', 'cancel']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
@ -642,7 +642,7 @@ Set `scrollEvents` to `true` to enable.
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||||
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showAdjacentDays', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'],
|
||||||
})
|
})
|
||||||
export class IonDatetime {
|
export class IonDatetime {
|
||||||
protected el: HTMLIonDatetimeElement;
|
protected el: HTMLIonDatetimeElement;
|
||||||
|
@ -40,6 +40,7 @@ const DATETIME_INPUTS = [
|
|||||||
'preferWheel',
|
'preferWheel',
|
||||||
'presentation',
|
'presentation',
|
||||||
'readonly',
|
'readonly',
|
||||||
|
'showAdjacentDays',
|
||||||
'showClearButton',
|
'showClearButton',
|
||||||
'showDefaultButtons',
|
'showDefaultButtons',
|
||||||
'showDefaultTimeLabel',
|
'showDefaultTimeLabel',
|
||||||
|
@ -309,6 +309,7 @@ export const IonDatetime: StencilVueComponent<JSX.IonDatetime, JSX.IonDatetime["
|
|||||||
'formatOptions',
|
'formatOptions',
|
||||||
'readonly',
|
'readonly',
|
||||||
'isDateEnabled',
|
'isDateEnabled',
|
||||||
|
'showAdjacentDays',
|
||||||
'min',
|
'min',
|
||||||
'max',
|
'max',
|
||||||
'presentation',
|
'presentation',
|
||||||
|