Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .storybook/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* - Please do NOT modify this file.
*/

const PACKAGE_VERSION = '2.14.6'
const PACKAGE_VERSION = '2.12.7'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
Expand Down
10,161 changes: 3,251 additions & 6,910 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions packages/ng/date2/abstract-date-component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { booleanAttribute, ChangeDetectionStrategy, Component, computed, effect, inject, input, LOCALE_ID, model, output, signal } from '@angular/core';
import { intlInputOptions, isNotNil } from '@lucca-front/ng/core';
import { addMonths, addYears, isAfter, isBefore, isSameMonth, startOfDay, startOfMonth } from 'date-fns';
import { addMonths, addYears, isAfter, isBefore, isSameMonth, startOfDay, startOfMonth, startOfWeek, WeekOptions } from 'date-fns';
import { WEEK_INFO } from './calendar.token';
import { CalendarMode } from './calendar2/calendar-mode';
import { CellStatus } from './calendar2/cell-status';
import { DateRange, DateRangeInput } from './calendar2/date-range';
import { getDateFormat, getLocalizedDateFormat, getSeparator } from './date-format';
import { LU_DATE2_TRANSLATIONS } from './date2.translate';
import { Date2ClearBehavior, DATE_FORMAT, DateFormat } from './date2.type';
import { transformDateInputToDate, transformDateRangeInputToDateRange } from './utils';
import { getJSFirstDayOfWeek, transformDateInputToDate, transformDateRangeInputToDateRange } from './utils';

