Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/red-terms-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solid-design-system/components': patch
---

Improved handling of `min` and `max` attributes in `sd-datepicker` and enhanced internal `parseLocalISO` date utility to accept slash-separated dates (`YYYY/MM/DD`) in addition to hyphen and dot separators.
16 changes: 16 additions & 0 deletions packages/components/src/components/datepicker/datepicker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,22 @@ describe('<sd-datepicker>', () => {
expect(el.checkValidity()).to.be.false;
expect(input.getAttribute('aria-invalid')).to.equal('true');
});

it('should respect min and max when set programmatically as Date objects', async () => {
const el = await fixture<SdDatepicker>(html`<sd-datepicker value="2025-12-10"></sd-datepicker>`);

el.min = new Date(2025, 11, 9); // 2025-12-09
el.max = new Date(2025, 11, 12); // 2025-12-12
await el.updateComplete;

el.show();
await el.updateComplete;

const dayButtons = Array.from(el.shadowRoot!.querySelectorAll('button.day'));
const enabledDays = dayButtons.filter(btn => !btn.classList.contains('disabled'));

expect(enabledDays.length).to.equal(4);
});
});

describe('set initial month displayed with viewMonth attribute', () => {
Expand Down
20 changes: 18 additions & 2 deletions packages/components/src/components/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,8 +560,24 @@ export default class SdDatepicker extends SolidElement implements SolidFormContr
}

private inMinMax(d: Date): boolean {
const min = this.min === null ? null : this.parseISO(String(this.min));
const max = this.max === null ? null : this.parseISO(String(this.max));
const normalizeBound = (value: string | number | Date | undefined | null): Date | null => {
if (value === undefined || value === null) return null;
if (value instanceof Date) {
return DateUtils.startOfDayLocal(value);
}
if (typeof value === 'number') {
return DateUtils.startOfDayLocal(new Date(value));
}
if (typeof value === 'string') {
// Allow both attribute-style strings and programmatic assignment.
const parsed = this.parseISO(value);
return parsed ? DateUtils.startOfDayLocal(parsed) : null;
}
return null;
};

const min = normalizeBound(this.min as string | number | Date | undefined | null);
const max = normalizeBound(this.max as string | number | Date | undefined | null);

if (min && d < min) return false;
if (max && d > max) return false;
Expand Down
31 changes: 12 additions & 19 deletions packages/components/src/utilities/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,19 @@ export const DateUtils = {
parseLocalISO: (iso: string | null): Date | null => {
if (!iso) return null;

let y: number;
let m: number;
let d: number;
const trimmed = iso.trim();
if (!trimmed) return null;

// support ISO
if (/^\d{4}-\d{2}-\d{2}$/.test(iso)) {
const parts = iso.split('-');
y = Number(parts[0]);
m = Number(parts[1]) - 1;
d = Number(parts[2]);
}
// support date with dots "YYYY.MM.DD"
else if (/^\d{4}\.\d{2}\.\d{2}$/.test(iso)) {
const parts = iso.split('.');
y = Number(parts[0]);
m = Number(parts[1]) - 1;
d = Number(parts[2]);
} else {
return null;
}
// Normalize dots and slashes to hyphens so we support
// YYYY-MM-DD, YYYY.MM.DD and YYYY/MM/DD uniformly.
const normalized = trimmed.replace(/[./]/g, '-');

const match = normalized.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) return null;

const y = Number(match[1]);
const m = Number(match[2]) - 1;
const d = Number(match[3]);

const date = new Date(y, m, d);
return Number.isNaN(date.getTime()) ? null : date;
Expand Down
Loading