@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
package/hydrate/index.js CHANGED
@@ -14507,6 +14507,48 @@ function dateFormatter(locale, options) {
14507
14507
  function formatDate(date, locale, options) {
14508
14508
  return dateFormatter(locale, options).format(date);
14509
14509
  }
14510
+ /**
14511
+ * Month-day mode's year. A leap year, so February has its 29th; every date the mode holds is in it,
14512
+ * and the value drops it (`--MM-DD`).
14513
+ */
14514
+ const MONTH_DAY_YEAR = 2000;
14515
+ const MONTH_DAY = /^--(\d{2})-(\d{2})$/;
14516
+ /** The same month and day in month-day mode's year. */
14517
+ function inMonthDayYear(date) {
14518
+ return new Date(Date.UTC(MONTH_DAY_YEAR, date.getUTCMonth(), date.getUTCDate()));
14519
+ }
14520
+ /**
14521
+ * Parses a month and day, `--MM-DD` (ISO 8601 without a year), to a date in month-day mode's year.
14522
+ * A full `YYYY-MM-DD` reads as its month and day. `null` when it isn't a real month and day.
14523
+ */
14524
+ function parseMonthDay(value) {
14525
+ const text = (value !== null && value !== void 0 ? value : '').trim();
14526
+ const match = MONTH_DAY.exec(text);
14527
+ if (match)
14528
+ return parseIsoDate(`${MONTH_DAY_YEAR}-${match[1]}-${match[2]}`);
14529
+ const date = parseIsoDate(text);
14530
+ return date && inMonthDayYear(date);
14531
+ }
14532
+ /** A date's month and day as `--MM-DD`. */
14533
+ function toMonthDay(date) {
14534
+ return `-${toIsoDate(date).slice(4)}`;
14535
+ }
14536
+ /**
14537
+ * A month's days for month-day mode, which has no weekdays: rows of seven from the 1st, with the
14538
+ * last row filled out with `null`.
14539
+ */
14540
+ function monthDayRows(month) {
14541
+ const cells = [];
14542
+ for (let day = 1; day <= daysInMonth(MONTH_DAY_YEAR, month); day++) {
14543
+ cells.push(new Date(Date.UTC(MONTH_DAY_YEAR, month, day)));
14544
+ }
14545
+ while (cells.length % 7 !== 0)
14546
+ cells.push(null);
14547
+ const rows = [];
14548
+ for (let i = 0; i < cells.length; i += 7)
14549
+ rows.push(cells.slice(i, i + 7));
14550
+ return rows;
14551
+ }
14510
14552
 
14511
14553
  /**
14512
14554
  * Where the open calendar lives: a `div` in `document.body`, or in the nearest `<dialog>` so it
@@ -14750,32 +14792,50 @@ const wordsCache = new Map();
14750
14792
  function localeWords(locale) {
14751
14793
  let words = wordsCache.get(locale);
14752
14794
  if (!words) {
14753
- const month = (style) => {
14754
- const format = new Intl.DateTimeFormat(locale, {
14755
- month: style,
14756
- timeZone: 'UTC',
14757
- calendar: 'gregory',
14795
+ const month = (style, withDay) => {
14796
+ const format = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({ month: style }, (withDay ? { day: 'numeric' } : {})), { timeZone: 'UTC', calendar: 'gregory' }));
14797
+ return Array.from({ length: 12 }, (_, m) => {
14798
+ const date = new Date(Date.UTC(2023, m, 1));
14799
+ if (!withDay)
14800
+ return format.format(date);
14801
+ const parts = format.formatToParts(date);
14802
+ const index = parts.findIndex((part) => part.type === 'month');
14803
+ if (index === -1)
14804
+ return format.format(date);
14805
+ // CJK locales split a counter word, as Japanese `月`, into its own literal right after the
14806
+ // month; a literal with no space is that word, not the separator before the day.
14807
+ const suffix = parts[index + 1];
14808
+ return (suffix === null || suffix === void 0 ? void 0 : suffix.type) === 'literal' && !/\s/.test(suffix.value) ? parts[index].value + suffix.value : parts[index].value;
14758
14809
  });
14759
- return Array.from({ length: 12 }, (_, m) => format.format(new Date(Date.UTC(2023, m, 1))));
14760
14810
  };
14761
14811
  const clock = clockFor(locale);
14762
- words = { months: month('long'), shortMonths: month('short'), am: clock.am, pm: clock.pm };
14812
+ words = {
14813
+ months: month('long', false),
14814
+ shortMonths: month('short', false),
14815
+ monthsWithDay: month('long', true),
14816
+ shortMonthsWithDay: month('short', true),
14817
+ am: clock.am,
14818
+ pm: clock.pm,
14819
+ };
14763
14820
  wordsCache.set(locale, words);
14764
14821
  }
14765
14822
  return words;
14766
14823
  }
14824
+ /** Whether a format's tokens (see `tokensOnly`) have a day, so a month name should inflect for one. */
14825
+ const hasDayToken = (tokens) => /D/.test(tokens);
14767
14826
  /** Writes a date in a format of tokens (see `FORMAT_PARTS`), with month names and AM/PM in the locale. */
14768
14827
  function formatWith(value, format, locale = 'en-US') {
14769
14828
  var _a;
14770
14829
  const words = localeWords(locale);
14830
+ const withDay = hasDayToken(tokensOnly(format));
14771
14831
  const [hours, minutes] = splitTime((_a = value.time) !== null && _a !== void 0 ? _a : '00:00');
14772
14832
  const hour12 = hours % 12 || 12;
14773
14833
  const month = value.date.getUTCMonth();
14774
14834
  const period = hours < 12 ? words.am : words.pm;
14775
14835
  const parts = {
14776
14836
  YYYY: pad(value.date.getUTCFullYear(), 4),
14777
- MMMM: words.months[month],
14778
- MMM: words.shortMonths[month],
14837
+ MMMM: withDay ? words.monthsWithDay[month] : words.months[month],
14838
+ MMM: withDay ? words.shortMonthsWithDay[month] : words.shortMonths[month],
14779
14839
  MM: pad(month + 1),
14780
14840
  M: String(month + 1),
14781
14841
  DD: pad(value.date.getUTCDate()),
@@ -14797,17 +14857,23 @@ const alternatives = (words) => `(${words.map(escapeRegExp).join('|')})`;
14797
14857
  /**
14798
14858
  * Reads typed text in a format of tokens, as written by `formatWith`. Leading zeros are optional,
14799
14859
  * spaces are loose, and month names and AM/PM are matched without regard to case, in the locale or
14800
- * in English. Returns `null` for anything that isn't a real date and time in that format.
14860
+ * in English. A format without `YYYY` reads into `year`, and without `year` it reads nothing.
14861
+ * Returns `null` for anything that isn't a real date and time in that format.
14801
14862
  */
14802
- function parseWith(text, format, locale = 'en-US') {
14863
+ function parseWith(text, format, locale = 'en-US', year) {
14803
14864
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
14804
14865
  const words = localeWords(locale);
14805
14866
  const english = localeWords('en-US');
14867
+ const withDay = hasDayToken(tokensOnly(format));
14868
+ const months = withDay ? words.monthsWithDay : words.months;
14869
+ const shortMonths = withDay ? words.shortMonthsWithDay : words.shortMonths;
14870
+ const englishMonths = withDay ? english.monthsWithDay : english.months;
14871
+ const englishShortMonths = withDay ? english.shortMonthsWithDay : english.shortMonths;
14806
14872
  const periods = [words.am, words.pm, english.am, english.pm];
14807
14873
  const patterns = {
14808
14874
  YYYY: '(\\d{4})',
14809
- MMMM: alternatives([...words.months, ...english.months]),
14810
- MMM: alternatives([...words.shortMonths, ...english.shortMonths]),
14875
+ MMMM: alternatives([...months, ...englishMonths]),
14876
+ MMM: alternatives([...shortMonths, ...englishShortMonths]),
14811
14877
  MM: '(\\d{1,2})',
14812
14878
  M: '(\\d{1,2})',
14813
14879
  DD: '(\\d{1,2})',
@@ -14854,7 +14920,7 @@ function parseWith(text, format, locale = 'en-US') {
14854
14920
  const monthIndex = (name, long) => {
14855
14921
  if (name === undefined)
14856
14922
  return undefined;
14857
- const lists = long ? [words.months, english.months] : [words.shortMonths, english.shortMonths];
14923
+ const lists = long ? [months, englishMonths] : [shortMonths, englishShortMonths];
14858
14924
  for (const list of lists) {
14859
14925
  const index = list.findIndex((candidate) => candidate.toLowerCase() === name.toLowerCase());
14860
14926
  if (index >= 0)
@@ -14862,12 +14928,12 @@ function parseWith(text, format, locale = 'en-US') {
14862
14928
  }
14863
14929
  return undefined;
14864
14930
  };
14865
- const year = Number(read.YYYY);
14931
+ const readYear = read.YYYY ? Number(read.YYYY) : year;
14866
14932
  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);
14867
14933
  const day = Number((_d = read.DD) !== null && _d !== void 0 ? _d : read.D);
14868
- if (!read.YYYY || !month || !day)
14934
+ if (readYear === undefined || !month || !day)
14869
14935
  return null;
14870
- const date = parseIsoDate(`${pad(year, 4)}-${pad(month)}-${pad(day)}`);
14936
+ const date = parseIsoDate(`${pad(readYear, 4)}-${pad(month)}-${pad(day)}`);
14871
14937
  if (!date)
14872
14938
  return null;
14873
14939
  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;
@@ -14889,6 +14955,40 @@ function parseFieldText(text, format = DEFAULT_FORMAT, locale = 'en-US') {
14889
14955
  var _a;
14890
14956
  return (_a = parseWith(text, format, locale)) !== null && _a !== void 0 ? _a : parseDateTime(text);
14891
14957
  }
14958
+ /**
14959
+ * The locale's way of writing a month and day, as a format of tokens: `MMMM D` in English, `D MMMM`
14960
+ * in French, `M月D日` in Japanese, or with `numeric`, `M/D` and `D/M`.
14961
+ */
14962
+ function monthDayFormat(locale = 'en-US', month = 'long') {
14963
+ const parts = dateFormatter(locale, { month, day: 'numeric' }).formatToParts(new Date(Date.UTC(MONTH_DAY_YEAR, 11, 31)));
14964
+ return parts
14965
+ .map((part) => {
14966
+ // Some locales write even the long month in digits, as Japanese `12月31日`.
14967
+ if (part.type === 'month')
14968
+ return /^\d+$/.test(part.value) ? 'M' : 'MMMM';
14969
+ if (part.type === 'day')
14970
+ return 'D';
14971
+ // Bracketed, spaces included, only when it has letters a token could be read from, as
14972
+ // Spanish `D[ de ]MMMM`.
14973
+ return /[A-Za-z]/.test(part.value) ? `[${part.value}]` : part.value;
14974
+ })
14975
+ .join('');
14976
+ }
14977
+ /**
14978
+ * Reads a typed month and day, into month-day mode's year: in the format, then as the locale writes
14979
+ * it with a month name or in numbers, with the English order too, then as `--MM-DD` or a full ISO
14980
+ * date, whose year is dropped.
14981
+ */
14982
+ function parseMonthDayText(text, format, locale = 'en-US') {
14983
+ const formats = [format, monthDayFormat(locale), monthDayFormat(locale, 'numeric'), 'MMMM D', 'D MMMM', 'MMM D', 'D MMM'];
14984
+ for (const candidate of formats) {
14985
+ const read = parseWith(text, candidate, locale, MONTH_DAY_YEAR);
14986
+ if (read)
14987
+ return { date: inMonthDayYear(read.date), time: null };
14988
+ }
14989
+ const date = parseMonthDay(text);
14990
+ return date ? { date, time: null } : null;
14991
+ }
14892
14992
  /**
14893
14993
  * Parses a typed range: two dates (with times, optionally) joined by a dash or `to` with spaces
14894
14994
  * around it, as `2026-09-13 – 2026-10-13`, or by `/` when the format doesn't use slashes. The
@@ -14943,6 +15043,8 @@ function clockFor(locale) {
14943
15043
  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}`;
14944
15044
 
14945
15045
  const FULL_DATE = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
15046
+ /** A day in month-day mode, which has no year and so no weekday. */
15047
+ const MONTH_AND_DAY = { month: 'long', day: 'numeric' };
14946
15048
  const DEFAULT_START_TIME = '00:00';
14947
15049
  const DEFAULT_END_TIME = '23:59';
14948
15050
  /** Ids for datepickers without a `component-id`, so their calendars' ids stay unique. */
@@ -14970,7 +15072,8 @@ const dayNumberText = (formatter, date) => { var _a, _b; return (_b = (_a = form
14970
15072
  /**
14971
15073
  * A field for one date, or in range mode a start and end date, with a calendar to pick from.
14972
15074
  * The value is `YYYY-MM-DD`, or `YYYY-MM-DD/YYYY-MM-DD` for a range; with the time picker, each
14973
- * date carries a time, as `YYYY-MM-DDTHH:mm`.
15075
+ * date carries a time, as `YYYY-MM-DDTHH:mm`. In month-day mode, for a date that comes round every
15076
+ * year such as a birthday, it's a month and day with no year, as `--MM-DD`.
14974
15077
  *
14975
15078
  * @part calendar - The open calendar. It's portaled out of the datepicker, so style it with `.pds-datepicker-calendar-portal::part(calendar)`.
14976
15079
  * @part presets - The list of preset ranges in the calendar, styled with `.pds-datepicker-calendar-portal::part(presets)`.
@@ -15029,8 +15132,10 @@ class PdsDatepicker {
15029
15132
  * in the locale), `MM` or `M` (month), `DD` or `D` (day), and with the time picker `HH` or `H`
15030
15133
  * (24-hour), `hh` or `h` (12-hour), `mm` and `A` (AM/PM, in the locale), as `MM/DD/YYYY`,
15031
15134
  * `MMM D, YYYY` or `D.M.YYYY h:mm A`. Put literal text in brackets, as `[Due] D/M/YYYY`. The value
15032
- * and the change event stay ISO (`YYYY-MM-DD`), whatever the format. Typed ISO dates are always
15033
- * accepted too.
15135
+ * and the change event stay ISO (`YYYY-MM-DD`; `--MM-DD` with `month-day`), whatever the format.
15136
+ * Typed ISO dates are always accepted too. With `month-day`, a format without `YYYY` is taken as a
15137
+ * month-and-day format and used as given, rather than reordered for the locale; leave it unset to
15138
+ * follow the locale instead.
15034
15139
  */
15035
15140
  this.format = 'YYYY-MM-DD';
15036
15141
  /**
@@ -15063,6 +15168,15 @@ class PdsDatepicker {
15063
15168
  * Accessible label for the month dropdown, when `show-dropdowns` is set.
15064
15169
  */
15065
15170
  this.monthLabel = 'Month';
15171
+ /**
15172
+ * Picks a month and day with no year, for a date that comes round every year, such as a birthday.
15173
+ * The value is `--MM-DD` (ISO 8601 without a year), the field shows the date as the locale writes
15174
+ * a month and day, and the calendar shows each month without a year or weekdays, with February
15175
+ * always having its 29th. Turns off `range`, `time-picker`, `show-dropdowns`, `min` and `max`, and
15176
+ * has no effect with `auto-apply`, `presets`, `first-day-of-week`, `min-date-message` or
15177
+ * `max-date-message`, which all depend on them.
15178
+ */
15179
+ this.monthDay = false;
15066
15180
  /**
15067
15181
  * Accessible label for the button that shows the next month.
15068
15182
  */
@@ -15111,7 +15225,9 @@ class PdsDatepicker {
15111
15225
  this.triggerLabel = 'Choose date';
15112
15226
  /**
15113
15227
  * The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with
15114
- * the time picker, each date as `YYYY-MM-DDTHH:mm`. An empty string when nothing is selected.
15228
+ * the time picker, each date as `YYYY-MM-DDTHH:mm`; with `month-day`, the month and day with no
15229
+ * year, as `--MM-DD` (a full date is read too, as that month and day, and rewritten to it). An
15230
+ * empty string when nothing is selected.
15115
15231
  */
15116
15232
  this.value = '';
15117
15233
  /**
@@ -15302,17 +15418,17 @@ class PdsDatepicker {
15302
15418
  // The times before the typed text changed them, for a pick in the calendar to go back to.
15303
15419
  (_b = this.timesBeforeTyping) !== null && _b !== void 0 ? _b : (this.timesBeforeTyping = [startTime, endTime]);
15304
15420
  this.loadDraft(parts);
15305
- if (this.timePicker) {
15421
+ if (this.hasTime) {
15306
15422
  // A time picked in the calendar survives typing that doesn't change the time: text with no
15307
15423
  // time, as a date part-way typed, or with the time as committed.
15308
15424
  const format = this.resolvedFormat;
15309
15425
  const locale = this.resolvedLocale;
15310
- const typed = this.range ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
15426
+ const typed = this.isRange ? parseRangeText(this.text, format, locale) : [parseFieldText(this.text, format, locale)];
15311
15427
  const committed = this.parsedValue();
15312
15428
  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); };
15313
15429
  if (keeps(0))
15314
15430
  this.startTime = startTime;
15315
- if (this.range && keeps(1))
15431
+ if (this.isRange && keeps(1))
15316
15432
  this.endTime = endTime;
15317
15433
  }
15318
15434
  this.showDate(parts[parts.length - 1].date);
@@ -15421,7 +15537,8 @@ class PdsDatepicker {
15421
15537
  if (!active)
15422
15538
  return;
15423
15539
  const rtl = getComputedStyle(this.el).direction === 'rtl';
15424
- const column = weekdayColumn(active, this.weekStart);
15540
+ // Month-day mode's rows start on the 1st, not on a weekday.
15541
+ const column = this.monthDay ? (active.getUTCDate() - 1) % 7 : weekdayColumn(active, this.weekStart);
15425
15542
  let next = null;
15426
15543
  switch (event.key) {
15427
15544
  case 'ArrowLeft':
@@ -15439,14 +15556,21 @@ class PdsDatepicker {
15439
15556
  case 'Home':
15440
15557
  next = addDays(active, -column);
15441
15558
  break;
15442
- case 'End':
15443
- next = addDays(active, 6 - column);
15559
+ case 'End': {
15560
+ // Month-day mode's rows don't carry into the next month, so End stops at the month's last day.
15561
+ const lastColumn = this.monthDay
15562
+ ? Math.min(6, daysInMonth(active.getUTCFullYear(), active.getUTCMonth()) - active.getUTCDate() + column)
15563
+ : 6;
15564
+ next = addDays(active, lastColumn - column);
15444
15565
  break;
15566
+ }
15445
15567
  case 'PageUp':
15446
- next = addMonths(active, event.shiftKey ? -12 : -1);
15568
+ // Month-day mode has no year to jump by, so Shift+PageUp stays put rather than passing
15569
+ // through a real, possibly non-leap year and clamping Feb 29 to the 28th.
15570
+ next = event.shiftKey && this.monthDay ? active : addMonths(active, event.shiftKey ? -12 : -1);
15447
15571
  break;
15448
15572
  case 'PageDown':
15449
- next = addMonths(active, event.shiftKey ? 12 : 1);
15573
+ next = event.shiftKey && this.monthDay ? active : addMonths(active, event.shiftKey ? 12 : 1);
15450
15574
  break;
15451
15575
  case 'Enter':
15452
15576
  case ' ':
@@ -15459,9 +15583,9 @@ class PdsDatepicker {
15459
15583
  return;
15460
15584
  }
15461
15585
  event.preventDefault();
15462
- const target = clampDate(next, this.minDate, this.maxDate);
15586
+ const target = clampDate(this.inView(next), this.minDate, this.maxDate);
15463
15587
  this.showDate(target);
15464
- if (this.range)
15588
+ if (this.isRange)
15465
15589
  this.previewDate = toIsoDate(target);
15466
15590
  this.focusActiveDay = true;
15467
15591
  };
@@ -15472,7 +15596,7 @@ class PdsDatepicker {
15472
15596
  this.pickDate(date);
15473
15597
  };
15474
15598
  this.onGridPointerOver = (event) => {
15475
- if (!this.range || !this.draftStart || this.draftEnd)
15599
+ if (!this.isRange || !this.draftStart || this.draftEnd)
15476
15600
  return;
15477
15601
  const date = this.dayFromEvent(event);
15478
15602
  const iso = date && toIsoDate(date);
@@ -15521,19 +15645,28 @@ class PdsDatepicker {
15521
15645
  var _a;
15522
15646
  this.text = this.fieldText();
15523
15647
  this.textError = '';
15648
+ const parts = this.parsedValue();
15649
+ // A full date reads as its month and day; keep what's submitted in step with what's shown,
15650
+ // rather than passing through whichever spelling was set until the next pick rewrites it.
15651
+ if (this.monthDay && parts) {
15652
+ const normalized = this.dayValue(parts[0].date);
15653
+ if (this.value !== normalized)
15654
+ this.value = normalized;
15655
+ }
15524
15656
  this.updateFormValue();
15525
15657
  if (!this.isOpen)
15526
15658
  return;
15527
- const parts = this.parsedValue();
15528
15659
  this.loadDraft(parts);
15529
- const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : today();
15660
+ const day = (_a = (parts && parts[parts.length - 1].date)) !== null && _a !== void 0 ? _a : this.now;
15530
15661
  this.showDate(clampDate(day, this.minDate, this.maxDate), true);
15531
15662
  }
15532
15663
  requiredChanged() {
15533
15664
  this.updateFormValue();
15534
15665
  }
15535
15666
  componentWillLoad() {
15536
- this.text = this.fieldText();
15667
+ // Through the same path a later value/context change takes, so an initial full date in
15668
+ // month-day mode submits normalized too, not only after the first pick rewrites it.
15669
+ this.refreshFromValue();
15537
15670
  }
15538
15671
  connectedCallback() {
15539
15672
  if (this.el.attachInternals && !this.internals) {
@@ -15583,10 +15716,30 @@ class PdsDatepicker {
15583
15716
  return localeFirstDayOfWeek(this.resolvedLocale);
15584
15717
  }
15585
15718
  get minDate() {
15586
- return parseIsoDate(this.min);
15719
+ return this.monthDay ? null : parseIsoDate(this.min);
15587
15720
  }
15588
15721
  get maxDate() {
15589
- return parseIsoDate(this.max);
15722
+ return this.monthDay ? null : parseIsoDate(this.max);
15723
+ }
15724
+ /** Range mode, which month-day mode turns off. */
15725
+ get isRange() {
15726
+ return this.range && !this.monthDay;
15727
+ }
15728
+ /** The time picker, which month-day mode turns off. */
15729
+ get hasTime() {
15730
+ return this.timePicker && !this.monthDay;
15731
+ }
15732
+ /** Today, or in month-day mode today's month and day. */
15733
+ get now() {
15734
+ return this.monthDay ? inMonthDayYear(today()) : today();
15735
+ }
15736
+ /** In month-day mode, a date moved to the mode's year, as when the calendar wraps past December. */
15737
+ inView(date) {
15738
+ return this.monthDay ? inMonthDayYear(date) : date;
15739
+ }
15740
+ /** One day as the value holds it: `YYYY-MM-DD`, or `--MM-DD` in month-day mode. */
15741
+ dayValue(date) {
15742
+ return this.monthDay ? toMonthDay(date) : toIsoDate(date);
15590
15743
  }
15591
15744
  get isInteractive() {
15592
15745
  return !this.disabled && !this.readonly;
@@ -15594,22 +15747,22 @@ class PdsDatepicker {
15594
15747
  /** How many months the calendar shows side by side. */
15595
15748
  get monthCount() {
15596
15749
  const narrow = typeof window !== 'undefined' && window.innerWidth < TWO_MONTHS_MIN_WIDTH;
15597
- return this.range && !narrow ? 2 : 1;
15750
+ return this.isRange && !narrow ? 2 : 1;
15598
15751
  }
15599
15752
  /** Whether picks apply at once, without the footer: `auto-apply` in range mode, without times. */
15600
15753
  get appliesOnPick() {
15601
- return this.range && this.autoApply && !this.timePicker;
15754
+ return this.isRange && this.autoApply && !this.hasTime;
15602
15755
  }
15603
15756
  /** Whether picks wait for Apply: for a range, unless it auto-applies, and for a date with a time. */
15604
15757
  get hasFooter() {
15605
- return (this.range || this.timePicker) && !this.appliesOnPick;
15758
+ return (this.isRange || this.hasTime) && !this.appliesOnPick;
15606
15759
  }
15607
15760
  get resolvedPresets() {
15608
- return this.range ? resolvePresets(this.presets, today()) : [];
15761
+ return this.isRange ? resolvePresets(this.presets, today()) : [];
15609
15762
  }
15610
15763
  /** The committed value, parsed: one date, or a range's start and end. */
15611
15764
  parsedValue(value = this.value) {
15612
- if (this.range) {
15765
+ if (this.isRange) {
15613
15766
  const [start, end, extra] = (value !== null && value !== void 0 ? value : '').split('/');
15614
15767
  const startValue = parseDateTime(start);
15615
15768
  const endValue = parseDateTime(end);
@@ -15617,12 +15770,22 @@ class PdsDatepicker {
15617
15770
  ? [startValue, endValue]
15618
15771
  : null;
15619
15772
  }
15773
+ if (this.monthDay) {
15774
+ const date = parseMonthDay(value);
15775
+ return date ? [{ date, time: null }] : null;
15776
+ }
15620
15777
  const single = parseDateTime(value);
15621
15778
  return single ? [single] : null;
15622
15779
  }
15623
15780
  /** The format the field shows and reads, with a time when the time picker is on. */
15624
15781
  get resolvedFormat() {
15625
- return fieldFormat(this.format, this.timePicker, this.timePicker && clockFor(this.resolvedLocale).hour12);
15782
+ var _a, _b;
15783
+ if (this.monthDay) {
15784
+ // A format with a year is the default, or one for full dates: use the locale's month and day.
15785
+ const format = (_b = (_a = this.format) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
15786
+ return format !== '' && !/YYYY/.test(format.replace(/\[[^\]]*\]/g, '')) ? format : monthDayFormat(this.resolvedLocale);
15787
+ }
15788
+ return fieldFormat(this.format, this.hasTime, this.hasTime && clockFor(this.resolvedLocale).hour12);
15626
15789
  }
15627
15790
  /** What the field shows for the committed value. */
15628
15791
  fieldText() {
@@ -15638,20 +15801,24 @@ class PdsDatepicker {
15638
15801
  /** Drops a time the time picker doesn't show, or adds the default one it does. */
15639
15802
  withTimePicker(value, fallback = DEFAULT_START_TIME) {
15640
15803
  var _a;
15641
- return { date: value.date, time: this.timePicker ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
15804
+ return { date: value.date, time: this.hasTime ? ((_a = value.time) !== null && _a !== void 0 ? _a : fallback) : null };
15642
15805
  }
15643
15806
  get fieldPlaceholder() {
15644
15807
  if (this.placeholder)
15645
15808
  return this.placeholder;
15809
+ // Month-day tokens, as `MMMM D`, don't read as a hint; an example date does.
15810
+ if (this.monthDay) {
15811
+ return formatWith({ date: parseMonthDay('--12-31'), time: null }, this.resolvedFormat, this.resolvedLocale);
15812
+ }
15646
15813
  const one = this.resolvedFormat;
15647
- return this.range ? `${one}${RANGE_SEPARATOR}${one}` : one;
15814
+ return this.isRange ? `${one}${RANGE_SEPARATOR}${one}` : one;
15648
15815
  }
15649
15816
  /** Loads the committed value into the draft the calendar edits. */
15650
15817
  loadDraft(parts) {
15651
15818
  var _a, _b;
15652
15819
  const [start, end] = parts !== null && parts !== void 0 ? parts : [];
15653
15820
  this.draftStart = start ? toIsoDate(start.date) : '';
15654
- this.draftEnd = this.range && end ? toIsoDate(end.date) : '';
15821
+ this.draftEnd = this.isRange && end ? toIsoDate(end.date) : '';
15655
15822
  this.startTime = (_a = start === null || start === void 0 ? void 0 : start.time) !== null && _a !== void 0 ? _a : DEFAULT_START_TIME;
15656
15823
  this.endTime = (_b = end === null || end === void 0 ? void 0 : end.time) !== null && _b !== void 0 ? _b : DEFAULT_END_TIME;
15657
15824
  this.previewDate = '';
@@ -15667,7 +15834,7 @@ class PdsDatepicker {
15667
15834
  const parts = this.parsedValue();
15668
15835
  this.loadDraft(parts);
15669
15836
  this.valueAtOpen = (_a = this.value) !== null && _a !== void 0 ? _a : '';
15670
- const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : today();
15837
+ const start = (_b = (parts && parts[parts.length - 1].date)) !== null && _b !== void 0 ? _b : this.now;
15671
15838
  this.showDate(clampDate(start, this.minDate, this.maxDate), true);
15672
15839
  this.focusActiveDay = focusDay;
15673
15840
  this.isOpen = true;
@@ -15689,7 +15856,8 @@ class PdsDatepicker {
15689
15856
  * Makes a day the active one and brings it into view. With two months, a day already on view
15690
15857
  * doesn't move the view; `reset` lines the view up so the day is in the last month shown.
15691
15858
  */
15692
- showDate(date, reset = false) {
15859
+ showDate(day, reset = false) {
15860
+ const date = this.inView(day);
15693
15861
  const first = firstOfMonth(date);
15694
15862
  const view = parseIsoDate(this.viewMonth);
15695
15863
  const lastView = view && addMonths(view, this.monthCount - 1);
@@ -15707,15 +15875,15 @@ class PdsDatepicker {
15707
15875
  // A pick in the calendar takes over from typed text, so dismissing afterwards commits nothing,
15708
15876
  // as when nothing was typed.
15709
15877
  this.dropTypedText();
15710
- if (this.range) {
15878
+ if (this.isRange) {
15711
15879
  this.pickRangeDay(date);
15712
15880
  }
15713
- else if (this.timePicker) {
15881
+ else if (this.hasTime) {
15714
15882
  this.draftStart = toIsoDate(date);
15715
15883
  this.activeDate = this.draftStart;
15716
15884
  }
15717
15885
  else {
15718
- this.commitValue(toIsoDate(date));
15886
+ this.commitValue(this.dayValue(date));
15719
15887
  this.closeCalendar(true);
15720
15888
  }
15721
15889
  }
@@ -15759,7 +15927,11 @@ class PdsDatepicker {
15759
15927
  }
15760
15928
  /** Commits one date, or a range with its start, end and matching preset. */
15761
15929
  commitParts(parts) {
15762
- if (!this.range) {
15930
+ if (this.monthDay) {
15931
+ this.commitValue(toMonthDay(parts[0].date));
15932
+ return;
15933
+ }
15934
+ if (!this.isRange) {
15763
15935
  this.commitValue(toValueString(parts[0]));
15764
15936
  return;
15765
15937
  }
@@ -15774,7 +15946,7 @@ class PdsDatepicker {
15774
15946
  if (!startDate)
15775
15947
  return null;
15776
15948
  const start = this.withTimePicker({ date: startDate, time: this.startTime });
15777
- if (!this.range)
15949
+ if (!this.isRange)
15778
15950
  return [start];
15779
15951
  const end = this.withTimePicker({ date: (_a = parseIsoDate(this.draftEnd)) !== null && _a !== void 0 ? _a : startDate, time: this.endTime });
15780
15952
  // An end time before the start time on the same day ends at the start.
@@ -15900,7 +16072,11 @@ class PdsDatepicker {
15900
16072
  readText(text) {
15901
16073
  const format = this.resolvedFormat;
15902
16074
  const locale = this.resolvedLocale;
15903
- const parts = this.range ? parseRangeText(text, format, locale) : [parseFieldText(text, format, locale)];
16075
+ const parts = this.monthDay
16076
+ ? [parseMonthDayText(text, format, locale)]
16077
+ : this.isRange
16078
+ ? parseRangeText(text, format, locale)
16079
+ : [parseFieldText(text, format, locale)];
15904
16080
  if (!parts || parts.some((part) => !part))
15905
16081
  return null;
15906
16082
  return parts.map((part, i) => this.withTimePicker(part, i === 1 ? DEFAULT_END_TIME : DEFAULT_START_TIME));
@@ -15944,17 +16120,17 @@ class PdsDatepicker {
15944
16120
  /** Whether the month before (`-1`) or after (`1`) the ones on view has a day in range. */
15945
16121
  canShowMonth(direction) {
15946
16122
  var _a;
15947
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
16123
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
15948
16124
  if (direction < 0)
15949
16125
  return !this.minDate || addDays(view, -1) >= this.minDate;
15950
16126
  return !this.maxDate || addMonths(view, this.monthCount) <= this.maxDate;
15951
16127
  }
15952
16128
  showMonth(event, direction) {
15953
16129
  var _a, _b;
15954
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
15955
- const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : today();
15956
- this.viewMonth = toIsoDate(addMonths(view, direction));
15957
- this.activeDate = toIsoDate(clampDate(addMonths(active, direction), this.minDate, this.maxDate));
16130
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
16131
+ const active = (_b = parseIsoDate(this.activeDate)) !== null && _b !== void 0 ? _b : this.now;
16132
+ this.viewMonth = toIsoDate(this.inView(addMonths(view, direction)));
16133
+ this.activeDate = toIsoDate(clampDate(this.inView(addMonths(active, direction)), this.minDate, this.maxDate));
15958
16134
  if (this.canShowMonth(direction)) {
15959
16135
  // Safari doesn't focus a clicked button; without this, focus falls to the page and Escape and
15960
16136
  // the Tab trap stop working.
@@ -16076,7 +16252,7 @@ class PdsDatepicker {
16076
16252
  const parts = this.parsedValue();
16077
16253
  if (!parts)
16078
16254
  return this.triggerLabel;
16079
- const dates = parts.map((part) => formatDate(part.date, locale, FULL_DATE));
16255
+ const dates = parts.map((part) => formatDate(part.date, locale, this.monthDay ? MONTH_AND_DAY : FULL_DATE));
16080
16256
  return `${this.triggerLabel}, ${dates.join(RANGE_SEPARATOR)}`;
16081
16257
  }
16082
16258
  /** The range to highlight: the chosen one, or the start to the previewed day while picking the end. */
@@ -16090,15 +16266,15 @@ class PdsDatepicker {
16090
16266
  }
16091
16267
  /** The day shown as selected: the draft, which opens as the committed day and follows typing. */
16092
16268
  selectedDay() {
16093
- if (this.range)
16269
+ if (this.isRange)
16094
16270
  return null;
16095
16271
  return parseIsoDate(this.draftStart);
16096
16272
  }
16097
16273
  renderMonthHeading(view, index, locale, headingId) {
16098
- const heading = formatDate(view, locale, { month: 'long', year: 'numeric' });
16274
+ const heading = formatDate(view, locale, this.monthDay ? { month: 'long' } : { month: 'long', year: 'numeric' });
16099
16275
  // Only the first month announces itself as it changes; two months move together.
16100
16276
  const live = index === 0 ? 'polite' : null;
16101
- if (!this.showDropdowns) {
16277
+ if (!this.showDropdowns || this.monthDay) {
16102
16278
  return (hAsync("div", { class: "pds-datepicker__heading", id: headingId, "aria-live": live }, heading));
16103
16279
  }
16104
16280
  const year = view.getUTCFullYear();
@@ -16124,14 +16300,14 @@ class PdsDatepicker {
16124
16300
  const min = this.minDate;
16125
16301
  const max = this.maxDate;
16126
16302
  const selected = this.selectedDay();
16127
- const [rangeStart, rangeEnd] = this.range ? this.highlightedRange() : [null, null];
16128
- const now = today();
16129
- const fullDate = dateFormatter(locale, FULL_DATE);
16303
+ const [rangeStart, rangeEnd] = this.isRange ? this.highlightedRange() : [null, null];
16304
+ const now = this.now;
16305
+ const fullDate = dateFormatter(locale, this.monthDay ? MONTH_AND_DAY : FULL_DATE);
16130
16306
  const dayNumber = dateFormatter(locale, { day: 'numeric' });
16131
16307
  const headingId = `${this.baseId}-calendar-heading-${index}`;
16132
16308
  const isFirst = index === 0;
16133
16309
  const isLast = index === this.monthCount - 1;
16134
- return (hAsync("div", { class: "pds-datepicker__month" }, hAsync("div", { class: "pds-datepicker__header" }, isFirst ? (hAsync("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) }, hAsync("pds-icon", { icon: caretLeft, "aria-hidden": "true" }))) : (hAsync("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index, locale, headingId), isLast ? (hAsync("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) }, hAsync("pds-icon", { icon: caretRight, "aria-hidden": "true" }))) : (hAsync("span", { class: "pds-datepicker__nav-spacer" }))), hAsync("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 }, hAsync("thead", null, hAsync("tr", null, weekdayNames(locale, this.weekStart).map((name) => (hAsync("th", { scope: "col", class: "pds-datepicker__weekday" }, hAsync("span", { "aria-hidden": "true" }, name.short), hAsync("span", { class: "visually-hidden" }, name.long)))))), hAsync("tbody", null, monthWeeks(year, month, this.weekStart).map((week) => (hAsync("tr", null, week.map((date, column) => {
16310
+ return (hAsync("div", { class: "pds-datepicker__month" }, hAsync("div", { class: "pds-datepicker__header" }, isFirst ? (hAsync("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) }, hAsync("pds-icon", { icon: caretLeft, "aria-hidden": "true" }))) : (hAsync("span", { class: "pds-datepicker__nav-spacer" })), this.renderMonthHeading(view, index, locale, headingId), isLast ? (hAsync("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) }, hAsync("pds-icon", { icon: caretRight, "aria-hidden": "true" }))) : (hAsync("span", { class: "pds-datepicker__nav-spacer" }))), hAsync("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 && (hAsync("thead", null, hAsync("tr", null, weekdayNames(locale, this.weekStart).map((name) => (hAsync("th", { scope: "col", class: "pds-datepicker__weekday" }, hAsync("span", { "aria-hidden": "true" }, name.short), hAsync("span", { class: "visually-hidden" }, name.long))))))), hAsync("tbody", null, (this.monthDay ? monthDayRows(month) : monthWeeks(year, month, this.weekStart)).map((week) => (hAsync("tr", null, week.map((date, column) => {
16135
16311
  if (!date)
16136
16312
  return hAsync("td", { class: "pds-datepicker__day pds-datepicker__day--empty" });
16137
16313
  const iso = toIsoDate(date);
@@ -16153,14 +16329,14 @@ class PdsDatepicker {
16153
16329
  'pds-datepicker__day--today': isToday,
16154
16330
  'pds-datepicker__day--disabled': unavailable,
16155
16331
  }, "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)));
16156
- })))))), this.timePicker && this.timesUnderMonth(index).map((which) => this.renderTime(which, locale))));
16332
+ })))))), this.hasTime && this.timesUnderMonth(index).map((which) => this.renderTime(which, locale))));
16157
16333
  }
16158
16334
  /**
16159
16335
  * The time pickers under a month: the start time under the first and the end time under the
16160
16336
  * second, or both under the one month a narrow range shows.
16161
16337
  */
16162
16338
  timesUnderMonth(index) {
16163
- if (!this.range)
16339
+ if (!this.isRange)
16164
16340
  return ['start'];
16165
16341
  if (this.monthCount === 1)
16166
16342
  return ['start', 'end'];
@@ -16185,7 +16361,7 @@ class PdsDatepicker {
16185
16361
  if (this.timesBeforeTyping)
16186
16362
  this.timesBeforeTyping[which === 'start' ? 0 : 1] = time;
16187
16363
  };
16188
- const groupLabel = !this.range ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
16364
+ const groupLabel = !this.isRange ? this.timeLabel : which === 'start' ? this.startTimeLabel : this.endTimeLabel;
16189
16365
  const isPm = hours >= 12;
16190
16366
  const hourOptions = clock.hour12 ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i);
16191
16367
  const shownHour = clock.hour12 ? hours % 12 || 12 : hours;
@@ -16226,7 +16402,7 @@ class PdsDatepicker {
16226
16402
  return '';
16227
16403
  const format = this.resolvedFormat;
16228
16404
  const locale = this.resolvedLocale;
16229
- if (this.range && !this.draftEnd)
16405
+ if (this.isRange && !this.draftEnd)
16230
16406
  return `${toFieldText(parts[0], format, locale)}${RANGE_SEPARATOR}`;
16231
16407
  return parts.map((part) => toFieldText(part, format, locale)).join(RANGE_SEPARATOR);
16232
16408
  }
@@ -16237,25 +16413,25 @@ class PdsDatepicker {
16237
16413
  }
16238
16414
  renderCalendar(locale) {
16239
16415
  var _a;
16240
- const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : today();
16416
+ const view = (_a = parseIsoDate(this.viewMonth)) !== null && _a !== void 0 ? _a : this.now;
16241
16417
  const months = Array.from({ length: this.monthCount }, (_, i) => addMonths(view, i));
16242
- return (hAsync("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.range }, id: `${this.baseId}-calendar`, role: "dialog",
16418
+ return (hAsync("div", { class: { 'pds-datepicker__dialog': true, 'pds-datepicker__dialog--range': this.isRange }, id: `${this.baseId}-calendar`, role: "dialog",
16243
16419
  // A click on a part that can't take focus, as the month heading, focuses the dialog
16244
16420
  // rather than dropping focus to the page, which would report blur mid-selection.
16245
- 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(), hAsync("div", { class: "pds-datepicker__panel" }, hAsync("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
16421
+ 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(), hAsync("div", { class: "pds-datepicker__panel" }, hAsync("div", { class: "pds-datepicker__months" }, months.map((month, index) => this.renderMonth(month, index, locale))), this.hasFooter && this.renderFooter(), this.appliesOnPick && (
16246
16422
  // With no footer, still announce the start once it's picked, so it's clear an end comes next.
16247
16423
  hAsync("span", { class: "visually-hidden", "aria-live": "polite" }, this.selectionText())))));
16248
16424
  }
16249
16425
  render() {
16250
16426
  const locale = this.resolvedLocale;
16251
- return (hAsync(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 }, hAsync("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 && (hAsync("button", {
16427
+ return (hAsync(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 }, hAsync("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 && (hAsync("button", {
16252
16428
  // Keyed, as is the calendar button: when Clear appears after a pick, the focused
16253
16429
  // calendar button must stay that button rather than be reused as Clear.
16254
16430
  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,
16255
16431
  // Out of the tab order, as a native search field's clear button is: Tab from a field
16256
16432
  // just emptied would land on it as the change removes it, dropping focus to the page.
16257
16433
  tabindex: "-1", onMouseDown: this.onClearMouseDown, onClick: this.onClearClick
16258
- }, hAsync("pds-icon", { key: '98c325fbba8ea78b2d409bc77099db3206738508', icon: remove, "aria-hidden": "true" }))), hAsync("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 }, hAsync("pds-icon", { key: 'cb8483febe1b485058b7bb4271b749981a7747a1', icon: calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
16434
+ }, hAsync("pds-icon", { key: '6a7da726d86654d3598f84b8ea7a8ed244a8e5da', icon: remove, "aria-hidden": "true" }))), hAsync("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 }, hAsync("pds-icon", { key: '92af87e66b7bb2e95861b873077a7bfb0268c2e0', icon: calendarSimple, "aria-hidden": "true" }))), this.isOpen && this.renderCalendar(locale)));
16259
16435
  }
16260
16436
  static get formAssociated() { return true; }
16261
16437
  get el() { return getElement(this); }
@@ -16275,6 +16451,9 @@ class PdsDatepicker {
16275
16451
  "range": [{
16276
16452
  "contextChanged": 0
16277
16453
  }],
16454
+ "monthDay": [{
16455
+ "contextChanged": 0
16456
+ }],
16278
16457
  "min": [{
16279
16458
  "contextChanged": 0
16280
16459
  }],
@@ -16326,6 +16505,7 @@ class PdsDatepicker {
16326
16505
  "minuteIncrement": [2, "minute-increment"],
16327
16506
  "minuteLabel": [1, "minute-label"],
16328
16507
  "monthLabel": [1, "month-label"],
16508
+ "monthDay": [4, "month-day"],
16329
16509
  "name": [1],
16330
16510
  "nextMonthLabel": [1, "next-month-label"],
16331
16511
  "periodLabel": [1, "period-label"],