@pine-ds/core 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/components/pds-datepicker.js +1 -1
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/pds-datepicker.cjs.entry.js +253 -75
  4. package/dist/cjs/pine-core.cjs.js +1 -1
  5. package/dist/collection/components/pds-datepicker/date-utils.js +42 -0
  6. package/dist/collection/components/pds-datepicker/pds-datepicker.js +163 -64
  7. package/dist/collection/components/pds-datepicker/stories/pds-datepicker.stories.js +11 -0
  8. package/dist/collection/components/pds-datepicker/value.js +76 -18
  9. package/dist/docs.json +35 -6
  10. package/dist/esm/loader.js +1 -1
  11. package/dist/esm/pds-datepicker.entry.js +253 -75
  12. package/dist/esm/pine-core.js +1 -1
  13. package/dist/esm-es5/loader.js +1 -1
  14. package/dist/esm-es5/pds-datepicker.entry.js +1 -1
  15. package/dist/esm-es5/pine-core.js +1 -1
  16. package/dist/pine-core/p-3359a935.entry.js +1 -0
  17. package/dist/pine-core/p-XK45dzWR.system.js +1 -1
  18. package/dist/pine-core/p-d4968f9d.system.entry.js +1 -0
  19. package/dist/pine-core/pine-core.esm.js +1 -1
  20. package/dist/types/components/pds-datepicker/date-utils.d.ts +19 -0
  21. package/dist/types/components/pds-datepicker/datepicker-interface.d.ts +2 -1
  22. package/dist/types/components/pds-datepicker/pds-datepicker.d.ts +30 -5
  23. package/dist/types/components/pds-datepicker/value.d.ts +14 -2
  24. package/dist/types/components.d.ts +25 -10
  25. package/dist/vscode.html-data.json +11 -4
  26. package/hydrate/index.js +256 -76
  27. package/hydrate/index.mjs +256 -76
  28. package/package.json +2 -2
  29. package/dist/pine-core/p-8436cbfb.entry.js +0 -1
  30. package/dist/pine-core/p-d47b2a6a.system.entry.js +0 -1
