Skip to content
Open
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
16 changes: 16 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,22 @@ <h2 id="options">Options</h2>
months.
</td>
</tr>
<tr>
<td><code>show-day-descriptions</code></td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>
Controls whether the day descriptions are shown in the calendar.
</td>
</tr>
<tr>
<td><code>addDateDescription</code></td>
<td><code>(date: Date) => string</code></td>
<td><code>Desc.</code></td>
<td>
A string to be used as the date description text.
</td>
</tr>
</tbody>
</table>

Expand Down
6 changes: 6 additions & 0 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { MonthChangedEventDetails, WCDatepickerLabels } from "./components/wc-datepicker/wc-datepicker";
export namespace Components {
interface WcDatepicker {
"addDateDescription"?: (date: Date) => string;
"clearButtonContent"?: string;
"disableDate"?: (date: Date) => boolean;
"disabled"?: boolean;
"elementClassName"?: string;
"firstDayOfWeek"?: number;
"getAllDayDescriptions": () => Promise<Record<string, string>>;
"getDescriptionForDay": (date: Date) => Promise<string>;
"goToRangeStartOnSelect"?: boolean;
"labels"?: WCDatepickerLabels;
"locale"?: string;
Expand All @@ -23,6 +26,7 @@ export namespace Components {
"previousYearButtonContent"?: string;
"range"?: boolean;
"showClearButton"?: boolean;
"showDayDescriptions"?: boolean;
"showMonthStepper"?: boolean;
"showTodayButton"?: boolean;
"showYearStepper"?: boolean;
Expand All @@ -48,6 +52,7 @@ declare global {
}
declare namespace LocalJSX {
interface WcDatepicker {
"addDateDescription"?: (date: Date) => string;
"clearButtonContent"?: string;
"disableDate"?: (date: Date) => boolean;
"disabled"?: boolean;
Expand All @@ -65,6 +70,7 @@ declare namespace LocalJSX {
"previousYearButtonContent"?: string;
"range"?: boolean;
"showClearButton"?: boolean;
"showDayDescriptions"?: boolean;
"showMonthStepper"?: boolean;
"showTodayButton"?: boolean;
"showYearStepper"?: boolean;
Expand Down
47 changes: 38 additions & 9 deletions src/components/wc-datepicker/wc-datepicker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,71 +183,71 @@ describe('wc-datepicker', () => {
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('2');

triggerKeyDown(page, 'ArrowRight');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('3');

triggerKeyDown(page, 'ArrowDown');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('10');

triggerKeyDown(page, 'ArrowLeft');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('9');

triggerKeyDown(page, 'ArrowUp');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('2');

triggerKeyDown(page, 'End');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('31');

triggerKeyDown(page, 'Home');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('1');

triggerKeyDown(page, 'PageDown');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('1');

triggerKeyDown(page, 'PageUp');
await page.waitForChanges();

expect(
page.root.querySelector('.wc-datepicker__date--current').children[0]
page.root.querySelector('.wc-datepicker__date--current .wc-datepicker__date-content').children[0]
.innerHTML
).toBe('1');
});
Expand Down Expand Up @@ -567,4 +567,33 @@ describe('wc-datepicker', () => {

expect(component['hoveredDate']).toBeUndefined();
});

it('add date descriptions to date cells', async () => {
const page = await newSpecPage({
components: [WCDatepicker],
html: `<wc-datepicker showDayDescriptions></wc-datepicker>`,
language: 'en'
});

// Since months are zero based
const november = 11 - 1;
const dayThird = 3;
const descriptionText = 'Desc.';
const currentDate = new Date('2025-11-03');
const component = page.rootInstance as WCDatepicker;
page.root.startDate = currentDate;
component.showDayDescriptions = true;

component.addDateDescription = (date: Date) => {
if (date.getMonth() === november && date.getDate() === dayThird) return descriptionText;
return ``;
};

await page.waitForChanges();

const getDayWithDescription = await component.getDescriptionForDay(currentDate);

expect(component.showDayDescriptions).toBe(true);
expect(getDayWithDescription).toBe(descriptionText);
});
});
45 changes: 42 additions & 3 deletions src/components/wc-datepicker/wc-datepicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EventEmitter,
h,
Host,
Method,
Prop,
State,
Watch
Expand Down Expand Up @@ -92,14 +93,19 @@ export class WCDatepicker {
@Prop() maxSearchDays?: number = 365;
@Prop() goToRangeStartOnSelect?: boolean = true;

@Prop({ mutable: true }) showDayDescriptions?: boolean = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Do we need this prop? This could be implicit information by looking at addDateDescription,. right? If addDateDescription is not set or returns undefined, the description should not be shown.

@Prop({ mutable: true }) addDateDescription?: (date: Date) => string;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I don't think we need the { mutable: true } here. The prop value is not mutated within the component.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Allow it to return undefined to explicitly skip the descriptions for a date.


@State() currentDate: Date;
@State() hoveredDate: Date;
@State() weekdays: string[][];


@Event() selectDate: EventEmitter<string | string[] | undefined>;
@Event() changeMonth: EventEmitter<MonthChangedEventDetails>;

private moveFocusAfterMonthChanged: Boolean;
private dayDescriptions: Record<string, string> = {};

componentWillLoad() {
this.init();
Expand Down Expand Up @@ -154,6 +160,16 @@ export class WCDatepicker {
}
}

@Method()
async getDescriptionForDay(date: Date): Promise<string> {
return this.dayDescriptions[getISODateString(date)] || '';
}

@Method()
async getAllDayDescriptions(): Promise<Record<string, string>> {
return { ...this.dayDescriptions };
}

Comment on lines +163 to +172
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is there a need to make these methods part of the public component API with the @Method decorator. I don't see an immediate use case why you need to retrieve the descriptions this way.

private init = () => {
this.currentDate = this.startDate
? removeTimezoneOffset(new Date(this.startDate))
Expand Down Expand Up @@ -224,6 +240,18 @@ export class WCDatepicker {
}
}

private setDescriptionForDay(date: Date): string {
Copy link
Owner

@Sqrrl Sqrrl Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Do you expect the addDateDescription functions to be expensive to run? Otherwise I would suggest to not cache the values at all and just call the function during render. This would simplify your changes a lot. (No need for the dayDescriptions record, or the new methods.)

if (!this.showDayDescriptions || !this.addDateDescription) {
return '';
}

if (!this.dayDescriptions[getISODateString(date)]) {
this.dayDescriptions[getISODateString(date)] = this.addDateDescription(date) || '';
}

return this.dayDescriptions[getISODateString(date)] || '';
}

private focusDate(date: Date) {
this.el
.querySelector<HTMLTableCellElement>(
Expand Down Expand Up @@ -573,7 +601,8 @@ export class WCDatepicker {
aria-label={this.labels.picker}
class={{
[this.getClassName()]: true,
[`${this.getClassName()}--disabled`]: this.disabled
[`${this.getClassName()}--disabled`]: this.disabled,
[`${this.getClassName()}--with-descriptions`]: this.showDayDescriptions
}}
role="group"
>
Expand Down Expand Up @@ -777,6 +806,8 @@ export class WCDatepicker {
const isDisabled = this.disableDate(day);

const cellKey = `cell-${day.getMonth()}-${day.getDate()}`;

const dayDescription = this.setDescriptionForDay(day);

const className = {
[this.getClassName('date')]: true,
Expand Down Expand Up @@ -817,12 +848,20 @@ export class WCDatepicker {
: -1
}
>
<Tag aria-hidden="true">{day.getDate()}</Tag>
<div class={this.getClassName('date-content')}>
<Tag aria-hidden="true">{day.getDate()}</Tag>
{this.showDayDescriptions && dayDescription && (
<span class={this.getClassName('date-description')} aria-hidden="true">
Comment on lines +851 to +854
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The new classnames here should be documented in the index.html.

{dayDescription}
</span>
)}
</div>
<span class="visually-hidden">
{Intl.DateTimeFormat(this.locale, {
day: 'numeric',
month: 'long'
}).format(day)}
{this.showDayDescriptions && dayDescription && `, ${dayDescription}`}
</span>
</td>
);
Expand Down Expand Up @@ -864,4 +903,4 @@ export class WCDatepicker {
</Host>
);
}
}
}
10 changes: 10 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@

<script>
const datepicker = document.getElementById('datepicker');
datepicker.showDayDescriptions = true;

datepicker.addDateDescription = (date) => {
const day = date.getDate();
const month = date.getMonth();

if (month === 3 && day === 11) return '845';
if (month === 5 && day === 27) return '900';
return '';
};

datepicker.addEventListener('selectDate', (event) => {
console.log(event);
Expand Down
18 changes: 18 additions & 0 deletions src/themes/dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,21 @@ wc-datepicker {
line-height: 1;
cursor: pointer;
}

.wc-datepicker--with-descriptions .wc-datepicker__date-content {
position: relative;
text-overflow: ellipsis;
overflow: hidden;
height: 42px;
white-space: nowrap;
width: 35px;
}

.wc-datepicker--with-descriptions .wc-datepicker__date-description {
font-size: 8px;
position: absolute;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
top: 30px;
}
18 changes: 18 additions & 0 deletions src/themes/light.css
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,21 @@ wc-datepicker {
line-height: 1;
cursor: pointer;
}

.wc-datepicker--with-descriptions .wc-datepicker__date-content {
position: relative;
text-overflow: ellipsis;
overflow: hidden;
height: 42px;
white-space: nowrap;
width: 35px;
}

.wc-datepicker--with-descriptions .wc-datepicker__date-description {
font-size: 8px;
position: absolute;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
top: 30px;
}