@pine-ds/core 4.9.1 → 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 (98) hide show
  1. package/components/pds-checkbox2.js +1 -1
  2. package/components/pds-chip2.js +1 -1
  3. package/components/pds-combobox.js +2 -2
  4. package/components/pds-datepicker.js +1 -1
  5. package/components/pds-dropdown-menu.js +1 -1
  6. package/components/truncation-tooltip.js +1 -1
  7. package/dist/cjs/loader.cjs.js +1 -1
  8. package/dist/cjs/pds-checkbox.cjs.entry.js +1 -1
  9. package/dist/cjs/pds-chip.cjs.entry.js +5 -3
  10. package/dist/cjs/pds-combobox.cjs.entry.js +101 -15
  11. package/dist/cjs/pds-copytext.cjs.entry.js +1 -1
  12. package/dist/cjs/pds-datepicker.cjs.entry.js +253 -75
  13. package/dist/cjs/pds-dropdown-menu.cjs.entry.js +58 -46
  14. package/dist/cjs/pds-table-cell.cjs.entry.js +1 -1
  15. package/dist/cjs/pds-text.cjs.entry.js +1 -1
  16. package/dist/cjs/pine-core.cjs.js +1 -1
  17. package/dist/cjs/{truncation-tooltip-DdB78RIe.js → truncation-tooltip-Bikp7n9I.js} +1 -1
  18. package/dist/collection/components/pds-checkbox/pds-checkbox.css +20 -14
  19. package/dist/collection/components/pds-chip/pds-chip.js +4 -2
  20. package/dist/collection/components/pds-combobox/grouped-options-panel.js +39 -1
  21. package/dist/collection/components/pds-combobox/pds-combobox.css +37 -1
  22. package/dist/collection/components/pds-combobox/pds-combobox.js +62 -14
  23. package/dist/collection/components/pds-combobox/stories/pds-combobox.stories.js +48 -0
  24. package/dist/collection/components/pds-datepicker/date-utils.js +42 -0
  25. package/dist/collection/components/pds-datepicker/pds-datepicker.js +163 -64
  26. package/dist/collection/components/pds-datepicker/stories/pds-datepicker.stories.js +11 -0
  27. package/dist/collection/components/pds-datepicker/value.js +76 -18
  28. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +64 -46
  29. package/dist/collection/utils/truncation-tooltip.js +1 -1
  30. package/dist/docs.json +42 -7
  31. package/dist/esm/loader.js +1 -1
  32. package/dist/esm/pds-checkbox.entry.js +1 -1
  33. package/dist/esm/pds-chip.entry.js +5 -3
  34. package/dist/esm/pds-combobox.entry.js +101 -15
  35. package/dist/esm/pds-copytext.entry.js +1 -1
  36. package/dist/esm/pds-datepicker.entry.js +253 -75
  37. package/dist/esm/pds-dropdown-menu.entry.js +58 -46
  38. package/dist/esm/pds-table-cell.entry.js +1 -1
  39. package/dist/esm/pds-text.entry.js +1 -1
  40. package/dist/esm/pine-core.js +1 -1
  41. package/dist/esm/{truncation-tooltip-CRSdYzpA.js → truncation-tooltip-DBcPipr9.js} +1 -1
  42. package/dist/esm-es5/loader.js +1 -1
  43. package/dist/esm-es5/pds-checkbox.entry.js +1 -1
  44. package/dist/esm-es5/pds-chip.entry.js +1 -1
  45. package/dist/esm-es5/pds-combobox.entry.js +1 -1
  46. package/dist/esm-es5/pds-copytext.entry.js +1 -1
  47. package/dist/esm-es5/pds-datepicker.entry.js +1 -1
  48. package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
  49. package/dist/esm-es5/pds-table-cell.entry.js +1 -1
  50. package/dist/esm-es5/pds-text.entry.js +1 -1
  51. package/dist/esm-es5/pine-core.js +1 -1
  52. package/dist/esm-es5/truncation-tooltip-DBcPipr9.js +1 -0
  53. package/dist/pine-core/p-0b7b3fc2.system.entry.js +3 -0
  54. package/dist/pine-core/{p-9a776443.entry.js → p-124a0a24.entry.js} +1 -1
  55. package/dist/pine-core/p-128c04e7.entry.js +1 -0
  56. package/dist/pine-core/p-3359a935.entry.js +1 -0
  57. package/dist/pine-core/{p-3d34b967.system.entry.js → p-3a1dc752.system.entry.js} +1 -1
  58. package/dist/pine-core/{p-20079552.system.entry.js → p-639acef8.system.entry.js} +1 -1
  59. package/dist/pine-core/{p-beb7a430.entry.js → p-97356d4b.entry.js} +1 -1
  60. package/dist/pine-core/p-BZVUD2on.js +1 -0
  61. package/dist/pine-core/p-Dj-z_ugg.system.js +1 -0
  62. package/dist/pine-core/p-XK45dzWR.system.js +1 -1
  63. package/dist/pine-core/{p-4d1a8803.entry.js → p-afc2f165.entry.js} +1 -1
  64. package/dist/pine-core/p-c6a9e62c.system.entry.js +1 -0
  65. package/dist/pine-core/p-d4968f9d.system.entry.js +1 -0
  66. package/dist/pine-core/p-da8b864a.entry.js +2 -0
  67. package/dist/pine-core/p-dd7ded84.entry.js +1 -0
  68. package/dist/pine-core/p-de455073.system.entry.js +1 -0
  69. package/dist/pine-core/{p-1cc39908.system.entry.js → p-eb56af4a.system.entry.js} +1 -1
  70. package/dist/pine-core/p-f354dbac.entry.js +1 -0
  71. package/dist/pine-core/{p-93fd7dd8.system.entry.js → p-f834cb07.system.entry.js} +1 -1
  72. package/dist/pine-core/pine-core.esm.js +1 -1
  73. package/dist/types/components/pds-combobox/combobox-interface.d.ts +26 -0
  74. package/dist/types/components/pds-combobox/grouped-options-panel.d.ts +17 -0
  75. package/dist/types/components/pds-combobox/pds-combobox.d.ts +12 -2
  76. package/dist/types/components/pds-datepicker/date-utils.d.ts +19 -0
  77. package/dist/types/components/pds-datepicker/datepicker-interface.d.ts +2 -1
  78. package/dist/types/components/pds-datepicker/pds-datepicker.d.ts +30 -5
  79. package/dist/types/components/pds-datepicker/value.d.ts +14 -2
  80. package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu.d.ts +4 -0
  81. package/dist/types/components.d.ts +25 -10
  82. package/dist/types/utils/truncation-tooltip.d.ts +1 -1
  83. package/dist/vscode.html-data.json +11 -4
  84. package/hydrate/index.js +422 -142
  85. package/hydrate/index.mjs +422 -142
  86. package/package.json +2 -2
  87. package/dist/esm-es5/truncation-tooltip-CRSdYzpA.js +0 -1
  88. package/dist/pine-core/p-37718637.entry.js +0 -1
  89. package/dist/pine-core/p-383d84f4.entry.js +0 -2
  90. package/dist/pine-core/p-3e30a761.entry.js +0 -1
  91. package/dist/pine-core/p-54f1e86f.system.entry.js +0 -1
  92. package/dist/pine-core/p-73efb1ed.system.entry.js +0 -1
  93. package/dist/pine-core/p-8436cbfb.entry.js +0 -1
  94. package/dist/pine-core/p-DQyF6v9Q.system.js +0 -1
  95. package/dist/pine-core/p-DwQ5Tjis.js +0 -1
  96. package/dist/pine-core/p-b3d4f97a.system.entry.js +0 -3
  97. package/dist/pine-core/p-d47b2a6a.system.entry.js +0 -1
  98. package/dist/pine-core/p-f018af99.entry.js +0 -1