@@ -141,6 +141,48 @@ function dateFormatter(locale, options) {
141
141
  function formatDate(date, locale, options) {
142
142
  return dateFormatter(locale, options).format(date);
143
143
  }
144
+ /**
145
+ * Month-day mode's year. A leap year, so February has its 29th; every date the mode holds is in it,
146
+ * and the value drops it (`--MM-DD`).
147
+ */
148
+ const MONTH_DAY_YEAR = 2000;
149
+ const MONTH_DAY = /^--(\d{2})-(\d{2})$/;
150
+ /** The same month and day in month-day mode's year. */
151
+ function inMonthDayYear(date) {
152
+ return new Date(Date.UTC(MONTH_DAY_YEAR, date.getUTCMonth(), date.getUTCDate()));
153
+ }
154
+ /**
155
+ * Parses a month and day, `--MM-DD` (ISO 8601 without a year), to a date in month-day mode's year.
156
+ * A full `YYYY-MM-DD` reads as its month and day. `null` when it isn't a real month and day.
157
+ */
158
+ function parseMonthDay(value) {
159
+ const text = (value !== null && value !== void 0 ? value : '').trim();
160
+ const match = MONTH_DAY.exec(text);
161
+ if (match)
162
+ return parseIsoDate(`${MONTH_DAY_YEAR}-${match[1]}-${match[2]}`);
163
+ const date = parseIsoDate(text);
164
+ return date && inMonthDayYear(date);
165
+ }
166
+ /** A date's month and day as `--MM-DD`. */
167
+ function toMonthDay(date) {
168
+ return `-${toIsoDate(date).slice(4)}`;
169
+ }
170
+ /**
171
+ * A month's days for month-day mode, which has no weekdays: rows of seven from the 1st, with the
172
+ * last row filled out with `null`.
173
+ */
174
+ function monthDayRows(month) {
175
+ const cells = [];
176
+ for (let day = 1; day <= daysInMonth(MONTH_DAY_YEAR, month); day++) {
177
+ cells.push(new Date(Date.UTC(MONTH_DAY_YEAR, month, day)));
178
+ }
179
+ while (cells.length % 7 !== 0)
180
+ cells.push(null);
181
+ const rows = [];
182
+ for (let i = 0; i < cells.length; i += 7)
183
+ rows.push(cells.slice(i, i + 7));
184
+ return rows;
185
+ }
144
186
 
145
187
  /**
146
188
  * Where the open calendar lives: a `div` in `document.body`, or in the nearest `<dialog>` so it
@@ -384,32 +426,50 @@ const wordsCache = new Map();
384
426
  function localeWords(locale) {
385
427
  let words = wordsCache.get(locale);
386
428
  if (!words) {
387
- const month = (style) => {
388
- const format = new Intl.DateTimeFormat(locale, {
389
- month: style,
390
- timeZone: 'UTC',
391
- calendar: 'gregory',
429
+ const month = (style, withDay) => {
430
+ const format = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({ month: style }, (withDay ? { day: 'numeric' } : {})), { timeZone: 'UTC', calendar: 'gregory' }));
431
+ return Array.from({ length: 12 }, (_, m) => {
432
+ const date = new Date(Date.UTC(2023, m, 1));
433
+ if (!withDay)
434
+ return format.format(date);
435
+ const parts = format.formatToParts(date);
436
+ const index = parts.findIndex((part) => part.type === 'month');
437
+ if (index === -1)
438
+ return format.format(date);
439
+ // CJK locales split a counter word, as Japanese `月`, into its own literal right after the
440
+ // month; a literal with no space is that word, not the separator before the day.
441
+ const suffix = parts[index + 1];
442
+ return (suffix === null || suffix === void 0 ? void 0 : suffix.type) === 'literal' && !/\s/.test(suffix.value) ? parts[index].value + suffix.value : parts[index].value;
392
443
  });
393
- return Array.from({ length: 12 }, (_, m) => format.format(new Date(Date.UTC(2023, m, 1))));
394
444
  };
395
445
  const clock = clockFor(locale);
396
- words = { months: month('long'), shortMonths: month('short'), am: clock.am, pm: clock.pm };
446
+ words = {
447
+ months: month('long', false),
448
+ shortMonths: month('short', false),
449
+ monthsWithDay: month('long', true),
450
+ shortMonthsWithDay: month('short', true),
451
+ am: clock.am,
452
+ pm: clock.pm,
453
+ };
397
454
  wordsCache.set(locale, words);
398
455
  }
399
456
  return words;
400
457
  }
458
+ /** Whether a format's tokens (see `tokensOnly`) have a day, so a month name should inflect for one. */
459
+ const hasDayToken = (tokens) => /D/.test(tokens);
401
460
  /** Writes a date in a format of tokens (see `FORMAT_PARTS`), with month names and AM/PM in the locale. */
402
461
  function formatWith(value, format, locale = 'en-US') {
403
462
  var _a;
404
463
  const words = localeWords(locale);
464
+ const withDay = hasDayToken(tokensOnly(format));
405
465
  const [hours, minutes] = splitTime((_a = value.time) !== null && _a !== void 0 ? _a : '00:00');
406
466
  const hour12 = hours % 12 || 12;
407
467
  const month = value.date.getUTCMonth();
408
468
  const period = hours < 12 ? words.am : words.pm;
409
469
  const parts = {
410
470
  YYYY: pad(value.date.getUTCFullYear(), 4),
411
- MMMM: words.months[month],
412
- MMM: words.shortMonths[month],
471
+ MMMM: withDay ? words.monthsWithDay[month] : words.months[month],
472
+ MMM: withDay ? words.shortMonthsWithDay[month] : words.shortMonths[month],
413
473
  MM: pad(month + 1),
414
474
  M: String(month + 1),
415
475
  DD: pad(value.date.getUTCDate()),
@@ -431,17 +491,23 @@ const alternatives = (words) => `(${words.map(escapeRegExp).join('|')})`;
431
491
  /**
432
492
  * Reads typed text in a format of tokens, as written by `formatWith`. Leading zeros are optional,
433
493
  * spaces are loose, and month names and AM/PM are matched without regard to case, in the locale or
434
- * in English. Returns `null` for anything that isn't a real date and time in that format.
494
+ * in English. A format without `YYYY` reads into `year`, and without `year` it reads nothing.
495
+ * Returns `null` for anything that isn't a real date and time in that format.
435
496
  */
436
- function parseWith(text, format, locale = 'en-US') {
497
+ function parseWith(text, format, locale = 'en-US', year) {
437
498
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
438
499
  const words = localeWords(locale);
439
500
  const english = localeWords('en-US');
501
+ const withDay = hasDayToken(tokensOnly(format));
502
+ const months = withDay ? words.monthsWithDay : words.months;
503
+ const shortMonths = withDay ? words.shortMonthsWithDay : words.shortMonths;
504
+ const englishMonths = withDay ? english.monthsWithDay : english.months;
505
+ const englishShortMonths = withDay ? english.shortMonthsWithDay : english.shortMonths;
440
506
  const periods = [words.am, words.pm, english.am, english.pm];
441
507
  const patterns = {
442
508
  YYYY: '(\\d{4})',
443
- MMMM: alternatives([...words.months, ...english.months]),
444
- MMM: alternatives([...words.shortMonths, ...english.shortMonths]),
509
+ MMMM: alternatives([...months, ...englishMonths]),
510
+ MMM: alternatives([...shortMonths, ...englishShortMonths]),
445
511
  MM: '(\\d{1,2})',
446
512
  M: '(\\d{1,2})',
447
513
  DD: '(\\d{1,2})',
@@ -488,7 +554,7 @@ function parseWith(text, format, locale = 'en-US') {
488
554
  const monthIndex = (name, long) => {
489
555
  if (name === undefined)
490
556
  return undefined;
491
- const lists = long ? [words.months, english.months] : [words.shortMonths, english.shortMonths];
557
+ const lists = long ? [months, englishMonths] : [shortMonths, englishShortMonths];
492
558
  for (const list of lists) {
493
559
  const index = list.findIndex((candidate) => candidate.toLowerCase() === name.toLowerCase());
494
560
  if (index >= 0)
@@ -496,12 +562,12 @@ function parseWith(text, format, locale = 'en-US') {
496
562
  }
497
563
  return undefined;
498
564
  };
499
- const year = Number(read.YYYY);
565
+ const readYear = read.YYYY ? Number(read.YYYY) : year;
500
566
  const month = (_b = (_a = monthIndex(read.MMMM, true)) !== null && _a !== void 0 ? _a : monthIndex(read.MMM, false)) !== null && _b !== void 0 ? _b : Number((_c = read.MM) !== null && _c !== void 0 ? _c : read.M);
501
567
  const day = Number((_d = read.DD) !== null && _d !== void 0 ? _d : read.D);
502
- if (!read.YYYY || !month || !day)
568
+ if (readYear === undefined || !month || !day)
503
569
  return null;
504
- const date = parseIsoDate(`${pad(year, 4)}-${pad(month)}-${pad(day)}`);
570
+ const date = parseIsoDate(`${pad(readYear, 4)}-${pad(month)}-${pad(day)}`);
505
571
  if (!date)
506
572
  return null;
507
573
  const hourText = (_g = (_f = (_e = read.HH) !== null && _e !== void 0 ? _e : read.H) !== null && _f !== void 0 ? _f : read.hh) !== null && _g !== void 0 ? _g : read.h;
@@ -523,6 +589,40 @@ function parseFieldText(text, format = DEFAULT_FORMAT, locale = 'en-US') {
523
589
  var _a;
524
590
  return (_a = parseWith(text, format, locale)) !== null && _a !== void 0 ? _a : parseDateTime(text);
525
591
  }
592
+ /**
593
+ * The locale's way of writing a month and day, as a format of tokens: `MMMM D` in English, `D MMMM`
594
+ * in French, `M月D日` in Japanese, or with `numeric`, `M/D` and `D/M`.
595
+ */
596
+ function monthDayFormat(locale = 'en-US', month = 'long') {
597
+ const parts = dateFormatter(locale, { month, day: 'numeric' }).formatToParts(new Date(Date.UTC(MONTH_DAY_YEAR, 11, 31)));
598
+ return parts
599
+ .map((part) => {
600
+ // Some locales write even the long month in digits, as Japanese `12月31日`.
601
+ if (part.type === 'month')
602
+ return /^\d+$/.test(part.value) ? 'M' : 'MMMM';
603
+ if (part.type === 'day')
604
+ return 'D';
605
+ // Bracketed, spaces included, only when it has letters a token could be read from, as
606
+ // Spanish `D[ de ]MMMM`.
607
+ return /[A-Za-z]/.test(part.value) ? `[${part.value}]` : part.value;
608
+ })
609
+ .join('');
610
+ }
611
+ /**
612
+ * Reads a typed month and day, into month-day mode's year: in the format, then as the locale writes
613
+ * it with a month name or in numbers, with the English order too, then as `--MM-DD` or a full ISO
614
+ * date, whose year is dropped.
615
+ */
616
+ function parseMonthDayText(text, format, locale = 'en-US') {
617
+ const formats = [format, monthDayFormat(locale), monthDayFormat(locale, 'numeric'), 'MMMM D', 'D MMMM', 'MMM D', 'D MMM'];
618
+ for (const candidate of formats) {
619
+ const read = parseWith(text, candidate, locale, MONTH_DAY_YEAR);
620
+ if (read)
621
+ return { date: inMonthDayYear(read.date), time: null };
622
+ }
623
+ const date = parseMonthDay(text);
624
+ return date ? { date, time: null } : null;
625
+ }
526
626
  /**
527
627
  * Parses a typed range: two dates (with times, optionally) joined by a dash or `to` with spaces
528
628
  * around it, as `2026-09-13 – 2026-10-13`, or by `/` when the format doesn't use slashes. The
@@ -577,6 +677,8 @@ function clockFor(locale) {
577
677
  const pdsDatepickerCss = () => `:host{--pds-datepicker-day-size:var(--pine-dimension-500);--pds-datepicker-presets-width:200px;display:inline-block}:host([full-width]){display:block}pds-input{display:block}:host(.pds-datepicker--time) pds-input{min-width:220px}:host(.pds-datepicker--range) pds-input{min-width:260px}:host(.pds-datepicker--range.pds-datepicker--time) pds-input{min-width:min(380px, 100%)}.pds-datepicker__trigger,.pds-datepicker__clear{-ms-flex-align:center;align-items:center;background:transparent;border:0;border-radius:var(--pine-border-radius-sm);color:var(--pine-color-text-secondary);cursor:pointer;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-pack:center;justify-content:center;padding:var(--pine-dimension-2xs)}.pds-datepicker__trigger:hover:not(:disabled),.pds-datepicker__clear:hover:not(:disabled){background:var(--pine-color-background-muted);color:var(--pine-color-text-strong)}.pds-datepicker__trigger:focus-visible,.pds-datepicker__clear:focus-visible{outline:var(--pine-outline-focus)}.pds-datepicker__trigger:disabled,.pds-datepicker__clear:disabled{color:var(--pine-color-text-disabled);cursor:not-allowed}.pds-datepicker__clear{-webkit-margin-end:var(--pine-dimension-2xs);margin-inline-end:var(--pine-dimension-2xs)}.pds-datepicker__nav{-ms-flex-align:center;align-items:center;background:var(--pine-color-secondary);border:var(--pine-border);border-radius:var(--pine-border-radius-full);-webkit-box-sizing:border-box;box-sizing:border-box;color:var(--pine-color-text-secondary);cursor:pointer;display:-ms-inline-flexbox;display:inline-flex;height:var(--pine-dimension-400);-ms-flex-pack:center;justify-content:center;padding:0;width:var(--pine-dimension-400)}.pds-datepicker__nav:hover:not(:disabled){background:var(--pine-color-secondary-hover);border-color:var(--pine-color-border-hover)}.pds-datepicker__nav:focus-visible{outline:var(--pine-outline-focus);outline-offset:var(--pine-border-width)}.pds-datepicker__nav:disabled{border-color:var(--pine-color-border-disabled);color:var(--pine-color-text-secondary-disabled);cursor:not-allowed}.pds-datepicker__nav:dir(rtl) pds-icon{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.pds-datepicker__dialog:focus{outline:none}.pds-datepicker__dialog{background:var(--pine-color-background-overlay);border-radius:var(--pine-dimension-150);-webkit-box-shadow:var(--pine-box-shadow-200);box-shadow:var(--pine-box-shadow-200);-webkit-box-sizing:border-box;box-sizing:border-box;left:0;max-height:calc(100vh - var(--pine-dimension-sm));max-height:calc(100dvh - var(--pine-dimension-sm));max-width:calc(100vw - var(--pine-dimension-sm));overflow:auto;-ms-scroll-chaining:none;overscroll-behavior:contain;padding:var(--pine-dimension-sm);pointer-events:auto;position:fixed;top:0;visibility:hidden;z-index:var(--pine-z-index-priority)}:host-context([data-theme=dark]) .pds-datepicker__dialog{border:var(--pine-border)}.pds-datepicker__header{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-webkit-margin-after:var(--pine-dimension-sm);margin-block-end:var(--pine-dimension-sm)}.pds-datepicker__nav-spacer{-ms-flex:none;flex:none;width:var(--pine-dimension-400)}.pds-datepicker__heading{color:var(--pine-color-text);font:var(--pine-typography-heading-5);font-weight:var(--pine-font-weight-semi-bold)}.pds-datepicker__grid{border-collapse:collapse;border-spacing:0}.pds-datepicker__weekday{color:var(--pine-color-text-muted);font:var(--pine-typography-body-sm);height:var(--pds-datepicker-day-size);padding:0;text-align:center;width:var(--pds-datepicker-day-size)}.pds-datepicker__day{border-radius:var(--pine-border-radius-full);color:var(--pine-color-text-secondary);cursor:pointer;font:var(--pine-typography-body);height:var(--pds-datepicker-day-size);padding:0;position:relative;text-align:center;-webkit-transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);width:var(--pds-datepicker-day-size)}.pds-datepicker__day:hover{background:var(--pine-color-background-muted)}.pds-datepicker__day:focus-visible{outline:var(--pine-outline-focus);outline-offset:calc(var(--pine-border-width-thick) * -1)}.pds-datepicker__day--empty{cursor:default;pointer-events:none}.pds-datepicker__day--today::after{background:var(--pine-color-primary);border-radius:var(--pine-border-radius-full);content:"";height:var(--pine-dimension-050);inset-block-end:var(--pine-dimension-050);inset-inline-start:50%;position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:var(--pine-dimension-050)}.pds-datepicker__day--selected,.pds-datepicker__day--selected:hover{background:var(--pine-color-primary);color:var(--pine-color-text-primary)}.pds-datepicker__day--selected:focus-visible{outline-offset:var(--pine-border-width)}.pds-datepicker__day--range-start{border-end-end-radius:0;border-start-end-radius:0}.pds-datepicker__day--range-end{border-end-start-radius:0;border-start-start-radius:0}.pds-datepicker__day--range-start.pds-datepicker__day--row-end{border-end-end-radius:var(--pine-border-radius-full);border-start-end-radius:var(--pine-border-radius-full)}.pds-datepicker__day--range-end.pds-datepicker__day--row-start{border-end-start-radius:var(--pine-border-radius-full);border-start-start-radius:var(--pine-border-radius-full)}.pds-datepicker__day--selected.pds-datepicker__day--today::after{background:var(--pine-color-text-primary)}.pds-datepicker__day--in-range{border-radius:0}.pds-datepicker__day--in-range,.pds-datepicker__day--in-range:hover{background:var(--pine-color-background-muted);color:var(--pine-color-text-secondary)}.pds-datepicker__day--row-start{border-end-start-radius:var(--pine-border-radius-full);border-start-start-radius:var(--pine-border-radius-full)}.pds-datepicker__day--row-end{border-end-end-radius:var(--pine-border-radius-full);border-start-end-radius:var(--pine-border-radius-full)}.pds-datepicker__day--disabled,.pds-datepicker__day--disabled:hover{background:transparent;color:var(--pine-color-text-disabled);cursor:not-allowed;text-decoration:line-through}.pds-datepicker__day--in-range:hover{background:var(--pine-color-background-inset)}.pds-datepicker__dialog--range{display:-ms-flexbox;display:flex;padding:0}.pds-datepicker__panel{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0}.pds-datepicker__dialog--range .pds-datepicker__panel{padding:var(--pine-dimension-sm)}.pds-datepicker__months{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:var(--pine-dimension-lg)}.pds-datepicker__presets-column{-webkit-border-end:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);border-inline-end:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);-ms-flex:none;flex:none;position:relative;width:var(--pds-datepicker-presets-width)}.pds-datepicker__presets{-webkit-box-sizing:border-box;box-sizing:border-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;gap:var(--pine-dimension-2xs);inset:0;list-style:none;margin:0;overflow-y:auto;padding:var(--pine-dimension-150);position:absolute}.pds-datepicker__preset{-ms-flex-align:center;align-items:center;background:transparent;border:0;border-radius:var(--pine-border-radius-md);color:var(--pine-color-text-secondary);cursor:pointer;display:-ms-flexbox;display:flex;font:var(--pine-typography-body);gap:var(--pine-dimension-xs);-ms-flex-pack:justify;justify-content:space-between;padding:var(--pine-dimension-xs) var(--pine-dimension-sm);text-align:start;-webkit-transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);transition:background var(--pine-motion-duration-fast) var(--pine-motion-easing-in);width:100%}.pds-datepicker__preset:hover:not(:disabled){background:var(--pine-color-background-muted)}.pds-datepicker__preset:focus-visible{outline:var(--pine-outline-focus)}.pds-datepicker__preset:disabled{color:var(--pine-color-text-disabled);cursor:not-allowed}.pds-datepicker__preset--selected{background:var(--pine-color-background-muted);color:var(--pine-color-text-active)}.pds-datepicker__footer{-ms-flex-align:center;align-items:center;background:var(--pine-color-background-overlay);-webkit-border-before:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);border-block-start:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:var(--pine-dimension-xs);inset-block-end:calc(var(--pine-dimension-sm) * -1);-ms-flex-pack:end;justify-content:flex-end;margin-block:var(--pine-dimension-sm) calc(var(--pine-dimension-sm) * -1);padding-block:var(--pine-dimension-sm);position:-webkit-sticky;position:sticky}.pds-datepicker__selection{color:var(--pine-color-text-muted);font:var(--pine-typography-body-sm-medium);-webkit-margin-end:auto;margin-inline-end:auto}.pds-datepicker__dropdowns{display:-ms-flexbox;display:flex;gap:var(--pine-dimension-2xs)}.pds-datepicker__select-wrap{-ms-flex-align:center;align-items:center;display:-ms-inline-flexbox;display:inline-flex;position:relative}.pds-datepicker__heading-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:var(--pine-border-radius-md);color:var(--pine-color-text);cursor:pointer;field-sizing:content;font:var(--pine-typography-heading-5);font-weight:var(--pine-font-weight-semi-bold);padding-block:var(--pine-dimension-2xs);padding-inline:var(--pine-dimension-xs) calc(var(--pine-dimension-xs) + var(--pine-dimension-sm))}.pds-datepicker__heading-select:hover{background:var(--pine-color-background-muted)}.pds-datepicker__heading-select:focus-visible{outline:var(--pine-outline-focus)}.pds-datepicker__select-caret{color:var(--pine-color-text-secondary);inset-inline-end:var(--pine-dimension-xs);pointer-events:none;position:absolute}.pds-datepicker__time{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;gap:var(--pine-dimension-xs);-ms-flex-pack:justify;justify-content:space-between;-webkit-margin-before:var(--pine-dimension-sm);margin-block-start:var(--pine-dimension-sm);padding-inline:var(--pine-dimension-xs)}.pds-datepicker__time-label{color:var(--pine-color-text-label);font:var(--pine-typography-body-medium)}.pds-datepicker__time-field{-ms-flex-align:center;align-items:center;background:var(--pine-color-background-container);border:var(--pine-border);border-radius:var(--pine-border-radius-md);display:-ms-inline-flexbox;display:inline-flex;min-height:var(--pine-dimension-400);padding-inline:var(--pine-dimension-2xs)}.pds-datepicker__time-field:hover{border-color:var(--pine-color-border-hover)}.pds-datepicker__time-field:focus-within{outline:var(--pine-outline-focus);outline-offset:var(--pine-border-width)}.pds-datepicker__time-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;border-radius:var(--pine-border-radius-sm);color:var(--pine-color-text-strong);cursor:pointer;font:var(--pine-typography-body);font-variant-numeric:tabular-nums;padding:var(--pine-dimension-2xs);text-align:center}.pds-datepicker__time-select:focus-visible{background:var(--pine-color-background-muted);outline:none}.pds-datepicker__time-select--period{-webkit-margin-start:var(--pine-dimension-2xs);margin-inline-start:var(--pine-dimension-2xs)}.pds-datepicker__time-separator{color:var(--pine-color-text-secondary)}:host-context([data-theme=dark]) select{color-scheme:dark}.pds-datepicker__action{background:var(--pine-color-secondary);border:var(--pine-border);border-radius:var(--pine-border-radius-full);color:var(--pine-color-text-secondary);cursor:pointer;font:var(--pine-typography-body-medium);padding:calc(var(--pine-dimension-xs) - var(--pine-border-width)) var(--pine-dimension-sm)}.pds-datepicker__action:hover:not(:disabled){background:var(--pine-color-secondary-hover);border-color:var(--pine-color-border-hover)}.pds-datepicker__action:focus-visible{outline:var(--pine-outline-focus);outline-offset:var(--pine-border-width)}.pds-datepicker__action:disabled{background:var(--pine-color-secondary-disabled);border-color:var(--pine-color-border-disabled);color:var(--pine-color-text-secondary-disabled);cursor:not-allowed}.pds-datepicker__action--primary{background:var(--pine-color-primary);border-color:var(--pine-color-primary);color:var(--pine-color-text-primary)}.pds-datepicker__action--primary:hover:not(:disabled){background:var(--pine-color-primary-hover);border-color:var(--pine-color-primary-hover)}.pds-datepicker__action--primary:disabled{background:var(--pine-color-primary-disabled);border-color:var(--pine-color-primary-disabled);color:var(--pine-color-text-primary-disabled)}@media (max-width: 600px){.pds-datepicker__dialog--range{-ms-flex-direction:column;flex-direction:column}.pds-datepicker__presets-column{-webkit-border-after:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);border-block-end:var(--pine-border-width-thin) solid var(--pine-color-border-subtle);-webkit-border-end:0;border-inline-end:0;width:auto}.pds-datepicker__presets{-ms-flex-direction:row;flex-direction:row;overflow-x:auto;position:static}.pds-datepicker__preset{white-space:nowrap}.pds-datepicker__selection{-ms-flex-preferred-size:100%;flex-basis:100%}}.pds-datepicker__trigger,.pds-datepicker__clear,.pds-datepicker__nav,.pds-datepicker__weekday,.pds-datepicker__day,.pds-datepicker__preset,.pds-datepicker__action{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (forced-colors: active){.pds-datepicker__day--selected,.pds-datepicker__day--selected:hover{background:Highlight;color:HighlightText;forced-color-adjust:none}.pds-datepicker__day--in-range,.pds-datepicker__day--in-range:hover{border-block:var(--pine-border-width-thin) solid Highlight}.pds-datepicker__day--today::after{background:CanvasText;forced-color-adjust:none}.pds-datepicker__day--selected.pds-datepicker__day--today::after{background:HighlightText}.pds-datepicker__day--disabled{color:GrayText}}.visually-hidden{border:0;clip:rect(0 0 0 0);-webkit-clip-path:polygon(0 0, 0 0, 0 0);clip-path:polygon(0 0, 0 0, 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}`;
578
678
 
579
679
  const FULL_DATE = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
680
+ /** A day in month-day mode, which has no year and so no weekday. */
681
+ const MONTH_AND_DAY = { month: 'long', day: 'numeric' };
580
682
  const DEFAULT_START_TIME = '00:00';
581
683
  const DEFAULT_END_TIME = '23:59';
582
684
  /** Ids for datepickers without a `component-id`, so their calendars' ids stay unique. */
@@ -655,8 +757,10 @@ const PdsDatepicker = class {
655
757
  * in the locale), `MM` or `M` (month), `DD` or `D` (day), and with the time picker `HH` or `H`
656
758
  * (24-hour), `hh` or `h` (12-hour), `mm` and `A` (AM/PM, in the locale), as `MM/DD/YYYY`,
657
759
  * `MMM D, YYYY` or `D.M.YYYY h:mm A`. Put literal text in brackets, as `[Due] D/M/YYYY`. The value
658
- * and the change event stay ISO (`YYYY-MM-DD`), whatever the format. Typed ISO dates are always
659
- * accepted too.
760
+ * and the change event stay ISO (`YYYY-MM-DD`; `--MM-DD` with `month-day`), whatever the format.
761
+ * Typed ISO dates are always accepted too. With `month-day`, a format without `YYYY` is taken as a
762
+ * month-and-day format and used as given, rather than reordered for the locale; leave it unset to
763
+ * follow the locale instead.
660
764
  */
661
765
  this.format = 'YYYY-MM-DD';
662
766
  /**
@@ -689,6 +793,15 @@ const PdsDatepicker = class {
689
793
  * Accessible label for the month dropdown, when `show-dropdowns` is set.
690
794
  */
691
795
  this.monthLabel = 'Month';
796
+ /**
797
+ * Picks a month and day with no year, for a date that comes round every year, such as a birthday.
798
+ * The value is `--MM-DD` (ISO 8601 without a year), the field shows the date as the locale writes
799
+ * a month and day, and the calendar shows each month without a year or weekdays, with February
800
+ * always having its 29th. Turns off `range`, `time-picker`, `show-dropdowns`, `min` and `max`, and
801
+ * has no effect with `auto-apply`, `presets`, `first-day-of-week`, `min-date-message` or
802
+ * `max-date-message`, which all depend on them.
803
+ */
804
+ this.monthDay = false;
692
805
  /**
693
806
  * Accessible label for the button that shows the next month.
694
807
  */
@@ -737,7 +850,9 @@ const PdsDatepicker = class {
737
850
  this.triggerLabel = 'Choose date';
738
851
  /**
739
852
  * The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with
740
- * the time picker, each date as `YYYY-MM-DDTHH:mm`. An empty string when nothing is selected.
853
+ * the time picker, each date as `YYYY-MM-DDTHH:mm`; with `month-day`, the month and day with no
854
+ * year, as `--MM-DD` (a full date is read too, as that month and day, and rewritten to it). An
855
+ * empty string when nothing is selected.
741
856
  */
742
857
  this.value = '';
743
858
  /**
@@ -928,17 +1043,17 @@ const PdsDatepicker = class {
928
1043
  // The times before the typed text changed them, for a pick in the calendar to go back to.
929
1044
  (_b = this.timesBeforeTyping) !== null && _b !== void 0 ? _b : (this.timesBeforeTyping = [startTime, endTime]);
930
1045
  this.loadDraft(parts);
931
- if (this.timePicker) {
1046
+ if (this.hasTime) {
932
1047
  // A time picked in the calendar survives typing that doesn't change the time: text with no
933
1048
  // time, as a date part-way typed, or with the time as committed.
934
1049
  const format = this.resolvedFormat;
935
1050
  const locale = this.resolvedLocale;
936
- const typed = this.range ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
1051
+ const typed = this.isRange ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
937
1052
  const committed = this.parsedValue();
938
1053
  const keeps = (i) => { var _a, _b, _c; return !((_a = typed === null || typed === void 0 ? void 0 : typed[i]) === null || _a === void 0 ? void 0 : _a.time) || ((_b = typed[i]) === null || _b === void 0 ? void 0 : _b.time) === ((_c = committed === null || committed === void 0 ? void 0 : committed[i]) === null || _c === void 0 ? void 0 : _c.time); };
939
1054
  if (keeps(0))
940
1055
  this.startTime = startTime;
941
- if (this.range && keeps(1))
1056
+ if (this.isRange && keeps(1))
942
1057
  this.endTime = endTime;
943
1058
  }
944
1059
  this.showDate(parts[parts.length - 1].date);
@@ -1047,7 +1162,8 @@ const PdsDatepicker = class {
1047
1162
  if (!active)
1048
1163
  return;
1049
1164
  const rtl = getComputedStyle(this.el).direction === 'rtl';
1050
- const column = weekdayColumn(active, this.weekStart);
1165
+ // Month-day mode's rows start on the 1st, not on a weekday.
1166
+ const column = this.monthDay ? (active.getUTCDate() - 1) % 7 : weekdayColumn(active, this.weekStart);
1051
1167
  let next = null;
1052
1168
  switch (event.key) {
1053
1169
  case 'ArrowLeft':
@@ -1065,14 +1181,21 @@ const PdsDatepicker = class {
1065
1181
  case 'Home':
1066
1182
  next = addDays(active, -column);
1067
1183
  break;
1068
- case 'End':
1069
- next = addDays(active, 6 - column);
1184
+ case 'End': {
1185
+ // Month-day mode's rows don't carry into the next month, so End stops at the month's last day.
1186
+ const lastColumn = this.monthDay
1187
+ ? Math.min(6, daysInMonth(active.getUTCFullYear(), active.getUTCMonth()) - active.getUTCDate() + column)
1188
+ : 6;
1189
+ next = addDays(active, lastColumn - column);
1070
1190
  break;
1191
+ }
1071
1192
  case 'PageUp':
1072
- next = addMonths(active, event.shiftKey ? -12 : -1);
1193
+ // Month-day mode has no year to jump by, so Shift+PageUp stays put rather than passing
1194
+ // through a real, possibly non-leap year and clamping Feb 29 to the 28th.
1195
+ next = event.shiftKey && this.monthDay ? active : addMonths(active, event.shiftKey ? -12 : -1);
1073
1196
  break;
1074
1197
  case 'PageDown':
1075
- next = addMonths(active, event.shiftKey ? 12 : 1);
1198
+ next = event.shiftKey && this.monthDay ? active : addMonths(active, event.shiftKey ? 12 : 1);
1076
1199
  break;
1077
1200
  case 'Enter':
1078
1201
  case ' ':
@@ -1085,9 +1208,9 @@ const PdsDatepicker = class {
1085
1208
  return;
1086
1209
  }
1087
1210
  event.preventDefault();
1088
- const target = clampDate(next, this.minDate, this.maxDate);
1211
+ const target = clampDate(this.inView(next), this.minDate, this.maxDate);
1089
1212
  this.showDate(target);
1090
- if (this.range)
1213
+ if (this.isRange)
1091
1214
  this.previewDate = toIsoDate(target);
1092
1215
  this.focusActiveDay = true;
1093
1216
  };
@@ -1098,7 +1221,7 @@ const PdsDatepicker = class {
1098
1221
  this.pickDate(date);
1099
1222
  };
1100
1223
  this.onGridPointerOver = (event) => {
1101
- if (!this.range || !this.draftStart || this.draftEnd)
1224
+ if (!this.isRange || !this.draftStart || this.draftEnd)
1102
1225
  return;
1103
1226
  const date = this.dayFromEvent(event);
1104
1227
  const iso = date && toIsoDate(date);
@@ -1147,19 +1270,28 @@ const PdsDatepicker = class {
1147
1270
  var _a;
1148
1271
  this.text = this.fieldText();
1149
1272
  this.textError = '';
1273
+ const parts = this.parsedValue();
1274
+ // A full date reads as its month and day; keep what's submitted in step with what's shown,
1275
+ // rather than passing through whichever spelling was set until the next pick rewrites it.
1276
+ if (this.monthDay && parts) {
1277
+ const normalized = this.dayValue(parts[0].date);
1278
+ if (this.value !== normalized)
1279
+ this.value = normalized;
1280
+ }
1150
1281
  this.updateFormValue();
1151
1282
  if (!this.isOpen)
1152
1283
  return;
1153
- const parts = this.parsedValue();
1154
1284
  this.loadDraft(parts);
1155
- const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : today();
1285
+ const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : this.now;
1156
1286
  this.showDate(clampDate(day, this.minDate, this.maxDate), true);
1157
1287
  }
1158
1288
  requiredChanged() {
1159
1289
  this.updateFormValue();
1160
1290
  }
1161
1291
  componentWillLoad() {
1162
- this.text = this.fieldText();
1292
+ // Through the same path a later value/context change takes, so an initial full date in
1293
+ // month-day mode submits normalized too, not only after the first pick rewrites it.
1294
+ this.refreshFromValue();
1163
1295
  }
1164
1296
  connectedCallback() {
1165
1297
  if (this.el.attachInternals && !this.internals) {
@@ -1209,10 +1341,30 @@ const PdsDatepicker = class {
1209
1341
  return localeFirstDayOfWeek(this.resolvedLocale);
1210
1342
  }
1211
1343
  get minDate() {
1212
- return parseIsoDate(this.min);
1344
+ return this.monthDay ? null : parseIsoDate(this.min);
1213
1345
  }
1214
1346
  get maxDate() {
1215
- return parseIsoDate(this.max);
1347
+ return this.monthDay ? null : parseIsoDate(this.max);
1348
+ }
1349
+ /** Range mode, which month-day mode turns off. */
1350
+ get isRange() {
1351
+ return this.range && !this.monthDay;
1352
+ }
1353
+ /** The time picker, which month-day mode turns off. */
1354
+ get hasTime() {
1355
+ return this.timePicker && !this.monthDay;
1356
+ }
1357
+ /** Today, or in month-day mode today's month and day. */
1358
+ get now() {
1359
+ return this.monthDay ? inMonthDayYear(today()) : today();
1360
+ }
1361
+ /** In month-day mode, a date moved to the mode's year, as when the calendar wraps past December. */
1362
+ inView(date) {
1363
+ return this.monthDay ? inMonthDayYear(date) : date;
1364
+ }
1365
+ /** One day as the value holds it: `YYYY-MM-DD`, or `--MM-DD` in month-day mode. */
1366
+ dayValue(date) {
1367
+ return this.monthDay ? toMonthDay(date) : toIsoDate(date);
1216
1368
  }
1217
1369
  get isInteractive() {
1218
1370
  return !this.disabled && !this.readonly;
@@ -1220,22 +1372,22 @@ const PdsDatepicker = class {
1220
1372
  /** How many months the calendar shows side by side. */
1221
1373
  get monthCount() {
1222
1374
  const narrow = typeof window !== 'undefined' && window.innerWidth < TWO_MONTHS_MIN_WIDTH;
1223
- return this.range && !narrow ? 2 : 1;
1375
+ return this.isRange && !narrow ? 2 : 1;
1224
1376
  }
1225
1377
  /** Whether picks apply at once, without the footer: `auto-apply` in range mode, without times. */
1226
1378
  get appliesOnPick() {
1227
- return this.range && this.autoApply && !this.timePicker;
1379
+ return this.isRange && this.autoApply && !this.hasTime;
1228
1380
  }
1229
1381
  /** Whether picks wait for Apply: for a range, unless it auto-applies, and for a date with a time. */
1230
1382
  get hasFooter() {
1231
- return (this.range || this.timePicker) && !this.appliesOnPick;
1383
+ return (this.isRange || this.hasTime) && !this.appliesOnPick;
1232
1384
  }
1233
1385
  get resolvedPresets() {
1234
- return this.range ? resolvePresets(this.presets, today()) : [];
1386
+ return this.isRange ? resolvePresets(this.presets, today()) : [];
1235
1387
  }
1236
1388
  /** The committed value, parsed: one date, or a range's start and end. */
1237
1389
  parsedValue(value = this.value) {
1238
- if (this.range) {
1390
+ if (this.isRange) {
1239
1391
  const [start, end, extra] = (value !== null && value !== void 0 ? value : '').split('/');
1240
1392
  const startValue = parseDateTime(start);
1241
1393
  const endValue = parseDateTime(end);
@@ -1243,12 +1395,22 @@ const PdsDatepicker = class {
1243
1395
  ? [startValue, endValue]
1244
1396
  : null;
1245
1397
  }
1398
+ if (this.monthDay) {
1399
+ const date = parseMonthDay(value);
1400
+ return date ? [{ date, time: null }] : null;
1401
+ }
1246
1402
  const single = parseDateTime(value);
1247
1403
  return single ? [single] : null;
1248
1404
  }
1249
1405
  /** The format the field shows and reads, with a time when the time picker is on. */
1250
1406
  get resolvedFormat() {
1251
- return fieldFormat(this.format, this.timePicker, this.timePicker && clockFor(this.resolvedLocale).hour12);
1407
+ var _a, _b;
1408
+ if (this.monthDay) {
1409
+ // A format with a year is the default, or one for full dates: use the locale's month and day.
1410
+ const format = (_b = (_a = this.format) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
1411
+ return format !== '' && !/YYYY/.test(format.replace(/\[[^\]]*\]/g, '')) ? format : monthDayFormat(this.resolvedLocale);
1412
+ }
1413
+ return fieldFormat(this.format, this.hasTime, this.hasTime && clockFor(this.resolvedLocale).hour12);
1252
1414
  }
1253
1415
  /** What the field shows for the committed value. */
1254
1416
  fieldText() {
@@ -1264,20 +1426,24 @@ const PdsDatepicker = class {
1264
1426
  /** Drops a time the time picker doesn't show, or adds the default one it does. */
1265
1427
  withTimePicker(value, fallback = DEFAULT_START_TIME) {
1266
1428
  var _a;
1267
- return { date: value.date, time: this.timePicker ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
1429
+ return { date: value.date, time: this.hasTime ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
1268
1430
  }
1269
1431
  get fieldPlaceholder() {
1270
1432
  if (this.placeholder)
1271
1433
  return this.placeholder;
1434
+ // Month-day tokens, as `MMMM D`, don't read as a hint; an example date does.
1435
+ if (this.monthDay) {
1436
+ return formatWith({ date: parseMonthDay('--12-31'), time: null }, this.resolvedFormat, this.resolvedLocale);
1437
+ }
1272
1438
  const one = this.resolvedFormat;
1273
- return this.range ? `${one}${RANGE_SEPARATOR}${one}` : one;
1439
+ return this.isRange ? `${one}${RANGE_SEPARATOR}${one}` : one;
1274
1440
  }
1275
1441
  /** Loads the committed value into the draft the calendar edits. */
1276
1442
  loadDraft(parts) {
1277
1443
  var _a, _b;
1278
1444
  const [start, end] = parts !== null && parts !== void 0 ? parts : [];
1279
1445
  this.draftStart = start ? toIsoDate(start.date) : '';
1280
- this.draftEnd = this.range && end ? toIsoDate(end.date) : '';
1446
+ this.draftEnd = this.isRange && end ? toIsoDate(end.date) : '';
1281
1447
  this.startTime = (_a = start === null || start === void 0 ? void 0 : start.time) !== null && _a !== void 0 ? _a : DEFAULT_START_TIME;
1282
1448
  this.endTime = (_b = end === null || end === void 0 ? void 0 : end.time) !== null && _b !== void 0 ? _b : DEFAULT_END_TIME;
1283
1449
  this.previewDate = '';
@@ -1293,7 +1459,7 @@ const PdsDatepicker = class {
1293
1459
  const parts = this.parsedValue();
1294
1460
  this.loadDraft(parts);
1295
1461
  this.valueAtOpen = (_a = this.value) !== null && _a !== void 0 ? _a : '';
1296
- const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : today();
1462
+ const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : this.now;
1297
1463
  this.showDate(clampDate(start, this.minDate, this.maxDate), true);
1298
1464
  this.focusActiveDay = focusDay;
1299
1465
  this.isOpen = true;
@@ -1315,7 +1481,8 @@ const PdsDatepicker = class {
1315
1481
  * Makes a day the active one and brings it into view. With two months, a day already on view
1316
1482
  * doesn't move the view; `reset` lines the view up so the day is in the last month shown.
1317
1483
  */
1318
- showDate(date, reset = false) {
1484
+ showDate(day, reset = false) {
1485
+ const date = this.inView(day);
1319
1486
  const first = firstOfMonth(date);
1320
1487
  const view = parseIsoDate(this.viewMonth);
1321
1488
  const lastView = view && addMonths(view, this.monthCount - 1);
@@ -1333,15 +1500,15 @@ const PdsDatepicker = class {
1333
1500
  // A pick in the calendar takes over from typed text, so dismissing afterwards commits nothing,
1334
1501
  // as when nothing was typed.
1335
1502
  this.dropTypedText();
1336
- if (this.range) {
1503
+ if (this.isRange) {
1337
1504
  this.pickRangeDay(date);
1338
1505
  }
1339
- else if (this.timePicker) {
1506
+ else if (this.hasTime) {
1340
1507
  this.draftStart = toIsoDate(date);
1341
1508
  this.activeDate = this.draftStart;
1342
1509
  }
1343
1510
  else {
1344
- this.commitValue(toIsoDate(date));
1511
+ this.commitValue(this.dayValue(date));
1345
1512
  this.closeCalendar(true);
1346
1513
  }
1347
1514
  }
@@ -1385,7 +1552,11 @@ const PdsDatepicker = class {
1385
1552
  }
1386
1553
  /** Commits one date, or a range with its start, end and matching preset. */
1387
1554
  commitParts(parts) {
1388
- if (!this.range) {
1555
+ if (this.monthDay) {
1556
+ this.commitValue(toMonthDay(parts[0].date));
1557
+ return;
1558
+ }
1559
+ if (!this.isRange) {
1389
1560
  this.commitValue(toValueString(parts[0]));
1390
1561
  return;
1391
1562
  }
@@ -1400,7 +1571,7 @@ const PdsDatepicker = class {
1400
1571
  if (!startDate)
1401
1572
  return null;
1402
1573
  const start = this.withTimePicker({ date: startDate, time: this.startTime });
1403
- if (!this.range)
1574
+ if (!this.isRange)
1404
1575
  return [start];
1405
1576
  const end = this.withTimePicker({ date: (_a = parseIsoDate(this.draftEnd)) !== null && _a !== void 0 ? _a : startDate, time: this.endTime });
1406
1577
  // An end time before the start time on the same day ends at the start.
@@ -1526,7 +1697,11 @@ const PdsDatepicker = class {
1526
1697
  readText(text) {
1527
1698
  const format = this.resolvedFormat;
1528
1699
  const locale = this.resolvedLocale;
1529
- const parts = this.range ? parseRangeText(text, format, locale) : [parseFieldText(text, format, locale)];
1700
+ const parts = this.monthDay
1701
+ ? [parseMonthDayText(text, format, locale)]
1702
+ : this.isRange
1703
+ ? parseRangeText(text, format, locale)
1704
+ : [parseFieldText(text, format, locale)];
1530
1705
  if (!parts || parts.some((part) => !part))
1531
1706
  return null;
1532
1707
  return parts.map((part, i) => this.withTimePicker(part, i === 1 ? DEFAULT_END_TIME : DEFAULT_START_TIME));
@@ -1570,17 +1745,17 @@ const PdsDatepicker = class {
1570
1745
  /** Whether the month before (`-1`) or after (`1`) the ones on view has a day in range. */
1571
1746
  canShowMonth(direction) {
1572
1747
  var _a;
1573
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
1748
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
1574
1749
  if (direction < 0)
1575
1750
  return !this.minDate || addDays(view, -1) >= this.minDate;
1576
1751
  return !this.maxDate || addMonths(view, this.monthCount) <= this.maxDate;
1577
1752
  }
1578
1753
  showMonth(event, direction) {
1579
1754
  var _a, _b;
1580
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
1581
- const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : today();
1582
- this.viewMonth = toIsoDate(addMonths(view, direction));
1583
- this.activeDate = toIsoDate(clampDate(addMonths(active, direction), this.minDate, this.maxDate));
1755
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
1756
+ const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : this.now;
1757
+ this.viewMonth = toIsoDate(this.inView(addMonths(view, direction)));
1758
+ this.activeDate = toIsoDate(clampDate(this.inView(addMonths(active, direction)), this.minDate, this.maxDate));
1584
1759
  if (this.canShowMonth(direction)) {
1585
1760
  // Safari doesn't focus a clicked button; without this, focus falls to the page and Escape and
1586
1761
  // the Tab trap stop working.
@@ -1702,7 +1877,7 @@ const PdsDatepicker = class {
1702
1877
  const parts = this.parsedValue();
1703
1878
  if (!parts)
1704
1879
  return this.triggerLabel;
1705
- const dates = parts.map((part) => formatDate(part.date, locale, FULL_DATE));
1880
+ const dates = parts.map((part) => formatDate(part.date, locale, this.monthDay ? MONTH_AND_DAY : FULL_DATE));
1706
1881
  return `${this.triggerLabel}, ${dates.join(RANGE_SEPARATOR)}`;
1707
1882
  }
1708
1883
  /** The range to highlight: the chosen one, or the start to the previewed day while picking the end. */
@@ -1716,15 +1891,15 @@ const PdsDatepicker = class {
1716
1891
  }
1717
1892
  /** The day shown as selected: the draft, which opens as the committed day and follows typing. */
1718
1893
  selectedDay() {
1719
- if (this.range)
1894
+ if (this.isRange)
1720
1895
  return null;
1721
1896
  return parseIsoDate(this.draftStart);
1722
1897
  }
1723
1898
  renderMonthHeading(view, index$2, locale, headingId) {
1724
- const heading = formatDate(view, locale, { month: 'long', year: 'numeric' });
1899
+ const heading = formatDate(view, locale, this.monthDay ? { month: 'long' } : { month: 'long', year: 'numeric' });
1725
1900
  // Only the first month announces itself as it changes; two months move together.
1726
1901
  const live = index$2 === 0 ? 'polite' : null;
1727
- if (!this.showDropdowns) {
1902
+ if (!this.showDropdowns || this.monthDay) {
1728
1903
  return (index.h("div", { class: "pds-datepicker__heading", id: headingId, "aria-live": live }, heading));
1729
1904
  }
1730
1905
  const year = view.getUTCFullYear();
@@ -1750,14 +1925,14 @@ const PdsDatepicker = class {
1750
1925
  const min = this.minDate;
1751
1926
  const max = this.maxDate;
1752
1927
  const selected = this.selectedDay();
1753
- const [rangeStart, rangeEnd] = this.range ? this.highlightedRange() : [null, null];
1754
- const now = today();
1755
- const fullDate = dateFormatter(locale, FULL_DATE);
1928
+ const [rangeStart, rangeEnd] = this.isRange ? this.highlightedRange() : [null, null];
1929
+ const now = this.now;
1930
+ const fullDate = dateFormatter(locale, this.monthDay ? MONTH_AND_DAY : FULL_DATE);
1756
1931
  const dayNumber = dateFormatter(locale, { day: 'numeric' });
1757
1932
  const headingId = `${this.baseId}-calendar-heading-${index$2}`;
1758
1933
  const isFirst = index$2 === 0;
1759
1934
  const isLast = index$2 === this.monthCount - 1;
1760
- return (index.h("div", { class: "pds-datepicker__month" }, index.h("div", { class: "pds-datepicker__header" }, isFirst ? (index.h("button", { type: "button", class: "pds-datepicker__nav pds-datepicker__nav--previous", "aria-label": this.previousMonthLabel, disabled: !this.canShowMonth(-1), onClick: (event) => this.showMonth(event, -1) }, index.h("pds-icon", { icon: index$1.caretLeft, "aria-hidden": "true" }))) : (index.h("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index$2, locale, headingId), isLast ? (index.h("button", { type: "button", class: "pds-datepicker__nav pds-datepicker__nav--next", "aria-label": this.nextMonthLabel, disabled: !this.canShowMonth(1), onClick: (event) => this.showMonth(event, 1) }, index.h("pds-icon", { icon: index$1.caretRight, "aria-hidden": "true" }))) : (index.h("span", { class: "pds-datepicker__nav-spacer" }))), index.h("table", { class: "pds-datepicker__grid", role: "grid", "aria-labelledby": headingId, "aria-multiselectable": this.range ? 'true' : null, onKeyDown: this.onGridKeyDown, onClick: this.onGridClick, onPointerOver: this.onGridPointerOver }, index.h("thead", null, index.h("tr", null, weekdayNames(locale, this.weekStart).map((name) => (index.h("th", { scope: "col", class: "pds-datepicker__weekday" }, index.h("span", { "aria-hidden": "true" }, name.short), index.h("span", { class: "visually-hidden" }, name.long)))))), index.h("tbody", null, monthWeeks(year, month, this.weekStart).map((week) => (index.h("tr", null, week.map((date, column) => {
1935
+ return (index.h("div", { class: "pds-datepicker__month" }, index.h("div", { class: "pds-datepicker__header" }, isFirst ? (index.h("button", { type: "button", class: "pds-datepicker__nav pds-datepicker__nav--previous", "aria-label": this.previousMonthLabel, disabled: !this.canShowMonth(-1), onClick: (event) => this.showMonth(event, -1) }, index.h("pds-icon", { icon: index$1.caretLeft, "aria-hidden": "true" }))) : (index.h("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index$2, locale, headingId), isLast ? (index.h("button", { type: "button", class: "pds-datepicker__nav pds-datepicker__nav--next", "aria-label": this.nextMonthLabel, disabled: !this.canShowMonth(1), onClick: (event) => this.showMonth(event, 1) }, index.h("pds-icon", { icon: index$1.caretRight, "aria-hidden": "true" }))) : (index.h("span", { class: "pds-datepicker__nav-spacer" }))), index.h("table", { class: "pds-datepicker__grid", role: "grid", "aria-labelledby": headingId, "aria-multiselectable": this.isRange ? 'true' : null, onKeyDown: this.onGridKeyDown, onClick: this.onGridClick, onPointerOver: this.onGridPointerOver }, !this.monthDay && (index.h("thead", null, index.h("tr", null, weekdayNames(locale, this.weekStart).map((name) => (index.h("th", { scope: "col", class: "pds-datepicker__weekday" }, index.h("span", { "aria-hidden": "true" }, name.short), index.h("span", { class: "visually-hidden" }, name.long))))))), index.h("tbody", null, (this.monthDay ? monthDayRows(month) : monthWeeks(year, month, this.weekStart)).map((week) => (index.h("tr", null, week.map((date, column) => {
1761
1936
  if (!date)
1762
1937
  return index.h("td", { class: "pds-datepicker__day pds-datepicker__day--empty" });
1763
1938
  const iso = toIsoDate(date);
@@ -1779,14 +1954,14 @@ const PdsDatepicker = class {
1779
1954
  'pds-datepicker__day--today': isToday,
1780
1955
  'pds-datepicker__day--disabled': unavailable,
1781
1956
  }, "data-date": iso, tabindex: iso === this.activeDate ? '0' : '-1', "aria-label": fullDate.format(date), "aria-selected": isSelected ? 'true' : null, "aria-current": isToday ? 'date' : null, "aria-disabled": unavailable ? 'true' : null }, dayNumberText(dayNumber, date)));
1782
- })))))), this.timePicker && this.timesUnderMonth(index$2).map((which) => this.renderTime(which, locale))));
1957
+ })))))), this.hasTime && this.timesUnderMonth(index$2).map((which) => this.renderTime(which, locale))));
1783
1958
  }
1784
1959
  /**
1785
1960
  * The time pickers under a month: the start time under the first and the end time under the
1786
1961
  * second, or both under the one month a narrow range shows.
1787
1962
  */
1788
1963
  timesUnderMonth(index) {
1789
- if (!this.range)
1964
+ if (!this.isRange)
1790
1965
  return ['start'];
1791
1966
  if (this.monthCount === 1)
1792
1967
  return ['start', 'end'];
@@ -1811,7 +1986,7 @@ const PdsDatepicker = class {
1811
1986
  if (this.timesBeforeTyping)
1812
1987
  this.timesBeforeTyping[which === 'start' ? 0 : 1] = time;
1813
1988
  };
1814
- const groupLabel = !this.range ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
1989
+ const groupLabel = !this.isRange ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
1815
1990
  const isPm = hours >= 12;
1816
1991
  const hourOptions = clock.hour12 ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i);
1817
1992
  const shownHour = clock.hour12 ? hours % 12 || 12 : hours;
@@ -1852,7 +2027,7 @@ const PdsDatepicker = class {
1852
2027
  return '';
1853
2028
  const format = this.resolvedFormat;
1854
2029
  const locale = this.resolvedLocale;
1855
- if (this.range && !this.draftEnd)
2030
+ if (this.isRange && !this.draftEnd)
1856
2031
  return `${toFieldText(parts[0], format, locale)}${RANGE_SEPARATOR}`;
1857
2032
  return parts.map((part) => toFieldText(part, format, locale)).join(RANGE_SEPARATOR);
1858
2033
  }
@@ -1863,25 +2038,25 @@ const PdsDatepicker = class {
1863
2038
  }
1864
2039
  renderCalendar(locale) {
1865
2040
  var _a;
1866
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
2041
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
1867
2042
  const months = Array.from({ length: this.monthCount }, (_, i) => addMonths(view, i));
1868
- return (index.h("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.range }, id: `${this.baseId}-calendar`, role: "dialog",
2043
+ return (index.h("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.isRange }, id: `${this.baseId}-calendar`, role: "dialog",
1869
2044
  // A click on a part that can't take focus, as the month heading, focuses the dialog
1870
2045
  // rather than dropping focus to the page, which would report blur mid-selection.
1871
- tabindex: "-1", "aria-labelledby": this.range ? null : `${this.baseId}-calendar-heading-0`, "aria-label": this.range ? this.label || this.triggerLabel : null, part: "calendar", ref: (el) => (this.dialogEl = el), onKeyDown: this.onDialogKeyDown, onFocusin: this.onCalendarFocusIn, onFocusout: this.onCalendarFocusOut }, this.range && this.renderPresets(), index.h("div", { class: "pds-datepicker__panel" }, index.h("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
2046
+ tabindex: "-1", "aria-labelledby": this.isRange ? null : `${this.baseId}-calendar-heading-0`, "aria-label": this.isRange ? this.label || this.triggerLabel : null, part: "calendar", ref: (el) => (this.dialogEl = el), onKeyDown: this.onDialogKeyDown, onFocusin: this.onCalendarFocusIn, onFocusout: this.onCalendarFocusOut }, this.isRange && this.renderPresets(), index.h("div", { class: "pds-datepicker__panel" }, index.h("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
1872
2047
  // With no footer, still announce the start once it's picked, so it's clear an end comes next.
1873
2048
  index.h("span", { class: "visually-hidden", "aria-live": "polite" }, this.selectionText())))));
1874
2049
  }
1875
2050
  render() {
1876
2051
  const locale = this.resolvedLocale;
1877
- return (index.h(index.Host, { key: 'a761f0ca6eeedcac3e4bd90760aa9781cd37dcdc', class: { 'pds-datepicker--range': this.range, 'pds-datepicker--time': this.timePicker }, "full-width": this.fullWidth ? 'true' : null, onFocusin: this.onHostFocusIn, onFocusout: this.onHostFocusOut }, index.h("pds-input", { key: '2eb04f9fd4e2f7a2437145d250c629c2cc8a0bf6', ref: (el) => (this.inputEl = el), componentId: this.baseId, label: this.label, hideLabel: this.hideLabel, helperMessage: this.helperMessage, errorMessage: this.errorMessage, invalid: this.invalid || !!this.textError || !!this.valueError(), disabled: this.disabled, readonly: this.readonly, required: this.required, placeholder: this.fieldPlaceholder, fullWidth: this.fullWidth, autocomplete: "off", value: this.text, onClick: this.onFieldClick, onPdsInput: this.onTextInput, onPdsChange: this.onTextChange, onPdsKeyDown: this.onTextKeyDown, onPdsFocus: this.stopInnerEvent, onPdsBlur: this.stopInnerEvent }, this.clearable && !!this.value && this.isInteractive && (index.h("button", {
2052
+ return (index.h(index.Host, { key: '7c5388369fe78becfd26b0807f1c6cb54c7934a1', class: { 'pds-datepicker--range': this.isRange, 'pds-datepicker--time': this.hasTime }, "full-width": this.fullWidth ? 'true' : null, onFocusin: this.onHostFocusIn, onFocusout: this.onHostFocusOut }, index.h("pds-input", { key: 'fc565c99359715415886ae63ef1bcbf8671569c9', ref: (el) => (this.inputEl = el), componentId: this.baseId, label: this.label, hideLabel: this.hideLabel, helperMessage: this.helperMessage, errorMessage: this.errorMessage, invalid: this.invalid || !!this.textError || !!this.valueError(), disabled: this.disabled, readonly: this.readonly, required: this.required, placeholder: this.fieldPlaceholder, fullWidth: this.fullWidth, autocomplete: "off", value: this.text, onClick: this.onFieldClick, onPdsInput: this.onTextInput, onPdsChange: this.onTextChange, onPdsKeyDown: this.onTextKeyDown, onPdsFocus: this.stopInnerEvent, onPdsBlur: this.stopInnerEvent }, this.clearable && !!this.value && this.isInteractive && (index.h("button", {
1878
2053
  // Keyed, as is the calendar button: when Clear appears after a pick, the focused
1879
2054
  // calendar button must stay that button rather than be reused as Clear.
1880
2055
  key: "clear", slot: "suffix", type: "button", class: "pds-datepicker__clear", ref: (el) => (this.clearEl = el), "aria-label": this.label ? `${this.clearLabel} ${this.label}` : this.clearLabel,
1881
2056
  // Out of the tab order, as a native search field's clear button is: Tab from a field
1882
2057
  // just emptied would land on it as the change removes it, dropping focus to the page.
1883
2058
  tabindex: "-1", onMouseDown: this.onClearMouseDown, onClick: this.onClearClick
1884
- }, index.h("pds-icon", { key: '98c325fbba8ea78b2d409bc77099db3206738508', icon: index$1.remove, "aria-hidden": "true" }))), index.h("button", { key: "trigger", slot: "suffix", type: "button", class: "pds-datepicker__trigger", ref: (el) => (this.triggerEl = el), "aria-label": this.triggerAccessibleName(locale), "aria-haspopup": "dialog", "aria-expanded": this.isOpen ? 'true' : 'false', disabled: !this.isInteractive, onClick: this.onTriggerClick, onKeyDown: this.onTriggerKeyDown }, index.h("pds-icon", { key: 'cb8483febe1b485058b7bb4271b749981a7747a1', icon: index$1.calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
2059
+ }, index.h("pds-icon", { key: '6a7da726d86654d3598f84b8ea7a8ed244a8e5da', icon: index$1.remove, "aria-hidden": "true" }))), index.h("button", { key: "trigger", slot: "suffix", type: "button", class: "pds-datepicker__trigger", ref: (el) => (this.triggerEl = el), "aria-label": this.triggerAccessibleName(locale), "aria-haspopup": "dialog", "aria-expanded": this.isOpen ? 'true' : 'false', disabled: !this.isInteractive, onClick: this.onTriggerClick, onKeyDown: this.onTriggerKeyDown }, index.h("pds-icon", { key: '92af87e66b7bb2e95861b873077a7bfb0268c2e0', icon: index$1.calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
1885
2060
  }
1886
2061
  static get formAssociated() { return true; }
1887
2062
  get el() { return index.getElement(this); }
@@ -1901,6 +2076,9 @@ const PdsDatepicker = class {
1901
2076
  "range": [{
1902
2077
  "contextChanged": 0
1903
2078
  }],
2079
+ "monthDay": [{
2080
+ "contextChanged": 0
2081
+ }],
1904
2082
  "min": [{
1905
2083
  "contextChanged": 0
1906
2084
  }],