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