@@ -139,6 +139,48 @@ function dateFormatter(locale, options) {
139
139
  function formatDate(date, locale, options) {
140
140
  return dateFormatter(locale, options).format(date);
141
141
  }
142
+ /**
143
+ * Month-day mode's year. A leap year, so February has its 29th; every date the mode holds is in it,
144
+ * and the value drops it (`--MM-DD`).
145
+ */
146
+ const MONTH_DAY_YEAR = 2000;
147
+ const MONTH_DAY = /^--(\d{2})-(\d{2})$/;
148
+ /** The same month and day in month-day mode's year. */
149
+ function inMonthDayYear(date) {
150
+ return new Date(Date.UTC(MONTH_DAY_YEAR, date.getUTCMonth(), date.getUTCDate()));
151
+ }
152
+ /**
153
+ * Parses a month and day, `--MM-DD` (ISO 8601 without a year), to a date in month-day mode's year.
154
+ * A full `YYYY-MM-DD` reads as its month and day. `null` when it isn't a real month and day.
155
+ */
156
+ function parseMonthDay(value) {
157
+ const text = (value !== null && value !== void 0 ? value : '').trim();
158
+ const match = MONTH_DAY.exec(text);
159
+ if (match)
160
+ return parseIsoDate(`${MONTH_DAY_YEAR}-${match[1]}-${match[2]}`);
161
+ const date = parseIsoDate(text);
162
+ return date && inMonthDayYear(date);
163
+ }
164
+ /** A date's month and day as `--MM-DD`. */
165
+ function toMonthDay(date) {
166
+ return `-${toIsoDate(date).slice(4)}`;
167
+ }
168
+ /**
169
+ * A month's days for month-day mode, which has no weekdays: rows of seven from the 1st, with the
170
+ * last row filled out with `null`.
171
+ */
172
+ function monthDayRows(month) {
173
+ const cells = [];
174
+ for (let day = 1; day <= daysInMonth(MONTH_DAY_YEAR, month); day++) {
175
+ cells.push(new Date(Date.UTC(MONTH_DAY_YEAR, month, day)));
176
+ }
177
+ while (cells.length % 7 !== 0)
178
+ cells.push(null);
179
+ const rows = [];
180
+ for (let i = 0; i < cells.length; i += 7)
181
+ rows.push(cells.slice(i, i + 7));
182
+ return rows;
183
+ }
142
184
 
143
185
  /**
144
186
  * Where the open calendar lives: a `div` in `document.body`, or in the nearest `<dialog>` so it
@@ -382,32 +424,50 @@ const wordsCache = new Map();
382
424
  function localeWords(locale) {
383
425
  let words = wordsCache.get(locale);
384
426
  if (!words) {
385
- const month = (style) => {
386
- const format = new Intl.DateTimeFormat(locale, {
387
- month: style,
388
- timeZone: 'UTC',
389
- calendar: 'gregory',
427
+ const month = (style, withDay) => {
428
+ const format = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({ month: style }, (withDay ? { day: 'numeric' } : {})), { timeZone: 'UTC', calendar: 'gregory' }));
429
+ return Array.from({ length: 12 }, (_, m) => {
430
+ const date = new Date(Date.UTC(2023, m, 1));
431
+ if (!withDay)
432
+ return format.format(date);
433
+ const parts = format.formatToParts(date);
434
+ const index = parts.findIndex((part) => part.type === 'month');
435
+ if (index === -1)
436
+ return format.format(date);
437
+ // CJK locales split a counter word, as Japanese `月`, into its own literal right after the
438
+ // month; a literal with no space is that word, not the separator before the day.
439
+ const suffix = parts[index + 1];
440
+ return (suffix === null || suffix === void 0 ? void 0 : suffix.type) === 'literal' && !/\s/.test(suffix.value) ? parts[index].value + suffix.value : parts[index].value;
390
441
  });
391
- return Array.from({ length: 12 }, (_, m) => format.format(new Date(Date.UTC(2023, m, 1))));
392
442
  };
393
443
  const clock = clockFor(locale);
394
- words = { months: month('long'), shortMonths: month('short'), am: clock.am, pm: clock.pm };
444
+ words = {
445
+ months: month('long', false),
446
+ shortMonths: month('short', false),
447
+ monthsWithDay: month('long', true),
448
+ shortMonthsWithDay: month('short', true),
449
+ am: clock.am,
450
+ pm: clock.pm,
451
+ };
395
452
  wordsCache.set(locale, words);
396
453
  }
397
454
  return words;
398
455
  }
456
+ /** Whether a format's tokens (see `tokensOnly`) have a day, so a month name should inflect for one. */
457
+ const hasDayToken = (tokens) => /D/.test(tokens);
399
458
  /** Writes a date in a format of tokens (see `FORMAT_PARTS`), with month names and AM/PM in the locale. */
400
459
  function formatWith(value, format, locale = 'en-US') {
401
460
  var _a;
402
461
  const words = localeWords(locale);
462
+ const withDay = hasDayToken(tokensOnly(format));
403
463
  const [hours, minutes] = splitTime((_a = value.time) !== null && _a !== void 0 ? _a : '00:00');
404
464
  const hour12 = hours % 12 || 12;
405
465
  const month = value.date.getUTCMonth();
406
466
  const period = hours < 12 ? words.am : words.pm;
407
467
  const parts = {
408
468
  YYYY: pad(value.date.getUTCFullYear(), 4),
409
- MMMM: words.months[month],
410
- MMM: words.shortMonths[month],
469
+ MMMM: withDay ? words.monthsWithDay[month] : words.months[month],
470
+ MMM: withDay ? words.shortMonthsWithDay[month] : words.shortMonths[month],
411
471
  MM: pad(month + 1),
412
472
  M: String(month + 1),
413
473
  DD: pad(value.date.getUTCDate()),
@@ -429,17 +489,23 @@ const alternatives = (words) => `(${words.map(escapeRegExp).join('|')})`;
429
489
  /**
430
490
  * Reads typed text in a format of tokens, as written by `formatWith`. Leading zeros are optional,
431
491
  * spaces are loose, and month names and AM/PM are matched without regard to case, in the locale or
432
- * in English. Returns `null` for anything that isn't a real date and time in that format.
492
+ * in English. A format without `YYYY` reads into `year`, and without `year` it reads nothing.
493
+ * Returns `null` for anything that isn't a real date and time in that format.
433
494
  */
434
- function parseWith(text, format, locale = 'en-US') {
495
+ function parseWith(text, format, locale = 'en-US', year) {
435
496
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
436
497
  const words = localeWords(locale);
437
498
  const english = localeWords('en-US');
499
+ const withDay = hasDayToken(tokensOnly(format));
500
+ const months = withDay ? words.monthsWithDay : words.months;
501
+ const shortMonths = withDay ? words.shortMonthsWithDay : words.shortMonths;
502
+ const englishMonths = withDay ? english.monthsWithDay : english.months;
503
+ const englishShortMonths = withDay ? english.shortMonthsWithDay : english.shortMonths;
438
504
  const periods = [words.am, words.pm, english.am, english.pm];
439
505
  const patterns = {
440
506
  YYYY: '(\\d{4})',
441
- MMMM: alternatives([...words.months, ...english.months]),
442
- MMM: alternatives([...words.shortMonths, ...english.shortMonths]),
507
+ MMMM: alternatives([...months, ...englishMonths]),
508
+ MMM: alternatives([...shortMonths, ...englishShortMonths]),
443
509
  MM: '(\\d{1,2})',
444
510
  M: '(\\d{1,2})',
445
511
  DD: '(\\d{1,2})',
@@ -486,7 +552,7 @@ function parseWith(text, format, locale = 'en-US') {
486
552
  const monthIndex = (name, long) => {
487
553
  if (name === undefined)
488
554
  return undefined;
489
- const lists = long ? [words.months, english.months] : [words.shortMonths, english.shortMonths];
555
+ const lists = long ? [months, englishMonths] : [shortMonths, englishShortMonths];
490
556
  for (const list of lists) {
491
557
  const index = list.findIndex((candidate) => candidate.toLowerCase() === name.toLowerCase());
492
558
  if (index >= 0)
@@ -494,12 +560,12 @@ function parseWith(text, format, locale = 'en-US') {
494
560
  }
495
561
  return undefined;
496
562
  };
497
- const year = Number(read.YYYY);
563
+ const readYear = read.YYYY ? Number(read.YYYY) : year;
498
564
  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);
499
565
  const day = Number((_d = read.DD) !== null && _d !== void 0 ? _d : read.D);
500
- if (!read.YYYY || !month || !day)
566
+ if (readYear === undefined || !month || !day)
501
567
  return null;
502
- const date = parseIsoDate(`${pad(year, 4)}-${pad(month)}-${pad(day)}`);
568
+ const date = parseIsoDate(`${pad(readYear, 4)}-${pad(month)}-${pad(day)}`);
503
569
  if (!date)
504
570
  return null;
505
571
  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;
@@ -521,6 +587,40 @@ function parseFieldText(text, format = DEFAULT_FORMAT, locale = 'en-US') {
521
587
  var _a;
522
588
  return (_a = parseWith(text, format, locale)) !== null && _a !== void 0 ? _a : parseDateTime(text);
523
589
  }
590
+ /**
591
+ * The locale's way of writing a month and day, as a format of tokens: `MMMM D` in English, `D MMMM`
592
+ * in French, `M月D日` in Japanese, or with `numeric`, `M/D` and `D/M`.
593
+ */
594
+ function monthDayFormat(locale = 'en-US', month = 'long') {
595
+ const parts = dateFormatter(locale, { month, day: 'numeric' }).formatToParts(new Date(Date.UTC(MONTH_DAY_YEAR, 11, 31)));
596
+ return parts
597
+ .map((part) => {
598
+ // Some locales write even the long month in digits, as Japanese `12月31日`.
599
+ if (part.type === 'month')
600
+ return /^\d+$/.test(part.value) ? 'M' : 'MMMM';
601
+ if (part.type === 'day')
602
+ return 'D';
603
+ // Bracketed, spaces included, only when it has letters a token could be read from, as
604
+ // Spanish `D[ de ]MMMM`.
605
+ return /[A-Za-z]/.test(part.value) ? `[${part.value}]` : part.value;
606
+ })
607
+ .join('');
608
+ }
609
+ /**
610
+ * Reads a typed month and day, into month-day mode's year: in the format, then as the locale writes
611
+ * it with a month name or in numbers, with the English order too, then as `--MM-DD` or a full ISO
612
+ * date, whose year is dropped.
613
+ */
614
+ function parseMonthDayText(text, format, locale = 'en-US') {
615
+ const formats = [format, monthDayFormat(locale), monthDayFormat(locale, 'numeric'), 'MMMM D', 'D MMMM', 'MMM D', 'D MMM'];
616
+ for (const candidate of formats) {
617
+ const read = parseWith(text, candidate, locale, MONTH_DAY_YEAR);
618
+ if (read)
619
+ return { date: inMonthDayYear(read.date), time: null };
620
+ }
621
+ const date = parseMonthDay(text);
622
+ return date ? { date, time: null } : null;
623
+ }
524
624
  /**
525
625
  * Parses a typed range: two dates (with times, optionally) joined by a dash or `to` with spaces
526
626
  * around it, as `2026-09-13 – 2026-10-13`, or by `/` when the format doesn't use slashes. The
@@ -575,6 +675,8 @@ function clockFor(locale) {
575
675
  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}`;
576
676
 
577
677
  const FULL_DATE = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
678
+ /** A day in month-day mode, which has no year and so no weekday. */
679
+ const MONTH_AND_DAY = { month: 'long', day: 'numeric' };
578
680
  const DEFAULT_START_TIME = '00:00';
579
681
  const DEFAULT_END_TIME = '23:59';
580
682
  /** Ids for datepickers without a `component-id`, so their calendars' ids stay unique. */
@@ -653,8 +755,10 @@ const PdsDatepicker = class {
653
755
  * in the locale), `MM` or `M` (month), `DD` or `D` (day), and with the time picker `HH` or `H`
654
756
  * (24-hour), `hh` or `h` (12-hour), `mm` and `A` (AM/PM, in the locale), as `MM/DD/YYYY`,
655
757
  * `MMM D, YYYY` or `D.M.YYYY h:mm A`. Put literal text in brackets, as `[Due] D/M/YYYY`. The value
656
- * and the change event stay ISO (`YYYY-MM-DD`), whatever the format. Typed ISO dates are always
657
- * accepted too.
758
+ * and the change event stay ISO (`YYYY-MM-DD`; `--MM-DD` with `month-day`), whatever the format.
759
+ * Typed ISO dates are always accepted too. With `month-day`, a format without `YYYY` is taken as a
760
+ * month-and-day format and used as given, rather than reordered for the locale; leave it unset to
761
+ * follow the locale instead.
658
762
  */
659
763
  this.format = 'YYYY-MM-DD';
660
764
  /**
@@ -687,6 +791,15 @@ const PdsDatepicker = class {
687
791
  * Accessible label for the month dropdown, when `show-dropdowns` is set.
688
792
  */
689
793
  this.monthLabel = 'Month';
794
+ /**
795
+ * Picks a month and day with no year, for a date that comes round every year, such as a birthday.
796
+ * The value is `--MM-DD` (ISO 8601 without a year), the field shows the date as the locale writes
797
+ * a month and day, and the calendar shows each month without a year or weekdays, with February
798
+ * always having its 29th. Turns off `range`, `time-picker`, `show-dropdowns`, `min` and `max`, and
799
+ * has no effect with `auto-apply`, `presets`, `first-day-of-week`, `min-date-message` or
800
+ * `max-date-message`, which all depend on them.
801
+ */
802
+ this.monthDay = false;
690
803
  /**
691
804
  * Accessible label for the button that shows the next month.
692
805
  */
@@ -735,7 +848,9 @@ const PdsDatepicker = class {
735
848
  this.triggerLabel = 'Choose date';
736
849
  /**
737
850
  * The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with
738
- * the time picker, each date as `YYYY-MM-DDTHH:mm`. An empty string when nothing is selected.
851
+ * the time picker, each date as `YYYY-MM-DDTHH:mm`; with `month-day`, the month and day with no
852
+ * year, as `--MM-DD` (a full date is read too, as that month and day, and rewritten to it). An
853
+ * empty string when nothing is selected.
739
854
  */
740
855
  this.value = '';
741
856
  /**
@@ -926,17 +1041,17 @@ const PdsDatepicker = class {
926
1041
  // The times before the typed text changed them, for a pick in the calendar to go back to.
927
1042
  (_b = this.timesBeforeTyping) !== null && _b !== void 0 ? _b : (this.timesBeforeTyping = [startTime, endTime]);
928
1043
  this.loadDraft(parts);
929
- if (this.timePicker) {
1044
+ if (this.hasTime) {
930
1045
  // A time picked in the calendar survives typing that doesn't change the time: text with no
931
1046
  // time, as a date part-way typed, or with the time as committed.
932
1047
  const format = this.resolvedFormat;
933
1048
  const locale = this.resolvedLocale;
934
- const typed = this.range ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
1049
+ const typed = this.isRange ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
935
1050
  const committed = this.parsedValue();
936
1051
  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); };
937
1052
  if (keeps(0))
938
1053
  this.startTime = startTime;
939
- if (this.range && keeps(1))
1054
+ if (this.isRange && keeps(1))
940
1055
  this.endTime = endTime;
941
1056
  }
942
1057
  this.showDate(parts[parts.length - 1].date);
@@ -1045,7 +1160,8 @@ const PdsDatepicker = class {
1045
1160
  if (!active)
1046
1161
  return;
1047
1162
  const rtl = getComputedStyle(this.el).direction === 'rtl';
1048
- const column = weekdayColumn(active, this.weekStart);
1163
+ // Month-day mode's rows start on the 1st, not on a weekday.
1164
+ const column = this.monthDay ? (active.getUTCDate() - 1) % 7 : weekdayColumn(active, this.weekStart);
1049
1165
  let next = null;
1050
1166
  switch (event.key) {
1051
1167
  case 'ArrowLeft':
@@ -1063,14 +1179,21 @@ const PdsDatepicker = class {
1063
1179
  case 'Home':
1064
1180
  next = addDays(active, -column);
1065
1181
  break;
1066
- case 'End':
1067
- next = addDays(active, 6 - column);
1182
+ case 'End': {
1183
+ // Month-day mode's rows don't carry into the next month, so End stops at the month's last day.
1184
+ const lastColumn = this.monthDay
1185
+ ? Math.min(6, daysInMonth(active.getUTCFullYear(), active.getUTCMonth()) - active.getUTCDate() + column)
1186
+ : 6;
1187
+ next = addDays(active, lastColumn - column);
1068
1188
  break;
1189
+ }
1069
1190
  case 'PageUp':
1070
- next = addMonths(active, event.shiftKey ? -12 : -1);
1191
+ // Month-day mode has no year to jump by, so Shift+PageUp stays put rather than passing
1192
+ // through a real, possibly non-leap year and clamping Feb 29 to the 28th.
1193
+ next = event.shiftKey && this.monthDay ? active : addMonths(active, event.shiftKey ? -12 : -1);
1071
1194
  break;
1072
1195
  case 'PageDown':
1073
- next = addMonths(active, event.shiftKey ? 12 : 1);
1196
+ next = event.shiftKey && this.monthDay ? active : addMonths(active, event.shiftKey ? 12 : 1);
1074
1197
  break;
1075
1198
  case 'Enter':
1076
1199
  case ' ':
@@ -1083,9 +1206,9 @@ const PdsDatepicker = class {
1083
1206
  return;
1084
1207
  }
1085
1208
  event.preventDefault();
1086
- const target = clampDate(next, this.minDate, this.maxDate);
1209
+ const target = clampDate(this.inView(next), this.minDate, this.maxDate);
1087
1210
  this.showDate(target);
1088
- if (this.range)
1211
+ if (this.isRange)
1089
1212
  this.previewDate = toIsoDate(target);
1090
1213
  this.focusActiveDay = true;
1091
1214
  };
@@ -1096,7 +1219,7 @@ const PdsDatepicker = class {
1096
1219
  this.pickDate(date);
1097
1220
  };
1098
1221
  this.onGridPointerOver = (event) => {
1099
- if (!this.range || !this.draftStart || this.draftEnd)
1222
+ if (!this.isRange || !this.draftStart || this.draftEnd)
1100
1223
  return;
1101
1224
  const date = this.dayFromEvent(event);
1102
1225
  const iso = date && toIsoDate(date);
@@ -1145,19 +1268,28 @@ const PdsDatepicker = class {
1145
1268
  var _a;
1146
1269
  this.text = this.fieldText();
1147
1270
  this.textError = '';
1271
+ const parts = this.parsedValue();
1272
+ // A full date reads as its month and day; keep what's submitted in step with what's shown,
1273
+ // rather than passing through whichever spelling was set until the next pick rewrites it.
1274
+ if (this.monthDay && parts) {
1275
+ const normalized = this.dayValue(parts[0].date);
1276
+ if (this.value !== normalized)
1277
+ this.value = normalized;
1278
+ }
1148
1279
  this.updateFormValue();
1149
1280
  if (!this.isOpen)
1150
1281
  return;
1151
- const parts = this.parsedValue();
1152
1282
  this.loadDraft(parts);
1153
- const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : today();
1283
+ const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : this.now;
1154
1284
  this.showDate(clampDate(day, this.minDate, this.maxDate), true);
1155
1285
  }
1156
1286
  requiredChanged() {
1157
1287
  this.updateFormValue();
1158
1288
  }
1159
1289
  componentWillLoad() {
1160
- this.text = this.fieldText();
1290
+ // Through the same path a later value/context change takes, so an initial full date in
1291
+ // month-day mode submits normalized too, not only after the first pick rewrites it.
1292
+ this.refreshFromValue();
1161
1293
  }
1162
1294
  connectedCallback() {
1163
1295
  if (this.el.attachInternals && !this.internals) {
@@ -1207,10 +1339,30 @@ const PdsDatepicker = class {
1207
1339
  return localeFirstDayOfWeek(this.resolvedLocale);
1208
1340
  }
1209
1341
  get minDate() {
1210
- return parseIsoDate(this.min);
1342
+ return this.monthDay ? null : parseIsoDate(this.min);
1211
1343
  }
1212
1344
  get maxDate() {
1213
- return parseIsoDate(this.max);
1345
+ return this.monthDay ? null : parseIsoDate(this.max);
1346
+ }
1347
+ /** Range mode, which month-day mode turns off. */
1348
+ get isRange() {
1349
+ return this.range && !this.monthDay;
1350
+ }
1351
+ /** The time picker, which month-day mode turns off. */
1352
+ get hasTime() {
1353
+ return this.timePicker && !this.monthDay;
1354
+ }
1355
+ /** Today, or in month-day mode today's month and day. */
1356
+ get now() {
1357
+ return this.monthDay ? inMonthDayYear(today()) : today();
1358
+ }
1359
+ /** In month-day mode, a date moved to the mode's year, as when the calendar wraps past December. */
1360
+ inView(date) {
1361
+ return this.monthDay ? inMonthDayYear(date) : date;
1362
+ }
1363
+ /** One day as the value holds it: `YYYY-MM-DD`, or `--MM-DD` in month-day mode. */
1364
+ dayValue(date) {
1365
+ return this.monthDay ? toMonthDay(date) : toIsoDate(date);
1214
1366
  }
1215
1367
  get isInteractive() {
1216
1368
  return !this.disabled && !this.readonly;
@@ -1218,22 +1370,22 @@ const PdsDatepicker = class {
1218
1370
  /** How many months the calendar shows side by side. */
1219
1371
  get monthCount() {
1220
1372
  const narrow = typeof window !== 'undefined' && window.innerWidth < TWO_MONTHS_MIN_WIDTH;
1221
- return this.range && !narrow ? 2 : 1;
1373
+ return this.isRange && !narrow ? 2 : 1;
1222
1374
  }
1223
1375
  /** Whether picks apply at once, without the footer: `auto-apply` in range mode, without times. */
1224
1376
  get appliesOnPick() {
1225
- return this.range && this.autoApply && !this.timePicker;
1377
+ return this.isRange && this.autoApply && !this.hasTime;
1226
1378
  }
1227
1379
  /** Whether picks wait for Apply: for a range, unless it auto-applies, and for a date with a time. */
1228
1380
  get hasFooter() {
1229
- return (this.range || this.timePicker) && !this.appliesOnPick;
1381
+ return (this.isRange || this.hasTime) && !this.appliesOnPick;
1230
1382
  }
1231
1383
  get resolvedPresets() {
1232
- return this.range ? resolvePresets(this.presets, today()) : [];
1384
+ return this.isRange ? resolvePresets(this.presets, today()) : [];
1233
1385
  }
1234
1386
  /** The committed value, parsed: one date, or a range's start and end. */
1235
1387
  parsedValue(value = this.value) {
1236
- if (this.range) {
1388
+ if (this.isRange) {
1237
1389
  const [start, end, extra] = (value !== null && value !== void 0 ? value : '').split('/');
1238
1390
  const startValue = parseDateTime(start);
1239
1391
  const endValue = parseDateTime(end);
@@ -1241,12 +1393,22 @@ const PdsDatepicker = class {
1241
1393
  ? [startValue, endValue]
1242
1394
  : null;
1243
1395
  }
1396
+ if (this.monthDay) {
1397
+ const date = parseMonthDay(value);
1398
+ return date ? [{ date, time: null }] : null;
1399
+ }
1244
1400
  const single = parseDateTime(value);
1245
1401
  return single ? [single] : null;
1246
1402
  }
1247
1403
  /** The format the field shows and reads, with a time when the time picker is on. */
1248
1404
  get resolvedFormat() {
1249
- return fieldFormat(this.format, this.timePicker, this.timePicker && clockFor(this.resolvedLocale).hour12);
1405
+ var _a, _b;
1406
+ if (this.monthDay) {
1407
+ // A format with a year is the default, or one for full dates: use the locale's month and day.
1408
+ const format = (_b = (_a = this.format) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
1409
+ return format !== '' && !/YYYY/.test(format.replace(/\[[^\]]*\]/g, '')) ? format : monthDayFormat(this.resolvedLocale);
1410
+ }
1411
+ return fieldFormat(this.format, this.hasTime, this.hasTime && clockFor(this.resolvedLocale).hour12);
1250
1412
  }
1251
1413
  /** What the field shows for the committed value. */
1252
1414
  fieldText() {
@@ -1262,20 +1424,24 @@ const PdsDatepicker = class {
1262
1424
  /** Drops a time the time picker doesn't show, or adds the default one it does. */
1263
1425
  withTimePicker(value, fallback = DEFAULT_START_TIME) {
1264
1426
  var _a;
1265
- return { date: value.date, time: this.timePicker ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
1427
+ return { date: value.date, time: this.hasTime ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
1266
1428
  }
1267
1429
  get fieldPlaceholder() {
1268
1430
  if (this.placeholder)
1269
1431
  return this.placeholder;
1432
+ // Month-day tokens, as `MMMM D`, don't read as a hint; an example date does.
1433
+ if (this.monthDay) {
1434
+ return formatWith({ date: parseMonthDay('--12-31'), time: null }, this.resolvedFormat, this.resolvedLocale);
1435
+ }
1270
1436
  const one = this.resolvedFormat;
1271
- return this.range ? `${one}${RANGE_SEPARATOR}${one}` : one;
1437
+ return this.isRange ? `${one}${RANGE_SEPARATOR}${one}` : one;
1272
1438
  }
1273
1439
  /** Loads the committed value into the draft the calendar edits. */
1274
1440
  loadDraft(parts) {
1275
1441
  var _a, _b;
1276
1442
  const [start, end] = parts !== null && parts !== void 0 ? parts : [];
1277
1443
  this.draftStart = start ? toIsoDate(start.date) : '';
1278
- this.draftEnd = this.range && end ? toIsoDate(end.date) : '';
1444
+ this.draftEnd = this.isRange && end ? toIsoDate(end.date) : '';
1279
1445
  this.startTime = (_a = start === null || start === void 0 ? void 0 : start.time) !== null && _a !== void 0 ? _a : DEFAULT_START_TIME;
1280
1446
  this.endTime = (_b = end === null || end === void 0 ? void 0 : end.time) !== null && _b !== void 0 ? _b : DEFAULT_END_TIME;
1281
1447
  this.previewDate = '';
@@ -1291,7 +1457,7 @@ const PdsDatepicker = class {
1291
1457
  const parts = this.parsedValue();
1292
1458
  this.loadDraft(parts);
1293
1459
  this.valueAtOpen = (_a = this.value) !== null && _a !== void 0 ? _a : '';
1294
- const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : today();
1460
+ const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : this.now;
1295
1461
  this.showDate(clampDate(start, this.minDate, this.maxDate), true);
1296
1462
  this.focusActiveDay = focusDay;
1297
1463
  this.isOpen = true;
@@ -1313,7 +1479,8 @@ const PdsDatepicker = class {
1313
1479
  * Makes a day the active one and brings it into view. With two months, a day already on view
1314
1480
  * doesn't move the view; `reset` lines the view up so the day is in the last month shown.
1315
1481
  */
1316
- showDate(date, reset = false) {
1482
+ showDate(day, reset = false) {
1483
+ const date = this.inView(day);
1317
1484
  const first = firstOfMonth(date);
1318
1485
  const view = parseIsoDate(this.viewMonth);
1319
1486
  const lastView = view && addMonths(view, this.monthCount - 1);
@@ -1331,15 +1498,15 @@ const PdsDatepicker = class {
1331
1498
  // A pick in the calendar takes over from typed text, so dismissing afterwards commits nothing,
1332
1499
  // as when nothing was typed.
1333
1500
  this.dropTypedText();
1334
- if (this.range) {
1501
+ if (this.isRange) {
1335
1502
  this.pickRangeDay(date);
1336
1503
  }
1337
- else if (this.timePicker) {
1504
+ else if (this.hasTime) {
1338
1505
  this.draftStart = toIsoDate(date);
1339
1506
  this.activeDate = this.draftStart;
1340
1507
  }
1341
1508
  else {
1342
- this.commitValue(toIsoDate(date));
1509
+ this.commitValue(this.dayValue(date));
1343
1510
  this.closeCalendar(true);
1344
1511
  }
1345
1512
  }
@@ -1383,7 +1550,11 @@ const PdsDatepicker = class {
1383
1550
  }
1384
1551
  /** Commits one date, or a range with its start, end and matching preset. */
1385
1552
  commitParts(parts) {
1386
- if (!this.range) {
1553
+ if (this.monthDay) {
1554
+ this.commitValue(toMonthDay(parts[0].date));
1555
+ return;
1556
+ }
1557
+ if (!this.isRange) {
1387
1558
  this.commitValue(toValueString(parts[0]));
1388
1559
  return;
1389
1560
  }
@@ -1398,7 +1569,7 @@ const PdsDatepicker = class {
1398
1569
  if (!startDate)
1399
1570
  return null;
1400
1571
  const start = this.withTimePicker({ date: startDate, time: this.startTime });
1401
- if (!this.range)
1572
+ if (!this.isRange)
1402
1573
  return [start];
1403
1574
  const end = this.withTimePicker({ date: (_a = parseIsoDate(this.draftEnd)) !== null && _a !== void 0 ? _a : startDate, time: this.endTime });
1404
1575
  // An end time before the start time on the same day ends at the start.
@@ -1524,7 +1695,11 @@ const PdsDatepicker = class {
1524
1695
  readText(text) {
1525
1696
  const format = this.resolvedFormat;
1526
1697
  const locale = this.resolvedLocale;
1527
- const parts = this.range ? parseRangeText(text, format, locale) : [parseFieldText(text, format, locale)];
1698
+ const parts = this.monthDay
1699
+ ? [parseMonthDayText(text, format, locale)]
1700
+ : this.isRange
1701
+ ? parseRangeText(text, format, locale)
1702
+ : [parseFieldText(text, format, locale)];
1528
1703
  if (!parts || parts.some((part) => !part))
1529
1704
  return null;
1530
1705
  return parts.map((part, i) => this.withTimePicker(part, i === 1 ? DEFAULT_END_TIME : DEFAULT_START_TIME));
@@ -1568,17 +1743,17 @@ const PdsDatepicker = class {
1568
1743
  /** Whether the month before (`-1`) or after (`1`) the ones on view has a day in range. */
1569
1744
  canShowMonth(direction) {
1570
1745
  var _a;
1571
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
1746
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
1572
1747
  if (direction < 0)
1573
1748
  return !this.minDate || addDays(view, -1) >= this.minDate;
1574
1749
  return !this.maxDate || addMonths(view, this.monthCount) <= this.maxDate;
1575
1750
  }
1576
1751
  showMonth(event, direction) {
1577
1752
  var _a, _b;
1578
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
1579
- const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : today();
1580
- this.viewMonth = toIsoDate(addMonths(view, direction));
1581
- this.activeDate = toIsoDate(clampDate(addMonths(active, direction), this.minDate, this.maxDate));
1753
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
1754
+ const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : this.now;
1755
+ this.viewMonth = toIsoDate(this.inView(addMonths(view, direction)));
1756
+ this.activeDate = toIsoDate(clampDate(this.inView(addMonths(active, direction)), this.minDate, this.maxDate));
1582
1757
  if (this.canShowMonth(direction)) {
1583
1758
  // Safari doesn't focus a clicked button; without this, focus falls to the page and Escape and
1584
1759
  // the Tab trap stop working.
@@ -1700,7 +1875,7 @@ const PdsDatepicker = class {
1700
1875
  const parts = this.parsedValue();
1701
1876
  if (!parts)
1702
1877
  return this.triggerLabel;
1703
- const dates = parts.map((part) => formatDate(part.date, locale, FULL_DATE));
1878
+ const dates = parts.map((part) => formatDate(part.date, locale, this.monthDay ? MONTH_AND_DAY : FULL_DATE));
1704
1879
  return `${this.triggerLabel}, ${dates.join(RANGE_SEPARATOR)}`;
1705
1880
  }
1706
1881
  /** The range to highlight: the chosen one, or the start to the previewed day while picking the end. */
@@ -1714,15 +1889,15 @@ const PdsDatepicker = class {
1714
1889
  }
1715
1890
  /** The day shown as selected: the draft, which opens as the committed day and follows typing. */
1716
1891
  selectedDay() {
1717
- if (this.range)
1892
+ if (this.isRange)
1718
1893
  return null;
1719
1894
  return parseIsoDate(this.draftStart);
1720
1895
  }
1721
1896
  renderMonthHeading(view, index, locale, headingId) {
1722
- const heading = formatDate(view, locale, { month: 'long', year: 'numeric' });
1897
+ const heading = formatDate(view, locale, this.monthDay ? { month: 'long' } : { month: 'long', year: 'numeric' });
1723
1898
  // Only the first month announces itself as it changes; two months move together.
1724
1899
  const live = index === 0 ? 'polite' : null;
1725
- if (!this.showDropdowns) {
1900
+ if (!this.showDropdowns || this.monthDay) {
1726
1901
  return (h("div", { class: "pds-datepicker__heading", id: headingId, "aria-live": live }, heading));
1727
1902
  }
1728
1903
  const year = view.getUTCFullYear();
@@ -1748,14 +1923,14 @@ const PdsDatepicker = class {
1748
1923
  const min = this.minDate;
1749
1924
  const max = this.maxDate;
1750
1925
  const selected = this.selectedDay();
1751
- const [rangeStart, rangeEnd] = this.range ? this.highlightedRange() : [null, null];
1752
- const now = today();
1753
- const fullDate = dateFormatter(locale, FULL_DATE);
1926
+ const [rangeStart, rangeEnd] = this.isRange ? this.highlightedRange() : [null, null];
1927
+ const now = this.now;
1928
+ const fullDate = dateFormatter(locale, this.monthDay ? MONTH_AND_DAY : FULL_DATE);
1754
1929
  const dayNumber = dateFormatter(locale, { day: 'numeric' });
1755
1930
  const headingId = `${this.baseId}-calendar-heading-${index}`;
1756
1931
  const isFirst = index === 0;
1757
1932
  const isLast = index === this.monthCount - 1;
1758
- return (h("div", { class: "pds-datepicker__month" }, h("div", { class: "pds-datepicker__header" }, isFirst ? (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) }, h("pds-icon", { icon: caretLeft, "aria-hidden": "true" }))) : (h("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index, locale, headingId), isLast ? (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) }, h("pds-icon", { icon: caretRight, "aria-hidden": "true" }))) : (h("span", { class: "pds-datepicker__nav-spacer" }))), 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 }, h("thead", null, h("tr", null, weekdayNames(locale, this.weekStart).map((name) => (h("th", { scope: "col", class: "pds-datepicker__weekday" }, h("span", { "aria-hidden": "true" }, name.short), h("span", { class: "visually-hidden" }, name.long)))))), h("tbody", null, monthWeeks(year, month, this.weekStart).map((week) => (h("tr", null, week.map((date, column) => {
1933
+ return (h("div", { class: "pds-datepicker__month" }, h("div", { class: "pds-datepicker__header" }, isFirst ? (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) }, h("pds-icon", { icon: caretLeft, "aria-hidden": "true" }))) : (h("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index, locale, headingId), isLast ? (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) }, h("pds-icon", { icon: caretRight, "aria-hidden": "true" }))) : (h("span", { class: "pds-datepicker__nav-spacer" }))), 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 && (h("thead", null, h("tr", null, weekdayNames(locale, this.weekStart).map((name) => (h("th", { scope: "col", class: "pds-datepicker__weekday" }, h("span", { "aria-hidden": "true" }, name.short), h("span", { class: "visually-hidden" }, name.long))))))), h("tbody", null, (this.monthDay ? monthDayRows(month) : monthWeeks(year, month, this.weekStart)).map((week) => (h("tr", null, week.map((date, column) => {
1759
1934
  if (!date)
1760
1935
  return h("td", { class: "pds-datepicker__day pds-datepicker__day--empty" });
1761
1936
  const iso = toIsoDate(date);
@@ -1777,14 +1952,14 @@ const PdsDatepicker = class {
1777
1952
  'pds-datepicker__day--today': isToday,
1778
1953
  'pds-datepicker__day--disabled': unavailable,
1779
1954
  }, "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)));
1780
- })))))), this.timePicker && this.timesUnderMonth(index).map((which) => this.renderTime(which, locale))));
1955
+ })))))), this.hasTime && this.timesUnderMonth(index).map((which) => this.renderTime(which, locale))));
1781
1956
  }
1782
1957
  /**
1783
1958
  * The time pickers under a month: the start time under the first and the end time under the
1784
1959
  * second, or both under the one month a narrow range shows.
1785
1960
  */
1786
1961
  timesUnderMonth(index) {
1787
- if (!this.range)
1962
+ if (!this.isRange)
1788
1963
  return ['start'];
1789
1964
  if (this.monthCount === 1)
1790
1965
  return ['start', 'end'];
@@ -1809,7 +1984,7 @@ const PdsDatepicker = class {
1809
1984
  if (this.timesBeforeTyping)
1810
1985
  this.timesBeforeTyping[which === 'start' ? 0 : 1] = time;
1811
1986
  };
1812
- const groupLabel = !this.range ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
1987
+ const groupLabel = !this.isRange ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
1813
1988
  const isPm = hours >= 12;
1814
1989
  const hourOptions = clock.hour12 ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i);
1815
1990
  const shownHour = clock.hour12 ? hours % 12 || 12 : hours;
@@ -1850,7 +2025,7 @@ const PdsDatepicker = class {
1850
2025
  return '';
1851
2026
  const format = this.resolvedFormat;
1852
2027
  const locale = this.resolvedLocale;
1853
- if (this.range && !this.draftEnd)
2028
+ if (this.isRange && !this.draftEnd)
1854
2029
  return `${toFieldText(parts[0], format, locale)}${RANGE_SEPARATOR}`;
1855
2030
  return parts.map((part) => toFieldText(part, format, locale)).join(RANGE_SEPARATOR);
1856
2031
  }
@@ -1861,25 +2036,25 @@ const PdsDatepicker = class {
1861
2036
  }
1862
2037
  renderCalendar(locale) {
1863
2038
  var _a;
1864
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
2039
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
1865
2040
  const months = Array.from({ length: this.monthCount }, (_, i) => addMonths(view, i));
1866
- return (h("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.range }, id: `${this.baseId}-calendar`, role: "dialog",
2041
+ return (h("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.isRange }, id: `${this.baseId}-calendar`, role: "dialog",
1867
2042
  // A click on a part that can't take focus, as the month heading, focuses the dialog
1868
2043
  // rather than dropping focus to the page, which would report blur mid-selection.
1869
- 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(), h("div", { class: "pds-datepicker__panel" }, h("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
2044
+ 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(), h("div", { class: "pds-datepicker__panel" }, h("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
1870
2045
  // With no footer, still announce the start once it's picked, so it's clear an end comes next.
1871
2046
  h("span", { class: "visually-hidden", "aria-live": "polite" }, this.selectionText())))));
1872
2047
  }
1873
2048
  render() {
1874
2049
  const locale = this.resolvedLocale;
1875
- return (h(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 }, 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 && (h("button", {
2050
+ return (h(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 }, 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 && (h("button", {
1876
2051
  // Keyed, as is the calendar button: when Clear appears after a pick, the focused
1877
2052
  // calendar button must stay that button rather than be reused as Clear.
1878
2053
  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,
1879
2054
  // Out of the tab order, as a native search field's clear button is: Tab from a field
1880
2055
  // just emptied would land on it as the change removes it, dropping focus to the page.
1881
2056
  tabindex: "-1", onMouseDown: this.onClearMouseDown, onClick: this.onClearClick
1882
- }, h("pds-icon", { key: '98c325fbba8ea78b2d409bc77099db3206738508', icon: remove, "aria-hidden": "true" }))), 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 }, h("pds-icon", { key: 'cb8483febe1b485058b7bb4271b749981a7747a1', icon: calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
2057
+ }, h("pds-icon", { key: '6a7da726d86654d3598f84b8ea7a8ed244a8e5da', icon: remove, "aria-hidden": "true" }))), 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 }, h("pds-icon", { key: '92af87e66b7bb2e95861b873077a7bfb0268c2e0', icon: calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
1883
2058
  }
1884
2059
  static get formAssociated() { return true; }
1885
2060
  get el() { return getElement(this); }
@@ -1899,6 +2074,9 @@ const PdsDatepicker = class {
1899
2074
  "range": [{
1900
2075
  "contextChanged": 0
1901
2076
  }],
2077
+ "monthDay": [{
2078
+ "contextChanged": 0
2079
+ }],
1902
2080
  "min": [{
1903
2081
  "contextChanged": 0
1904
2082
  }],