@Component({
selector: '',
Expand All @@ -16,6 +17,11 @@ import { transformDateInputToDate, transformDateRangeInputToDateRange } from './
})
export abstract class AbstractDateComponent {
protected locale = inject(LOCALE_ID);
#weekInfo = inject(WEEK_INFO);

protected get weekOptions(): WeekOptions {
return { weekStartsOn: getJSFirstDayOfWeek(this.#weekInfo) };
}
// Contains the current date format (like dd/mm/yy etc) based on current locale
protected dateFormat = getDateFormat(this.locale);
protected separator = getSeparator(this.locale);
Expand Down Expand Up @@ -88,6 +94,9 @@ export abstract class AbstractDateComponent {
switch (mode) {
case 'day':
return min.getTime() <= date.getTime();
case 'week':
// In week mode the emitted value is the start of the week, so min applies to it
return startOfWeek(date, this.weekOptions).getTime() >= min.getTime();
case 'month':
return isBefore(startOfMonth(min), startOfMonth(date)) || isSameMonth(min, date);
case 'year':
Expand All @@ -103,6 +112,9 @@ export abstract class AbstractDateComponent {
switch (mode) {
case 'day':
return max.getTime() >= date.getTime();
case 'week':
// In week mode the emitted value is the start of the week, so max applies to it
return startOfWeek(date, this.weekOptions).getTime() <= max.getTime();
case 'month':
return isAfter(startOfMonth(max), startOfMonth(date)) || isSameMonth(max, date);
case 'year':
Expand Down Expand Up @@ -144,6 +156,7 @@ export abstract class AbstractDateComponent {
this.currentDate.set(addYears(this.currentDate(), direction));
this.tabbableDate.set(addYears(this.tabbableDate() ?? 0, direction));
break;
case 'week':
case 'day':
this.currentDate.set(addMonths(this.currentDate(), direction));
this.tabbableDate.set(addMonths(this.tabbableDate() ?? 0, direction));
Expand Down
1 change: 1 addition & 0 deletions packages/ng/date2/calendar2/calendar-cell-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface RangeInfo {

export interface CalendarCellInfo {
day: number;
week: number;
date: Date;
status: CellStatus;
disabled: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/ng/date2/calendar2/calendar-mode.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const CALENDAR_MODE = ['day', 'month', 'year'] as const;
export const CALENDAR_MODE = ['day', 'week', 'month', 'year'] as const;
export type CalendarMode = (typeof CALENDAR_MODE)[number];
18 changes: 13 additions & 5 deletions packages/ng/date2/calendar2/calendar2-cell.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { add, addMonths, addYears, endOfWeek, startOfWeek, sub, subMonths, subYe
import { WEEK_INFO } from '../calendar.token';
import { comparePeriods, getJSFirstDayOfWeek } from '../utils';
import { CalendarMode } from './calendar-mode';
import { CALENDAR_TABBABLE_DATE } from './calendar2.tokens';
import { CALENDAR_DISPLAY_MODE, CALENDAR_TABBABLE_DATE } from './calendar2.tokens';

const modeToDurationKey: Record<CalendarMode, keyof Duration> = {
day: 'days',
week: 'weeks',
month: 'months',
year: 'years',
};
Expand All @@ -23,6 +24,7 @@ export class Calendar2CellDirective {
#host = inject<ElementRef<HTMLButtonElement>>(ElementRef);

readonly #tabbableDate = inject(CALENDAR_TABBABLE_DATE);
readonly #displayMode = inject(CALENDAR_DISPLAY_MODE);
#weekInfo = inject(WEEK_INFO);

// Index of this day in the current week display row, not depending on locale, 0 is first day of week and 6 is last
Expand All @@ -37,13 +39,19 @@ export class Calendar2CellDirective {
}

readonly isTabbableDate = computed(() => {
return comparePeriods(this.luCalendar2Mode(), this.luCalendar2Date(), this.#tabbableDate());
// In week display mode, day cells share the grid with the week rowheaders: only the cell
// whose own mode matches the calendar's display mode can be tabbable, so a tabbable date
// never makes both a week rowheader and a day cell tabbable at the same time.
if (this.luCalendar2Mode() !== this.#displayMode()) {
return false;
}
return comparePeriods(this.luCalendar2Mode(), this.luCalendar2Date(), this.#tabbableDate(), { weekStartsOn: getJSFirstDayOfWeek(this.#weekInfo) });
});

keydown($event: Event): void {
// See https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/#ex_label for keyboard
// navigation standards on date pickers
const cellsPerRow = this.luCalendar2Mode() === 'day' ? 7 : 3;
const cellsPerRow = this.luCalendar2Mode() === 'day' ? 7 : this.luCalendar2Mode() === 'week' ? 1 : 3;
const datePropertyToEdit = modeToDurationKey[this.luCalendar2Mode()];
switch (($event as KeyboardEvent).key) {
case 'ArrowRight':
Expand Down Expand Up @@ -75,7 +83,7 @@ export class Calendar2CellDirective {
}
break;
case 'PageUp':
if (this.luCalendar2Mode() === 'day') {
if (this.luCalendar2Mode() === 'day' || this.luCalendar2Mode() === 'week') {
if (($event as KeyboardEvent).shiftKey) {
this.#tabbableDate.set(subYears(this.luCalendar2Date(), 1));
} else {
Expand All @@ -85,7 +93,7 @@ export class Calendar2CellDirective {
}
break;
case 'PageDown':
if (this.luCalendar2Mode() === 'day') {
if (this.luCalendar2Mode() === 'day' || this.luCalendar2Mode() === 'week') {
if (($event as KeyboardEvent).shiftKey) {
this.#tabbableDate.set(addYears(this.luCalendar2Date(), 1));
} else {
Expand Down
118 changes: 118 additions & 0 deletions packages/ng/date2/calendar2/calendar2.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,124 @@
</symbol>
</svg>
}
@case ("week") {
<div class="calendar-name" id="calendar-week-name" aria-live="polite">
@if (disableModeChange()) {
{{ currentMonthLabel() }}&ngsp; {{ currentYearLabel() }}
} @else {
<button type="button" class="calendar-name-button" (click)="displayMode.set('month')">{{ currentMonthLabel() }}</button>&ngsp;
<button type="button" class="calendar-name-button" (click)="displayMode.set('year')">{{ currentYearLabel() }}</button>
}
<span class="pr-u-mask">{{ intl().directionKeysTip }} </span>
</div>
<table class="calendar-table" role="grid" aria-labelledby="calendar-week-name">
<thead class="calendar-table-head">
<tr class="calendar-table-head-row">
<th scope="col" class="calendar-table-head-row-cell">
<span class="pr-u-mask">{{ intl().weekNumber }}</span>
</th>
@for (day of daysOfWeek; track day.long) {
<th class="calendar-table-head-row-cell" scope="col">
<span [attr.data-content-after]="day.short.substring(0, 1)" aria-hidden="true"></span>
<span class="pr-u-mask">{{ day.long }}</span>
</th>
}
</tr>
</thead>
<tbody class="calendar-table-body">
@for (week of monthGridDisplay(); track $index; let isLastWeek = $last) {
@let weekHeader = getWeekHeaderCell(week);
<tr class="calendar-table-body-row">
<th
scope="row"
class="calendar-table-body-row-cell"
[class]="getWeekRowClasses(week)"
[attr.aria-selected]="weekHeader.isSelected || null"
>
<button
type="button"
class="calendar-table-body-row-cell-action"
[luCalendar2Cell]="0"
[luCalendar2Date]="weekHeader.date"
luCalendar2Mode="week"
[attr.aria-disabled]="weekHeader.disabled ? 'true' : null"
(click)="!weekHeader.disabled ? onCellClicked(weekHeader.date) : null"
(keydown.space)="!weekHeader.disabled ? onCellClicked(weekHeader.date) : null"
(focus)="dateHovered.set(weekHeader.date)"
(blur)="dateHovered.set(null)"
>
<span class="calendar-table-body-row-cell-action-week">{{ weekHeader.week }}</span>
</button>
</th>
@for (day of week; track dayIndex; let dayIndex = $index; let isLastDay = $last) {
<td
class="calendar-table-body-row-cell"
(mouseenter)="day.isOverflow ? null : dateHovered.set(day.date)"
(mouseleave)="day.isOverflow ? null : dateHovered.set(null)"
[class]="getWeekDayCellClasses(day, isLastDay)"
>
@if (!day.noButton) {
<button
[luCalendar2Cell]="dayIndex"
[luCalendar2Date]="day.date"
luCalendar2Mode="day"
class="calendar-table-body-row-cell-action"
type="button"
[attr.aria-disabled]="day.disabled ? 'true' : null"
(click)="day.disabled ? null : onCellClicked(day.date)"
(keydown.space)="day.disabled ? null : onCellClicked(day.date)"
(focus)="day.isOverflow ? null : dateHovered.set(day.date)"
(blur)="day.isOverflow ? null : dateHovered.set(null)"
[luTooltip]="day.label"
tabindex="-1"
>
{{ day.day }}
@if (day.isCurrent) {
<span class="pr-u-mask">({{ todayLabel }})</span>
}
@if (day.isWeekend) {
<svg class="calendar-table-body-row-cell-action-stripes">
<use href="#calendar-stripes-symbol" />
</svg>
}
</button>
}
</td>
}
<td class="calendar-table-body-row-cell is-overflow" *luRepeatTimes="isLastWeek ? 7 - week.length : 0"></td>
</tr>
}
<tr class="calendar-table-body-row" *luRepeatTimes="6 - monthGridDisplay().length">
<td class="calendar-table-body-row-cell is-overflow" *luRepeatTimes="7"></td>
</tr>
</tbody>
</table>
@if (hasTodayButton()) {
<div class="calendar-divider divider"></div>
<button class="calendar-todayLink link" type="button" (click)="clickToday()">{{ todayLabel }}</button>
}
<svg class="calendar-stripes" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" aria-hidden="true">
<symbol id="calendar-stripes-symbol" viewBox="0 0 60 60">
<path
class="calendar-stripes-symbol-path"
d="m-41.82 43.03 84.85-84.85.53.53-84.85 84.85zm2.12 2.12L45.15-39.7l.53.53-84.85 84.85zm2.12 2.12
84.85-84.85.53.53-84.85 84.85zm2.12 2.13L49.4-35.47l.53.53-84.85 84.85zm2.12 2.11
84.85-84.85.54.53-84.86 84.86zm2.12 2.13 84.86-84.86.53.53-84.86 84.86zm2.12
2.12L55.76-29.1l.53.53-84.86 84.86zm2.13 2.12 84.85-84.85.53.53-84.85 84.85zm2.12
2.12L60-24.85l.53.53-84.85 84.85zm2.12 2.12 84.85-84.85.53.53-84.85 84.85zm2.12
2.12L64.24-20.6l.53.53-84.85 84.85zm2.12 2.12 84.85-84.85.53.53-84.85 84.85zm2.12
2.13L68.5-16.37l.53.53-84.86 84.86zm2.12 2.11 84.86-84.85.53.53-84.86 84.86zm2.13 2.13
84.85-84.85.53.53-84.85 84.85zm2.12 2.12L74.85-10l.53.53-84.85 84.85zm2.12
2.12L76.97-7.88l.53.53L-7.35 77.5zm2.12 2.13L79.09-5.77l.53.53-84.85 84.85zm2.12
2.11L81.21-3.64l.53.53L-3.1 81.74zm2.12 2.12L83.33-1.52l.53.53L-.99
83.86zM.6 85.46 85.46.6l.53.53L1.13 86zm2.12 2.12L87.58 2.72l.53.53L3.25 88.11zm2.13 2.12L89.7
4.85l.53.53L5.38 90.23zm2.12 2.12L91.82 6.97l.53.53L7.5 92.35zm2.12 2.12L93.94 9.1l.53.53L9.62
94.47zm2.12 2.12 84.85-84.85.53.53-84.85 84.85zm2.12 2.12 84.85-84.85.53.53-84.85 84.85zm2.12
2.12 84.86-84.85.53.53-84.86 84.86z"
/>
</symbol>
</svg>
}
@case ("month") {
<div class="calendar-name" id="calendar-year-name">
@if (disableModeChange()) {
Expand Down
Loading
Loading