@pine-ds/core 4.9.1 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/components/pds-checkbox2.js +1 -1
  2. package/components/pds-chip2.js +1 -1
  3. package/components/pds-combobox.js +2 -2
  4. package/components/pds-datepicker.js +1 -1
  5. package/components/pds-dropdown-menu.js +1 -1
  6. package/components/truncation-tooltip.js +1 -1
  7. package/dist/cjs/loader.cjs.js +1 -1
  8. package/dist/cjs/pds-checkbox.cjs.entry.js +1 -1
  9. package/dist/cjs/pds-chip.cjs.entry.js +5 -3
  10. package/dist/cjs/pds-combobox.cjs.entry.js +101 -15
  11. package/dist/cjs/pds-copytext.cjs.entry.js +1 -1
  12. package/dist/cjs/pds-datepicker.cjs.entry.js +253 -75
  13. package/dist/cjs/pds-dropdown-menu.cjs.entry.js +58 -46
  14. package/dist/cjs/pds-table-cell.cjs.entry.js +1 -1
  15. package/dist/cjs/pds-text.cjs.entry.js +1 -1
  16. package/dist/cjs/pine-core.cjs.js +1 -1
  17. package/dist/cjs/{truncation-tooltip-DdB78RIe.js → truncation-tooltip-Bikp7n9I.js} +1 -1
  18. package/dist/collection/components/pds-checkbox/pds-checkbox.css +20 -14
  19. package/dist/collection/components/pds-chip/pds-chip.js +4 -2
  20. package/dist/collection/components/pds-combobox/grouped-options-panel.js +39 -1
  21. package/dist/collection/components/pds-combobox/pds-combobox.css +37 -1
  22. package/dist/collection/components/pds-combobox/pds-combobox.js +62 -14
  23. package/dist/collection/components/pds-combobox/stories/pds-combobox.stories.js +48 -0
  24. package/dist/collection/components/pds-datepicker/date-utils.js +42 -0
  25. package/dist/collection/components/pds-datepicker/pds-datepicker.js +163 -64
  26. package/dist/collection/components/pds-datepicker/stories/pds-datepicker.stories.js +11 -0
  27. package/dist/collection/components/pds-datepicker/value.js +76 -18
  28. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +64 -46
  29. package/dist/collection/utils/truncation-tooltip.js +1 -1
  30. package/dist/docs.json +42 -7
  31. package/dist/esm/loader.js +1 -1
  32. package/dist/esm/pds-checkbox.entry.js +1 -1
  33. package/dist/esm/pds-chip.entry.js +5 -3
  34. package/dist/esm/pds-combobox.entry.js +101 -15
  35. package/dist/esm/pds-copytext.entry.js +1 -1
  36. package/dist/esm/pds-datepicker.entry.js +253 -75
  37. package/dist/esm/pds-dropdown-menu.entry.js +58 -46
  38. package/dist/esm/pds-table-cell.entry.js +1 -1
  39. package/dist/esm/pds-text.entry.js +1 -1
  40. package/dist/esm/pine-core.js +1 -1
  41. package/dist/esm/{truncation-tooltip-CRSdYzpA.js → truncation-tooltip-DBcPipr9.js} +1 -1
  42. package/dist/esm-es5/loader.js +1 -1
  43. package/dist/esm-es5/pds-checkbox.entry.js +1 -1
  44. package/dist/esm-es5/pds-chip.entry.js +1 -1
  45. package/dist/esm-es5/pds-combobox.entry.js +1 -1
  46. package/dist/esm-es5/pds-copytext.entry.js +1 -1
  47. package/dist/esm-es5/pds-datepicker.entry.js +1 -1
  48. package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
  49. package/dist/esm-es5/pds-table-cell.entry.js +1 -1
  50. package/dist/esm-es5/pds-text.entry.js +1 -1
  51. package/dist/esm-es5/pine-core.js +1 -1
  52. package/dist/esm-es5/truncation-tooltip-DBcPipr9.js +1 -0
  53. package/dist/pine-core/p-0b7b3fc2.system.entry.js +3 -0
  54. package/dist/pine-core/{p-9a776443.entry.js → p-124a0a24.entry.js} +1 -1
  55. package/dist/pine-core/p-128c04e7.entry.js +1 -0
  56. package/dist/pine-core/p-3359a935.entry.js +1 -0
  57. package/dist/pine-core/{p-3d34b967.system.entry.js → p-3a1dc752.system.entry.js} +1 -1
  58. package/dist/pine-core/{p-20079552.system.entry.js → p-639acef8.system.entry.js} +1 -1
  59. package/dist/pine-core/{p-beb7a430.entry.js → p-97356d4b.entry.js} +1 -1
  60. package/dist/pine-core/p-BZVUD2on.js +1 -0
  61. package/dist/pine-core/p-Dj-z_ugg.system.js +1 -0
  62. package/dist/pine-core/p-XK45dzWR.system.js +1 -1
  63. package/dist/pine-core/{p-4d1a8803.entry.js → p-afc2f165.entry.js} +1 -1
  64. package/dist/pine-core/p-c6a9e62c.system.entry.js +1 -0
  65. package/dist/pine-core/p-d4968f9d.system.entry.js +1 -0
  66. package/dist/pine-core/p-da8b864a.entry.js +2 -0
  67. package/dist/pine-core/p-dd7ded84.entry.js +1 -0
  68. package/dist/pine-core/p-de455073.system.entry.js +1 -0
  69. package/dist/pine-core/{p-1cc39908.system.entry.js → p-eb56af4a.system.entry.js} +1 -1
  70. package/dist/pine-core/p-f354dbac.entry.js +1 -0
  71. package/dist/pine-core/{p-93fd7dd8.system.entry.js → p-f834cb07.system.entry.js} +1 -1
  72. package/dist/pine-core/pine-core.esm.js +1 -1
  73. package/dist/types/components/pds-combobox/combobox-interface.d.ts +26 -0
  74. package/dist/types/components/pds-combobox/grouped-options-panel.d.ts +17 -0
  75. package/dist/types/components/pds-combobox/pds-combobox.d.ts +12 -2
  76. package/dist/types/components/pds-datepicker/date-utils.d.ts +19 -0
  77. package/dist/types/components/pds-datepicker/datepicker-interface.d.ts +2 -1
  78. package/dist/types/components/pds-datepicker/pds-datepicker.d.ts +30 -5
  79. package/dist/types/components/pds-datepicker/value.d.ts +14 -2
  80. package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu.d.ts +4 -0
  81. package/dist/types/components.d.ts +25 -10
  82. package/dist/types/utils/truncation-tooltip.d.ts +1 -1
  83. package/dist/vscode.html-data.json +11 -4
  84. package/hydrate/index.js +422 -142
  85. package/hydrate/index.mjs +422 -142
  86. package/package.json +2 -2
  87. package/dist/esm-es5/truncation-tooltip-CRSdYzpA.js +0 -1
  88. package/dist/pine-core/p-37718637.entry.js +0 -1
  89. package/dist/pine-core/p-383d84f4.entry.js +0 -2
  90. package/dist/pine-core/p-3e30a761.entry.js +0 -1
  91. package/dist/pine-core/p-54f1e86f.system.entry.js +0 -1
  92. package/dist/pine-core/p-73efb1ed.system.entry.js +0 -1
  93. package/dist/pine-core/p-8436cbfb.entry.js +0 -1
  94. package/dist/pine-core/p-DQyF6v9Q.system.js +0 -1
  95. package/dist/pine-core/p-DwQ5Tjis.js +0 -1
  96. package/dist/pine-core/p-b3d4f97a.system.entry.js +0 -3
  97. package/dist/pine-core/p-d47b2a6a.system.entry.js +0 -1
  98. package/dist/pine-core/p-f018af99.entry.js +0 -1
@@ -1,4 +1,4 @@
1
- import { parseIsoDate, toIsoDate } from "./date-utils";
1
+ import { MONTH_DAY_YEAR, dateFormatter, inMonthDayYear, parseIsoDate, parseMonthDay, toIsoDate } from "./date-utils";
2
2
  const DATE_TIME = /^(\d{4}-\d{2}-\d{2})(?:[T ](\d{2}):(\d{2}))?$/;
3
3
  /** Parses `YYYY-MM-DD`, `YYYY-MM-DDTHH:mm` or `YYYY-MM-DD HH:mm`. */
4
4
  export function parseDateTime(text) {
@@ -47,32 +47,50 @@ const wordsCache = new Map();
47
47
  function localeWords(locale) {
48
48
  let words = wordsCache.get(locale);
49
49
  if (!words) {
50
- const month = (style) => {
51
- const format = new Intl.DateTimeFormat(locale, {
52
- month: style,
53
- timeZone: 'UTC',
54
- calendar: 'gregory',
50
+ const month = (style, withDay) => {
51
+ const format = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({ month: style }, (withDay ? { day: 'numeric' } : {})), { timeZone: 'UTC', calendar: 'gregory' }));
52
+ return Array.from({ length: 12 }, (_, m) => {
53
+ const date = new Date(Date.UTC(2023, m, 1));
54
+ if (!withDay)
55
+ return format.format(date);
56
+ const parts = format.formatToParts(date);
57
+ const index = parts.findIndex((part) => part.type === 'month');
58
+ if (index === -1)
59
+ return format.format(date);
60
+ // CJK locales split a counter word, as Japanese `月`, into its own literal right after the
61
+ // month; a literal with no space is that word, not the separator before the day.
62
+ const suffix = parts[index + 1];
63
+ return (suffix === null || suffix === void 0 ? void 0 : suffix.type) === 'literal' && !/\s/.test(suffix.value) ? parts[index].value + suffix.value : parts[index].value;
55
64
  });
56
- return Array.from({ length: 12 }, (_, m) => format.format(new Date(Date.UTC(2023, m, 1))));
57
65
  };
58
66
  const clock = clockFor(locale);
59
- words = { months: month('long'), shortMonths: month('short'), am: clock.am, pm: clock.pm };
67
+ words = {
68
+ months: month('long', false),
69
+ shortMonths: month('short', false),
70
+ monthsWithDay: month('long', true),
71
+ shortMonthsWithDay: month('short', true),
72
+ am: clock.am,
73
+ pm: clock.pm,
74
+ };
60
75
  wordsCache.set(locale, words);
61
76
  }
62
77
  return words;
63
78
  }
79
+ /** Whether a format's tokens (see `tokensOnly`) have a day, so a month name should inflect for one. */
80
+ const hasDayToken = (tokens) => /D/.test(tokens);
64
81
  /** Writes a date in a format of tokens (see `FORMAT_PARTS`), with month names and AM/PM in the locale. */
65
82
  export function formatWith(value, format, locale = 'en-US') {
66
83
  var _a;
67
84
  const words = localeWords(locale);
85
+ const withDay = hasDayToken(tokensOnly(format));
68
86
  const [hours, minutes] = splitTime((_a = value.time) !== null && _a !== void 0 ? _a : '00:00');
69
87
  const hour12 = hours % 12 || 12;
70
88
  const month = value.date.getUTCMonth();
71
89
  const period = hours < 12 ? words.am : words.pm;
72
90
  const parts = {
73
91
  YYYY: pad(value.date.getUTCFullYear(), 4),
74
- MMMM: words.months[month],
75
- MMM: words.shortMonths[month],
92
+ MMMM: withDay ? words.monthsWithDay[month] : words.months[month],
93
+ MMM: withDay ? words.shortMonthsWithDay[month] : words.shortMonths[month],
76
94
  MM: pad(month + 1),
77
95
  M: String(month + 1),
78
96
  DD: pad(value.date.getUTCDate()),
@@ -94,17 +112,23 @@ const alternatives = (words) => `(${words.map(escapeRegExp).join('|')})`;
94
112
  /**
95
113
  * Reads typed text in a format of tokens, as written by `formatWith`. Leading zeros are optional,
96
114
  * spaces are loose, and month names and AM/PM are matched without regard to case, in the locale or
97
- * in English. Returns `null` for anything that isn't a real date and time in that format.
115
+ * in English. A format without `YYYY` reads into `year`, and without `year` it reads nothing.
116
+ * Returns `null` for anything that isn't a real date and time in that format.
98
117
  */
99
- export function parseWith(text, format, locale = 'en-US') {
118
+ export function parseWith(text, format, locale = 'en-US', year) {
100
119
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
101
120
  const words = localeWords(locale);
102
121
  const english = localeWords('en-US');
122
+ const withDay = hasDayToken(tokensOnly(format));
123
+ const months = withDay ? words.monthsWithDay : words.months;
124
+ const shortMonths = withDay ? words.shortMonthsWithDay : words.shortMonths;
125
+ const englishMonths = withDay ? english.monthsWithDay : english.months;
126
+ const englishShortMonths = withDay ? english.shortMonthsWithDay : english.shortMonths;
103
127
  const periods = [words.am, words.pm, english.am, english.pm];
104
128
  const patterns = {
105
129
  YYYY: '(\\d{4})',
106
- MMMM: alternatives([...words.months, ...english.months]),
107
- MMM: alternatives([...words.shortMonths, ...english.shortMonths]),
130
+ MMMM: alternatives([...months, ...englishMonths]),
131
+ MMM: alternatives([...shortMonths, ...englishShortMonths]),
108
132
  MM: '(\\d{1,2})',
109
133
  M: '(\\d{1,2})',
110
134
  DD: '(\\d{1,2})',
@@ -151,7 +175,7 @@ export function parseWith(text, format, locale = 'en-US') {
151
175
  const monthIndex = (name, long) => {
152
176
  if (name === undefined)
153
177
  return undefined;
154
- const lists = long ? [words.months, english.months] : [words.shortMonths, english.shortMonths];
178
+ const lists = long ? [months, englishMonths] : [shortMonths, englishShortMonths];
155
179
  for (const list of lists) {
156
180
  const index = list.findIndex((candidate) => candidate.toLowerCase() === name.toLowerCase());
157
181
  if (index >= 0)
@@ -159,12 +183,12 @@ export function parseWith(text, format, locale = 'en-US') {
159
183
  }
160
184
  return undefined;
161
185
  };
162
- const year = Number(read.YYYY);
186
+ const readYear = read.YYYY ? Number(read.YYYY) : year;
163
187
  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);
164
188
  const day = Number((_d = read.DD) !== null && _d !== void 0 ? _d : read.D);
165
- if (!read.YYYY || !month || !day)
189
+ if (readYear === undefined || !month || !day)
166
190
  return null;
167
- const date = parseIsoDate(`${pad(year, 4)}-${pad(month)}-${pad(day)}`);
191
+ const date = parseIsoDate(`${pad(readYear, 4)}-${pad(month)}-${pad(day)}`);
168
192
  if (!date)
169
193
  return null;
170
194
  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;
@@ -186,6 +210,40 @@ export function parseFieldText(text, format = DEFAULT_FORMAT, locale = 'en-US')
186
210
  var _a;
187
211
  return (_a = parseWith(text, format, locale)) !== null && _a !== void 0 ? _a : parseDateTime(text);
188
212
  }
213
+ /**
214
+ * The locale's way of writing a month and day, as a format of tokens: `MMMM D` in English, `D MMMM`
215
+ * in French, `M月D日` in Japanese, or with `numeric`, `M/D` and `D/M`.
216
+ */
217
+ export function monthDayFormat(locale = 'en-US', month = 'long') {
218
+ const parts = dateFormatter(locale, { month, day: 'numeric' }).formatToParts(new Date(Date.UTC(MONTH_DAY_YEAR, 11, 31)));
219
+ return parts
220
+ .map((part) => {
221
+ // Some locales write even the long month in digits, as Japanese `12月31日`.
222
+ if (part.type === 'month')
223
+ return /^\d+$/.test(part.value) ? 'M' : 'MMMM';
224
+ if (part.type === 'day')
225
+ return 'D';
226
+ // Bracketed, spaces included, only when it has letters a token could be read from, as
227
+ // Spanish `D[ de ]MMMM`.
228
+ return /[A-Za-z]/.test(part.value) ? `[${part.value}]` : part.value;
229
+ })
230
+ .join('');
231
+ }
232
+ /**
233
+ * Reads a typed month and day, into month-day mode's year: in the format, then as the locale writes
234
+ * it with a month name or in numbers, with the English order too, then as `--MM-DD` or a full ISO
235
+ * date, whose year is dropped.
236
+ */
237
+ export function parseMonthDayText(text, format, locale = 'en-US') {
238
+ const formats = [format, monthDayFormat(locale), monthDayFormat(locale, 'numeric'), 'MMMM D', 'D MMMM', 'MMM D', 'D MMM'];
239
+ for (const candidate of formats) {
240
+ const read = parseWith(text, candidate, locale, MONTH_DAY_YEAR);
241
+ if (read)
242
+ return { date: inMonthDayYear(read.date), time: null };
243
+ }
244
+ const date = parseMonthDay(text);
245
+ return date ? { date, time: null } : null;
246
+ }
189
247
  /**
190
248
  * Parses a typed range: two dates (with times, optionally) joined by a dash or `to` with spaces
191
249
  * around it, as `2026-09-13 – 2026-10-13`, or by `/` when the format doesn't use slashes. The
@@ -107,7 +107,7 @@ export class PdsDropdownMenu {
107
107
  this.currentFocusIndex = -1;
108
108
  // Return focus to trigger
109
109
  if (this.isFocusInMenu()) {
110
- this.triggerEl.focus();
110
+ this.focusTrigger();
111
111
  }
112
112
  };
113
113
  // Handle click on the trigger element
@@ -158,6 +158,14 @@ export class PdsDropdownMenu {
158
158
  const focused = this.host.getRootNode().activeElement;
159
159
  return focused != null && this.host.contains(focused);
160
160
  }
161
+ // A trigger whose host can't take focus itself, such as pds-chip, passes it to the control inside.
162
+ focusTrigger() {
163
+ var _a, _b;
164
+ this.triggerEl.focus();
165
+ if (this.host.getRootNode().activeElement === this.triggerEl)
166
+ return;
167
+ (_b = (_a = this.triggerEl.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('button, [href], [tabindex]:not([tabindex="-1"])')) === null || _b === void 0 ? void 0 : _b.focus();
168
+ }
161
169
  // Check if a menu item is disabled (handles both component and raw elements)
162
170
  isItemDisabled(item) {
163
171
  const tagName = item.tagName.toLowerCase();
@@ -253,6 +261,23 @@ export class PdsDropdownMenu {
253
261
  this.focusItemByIndex(prevIndex);
254
262
  }
255
263
  }
264
+ // Unlike the arrow keys, Tab stops at either end of the menu instead of wrapping.
265
+ adjacentEnabledIndex(fromIndex, step) {
266
+ for (let index = fromIndex + step; index >= 0 && index < this.menuItems.length; index += step) {
267
+ if (!this.isItemDisabled(this.menuItems[index]))
268
+ return index;
269
+ }
270
+ return -1;
271
+ }
272
+ // An enabled item can still refuse focus (hidden, or a link with no href), so each one is tried in turn.
273
+ focusAdjacentItem(fromIndex, step) {
274
+ for (let index = this.adjacentEnabledIndex(fromIndex, step); index !== -1; index = this.adjacentEnabledIndex(index, step)) {
275
+ this.focusItemByIndex(index);
276
+ if (this.getFocusedItemIndex() === index)
277
+ return true;
278
+ }
279
+ return false;
280
+ }
256
281
  // Escape is taken in document's capture phase, before a modal around the menu hears it on
257
282
  // document and closes too.
258
283
  handleEscapeKeyDown(event) {
@@ -261,6 +286,28 @@ export class PdsDropdownMenu {
261
286
  event.preventDefault();
262
287
  this.closeDropdown();
263
288
  }
289
+ // Taken in the capture phase, so a modal or popover around the menu that traps Tab finds the key
290
+ // already handled, or focus back on the trigger with the items hidden.
291
+ handleTabKeyDown(event) {
292
+ if (!this.isOpen || event.key !== 'Tab' || event.defaultPrevented)
293
+ return;
294
+ const currentIndex = this.getFocusedItemIndex();
295
+ if (currentIndex === -1) {
296
+ // The menu sits after the trigger, so Shift+Tab from the trigger leaves it behind. So does Shift+Tab
297
+ // from outside the menu, where focus stays after a mouse open in Safari.
298
+ if (event.shiftKey && (document.activeElement === this.triggerEl || !this.isFocusInMenu()))
299
+ this.closeDropdown();
300
+ return;
301
+ }
302
+ if (this.focusAdjacentItem(currentIndex, event.shiftKey ? -1 : 1)) {
303
+ event.preventDefault();
304
+ return;
305
+ }
306
+ // Past either end, closing moves focus to the trigger. Shift+Tab stays there; Tab carries on past it.
307
+ if (event.shiftKey)
308
+ event.preventDefault();
309
+ this.closeDropdown();
310
+ }
264
311
  // Handle keyboard events for the dropdown
265
312
  handleKeyDown(event) {
266
313
  if (!this.isOpen)
@@ -300,54 +347,19 @@ export class PdsDropdownMenu {
300
347
  }
301
348
  }
302
349
  break;
303
- case 'Tab':
304
- if (event.shiftKey) {
305
- // Let Shift+Tab navigate naturally from first item to trigger
306
- // For all other items, move to previous item
307
- const currentIndex = this.getFocusedItemIndex();
308
- if (currentIndex > 0) {
309
- // If not on first item, prevent default and go to previous item
310
- event.preventDefault();
311
- this.focusPreviousItem(); // Use our method that skips disabled items
312
- }
313
- // If on first item or no item, let natural tab order move back to trigger
350
+ case 'Tab': {
351
+ // Tab from an item is handled in handleTabKeyDown; from anywhere else it moves into the menu.
352
+ if (event.shiftKey || this.getFocusedItemIndex() !== -1)
353
+ break;
354
+ if (this.focusAdjacentItem(-1, 1)) {
355
+ event.preventDefault();
314
356
  }
315
357
  else {
316
- // Forward Tab navigation
317
- const activeElement = document.activeElement;
318
- const isTriggerFocused = activeElement === this.triggerEl;
319
- const currentIndex = this.getFocusedItemIndex();
320
- if (isTriggerFocused && this.menuItems.length > 0) {
321
- // If trigger is focused, move to first non-disabled menu item
322
- event.preventDefault();
323
- // Find the first non-disabled item
324
- let firstFocusableIndex = 0;
325
- while (firstFocusableIndex < this.menuItems.length && this.isItemDisabled(this.menuItems[firstFocusableIndex])) {
326
- firstFocusableIndex++;
327
- }
328
- if (firstFocusableIndex < this.menuItems.length) {
329
- this.focusItemByIndex(firstFocusableIndex);
330
- }
331
- }
332
- else if (currentIndex === -1 && this.menuItems.length > 0) {
333
- // If no menu item is focused, focus the first non-disabled one
334
- event.preventDefault();
335
- // Find the first non-disabled item
336
- let firstFocusableIndex = 0;
337
- while (firstFocusableIndex < this.menuItems.length && this.isItemDisabled(this.menuItems[firstFocusableIndex])) {
338
- firstFocusableIndex++;
339
- }
340
- if (firstFocusableIndex < this.menuItems.length) {
341
- this.focusItemByIndex(firstFocusableIndex);
342
- }
343
- }
344
- else if (currentIndex !== -1) {
345
- // Use our method that skips disabled items
346
- event.preventDefault();
347
- this.focusNextItem();
348
- }
358
+ // With no item to move to, Tab carries on past the menu, as it does past the last item.
359
+ this.closeDropdown();
349
360
  }
350
361
  break;
362
+ }
351
363
  }
352
364
  }
353
365
  // Handle clicks outside the dropdown to close it
@@ -357,7 +369,7 @@ export class PdsDropdownMenu {
357
369
  }
358
370
  }
359
371
  render() {
360
- return (h(Host, { key: '0ed3d8ee889a5423ca9de66a86791d084f75abf0', id: this.componentId }, h("slot", { key: '6bab71652d6afe094075623d3a48641a4d89b232', name: "trigger", onSlotchange: this.handleTriggerSlotChange }), h("pds-box", { key: 'fcef0667f999ae5b8cd03eb94a0db0be7be502cb', "border-radius": "sm", display: "flex", direction: "column", class: "pds-dropdown-menu--panel is-hidden", shadow: "100", part: "menu-panel" }, h("div", { key: 'c78d5bd7e48c5483b12171341271f9132f4b2ff9', class: { 'pds-dropdown-menu__header': true, 'is-hidden': !this.hasHeader } }, h("slot", { key: '6464994260720d6046da548f4aad0896965cc47c', name: "header", onSlotchange: this.handleHeaderSlotChange })), h("div", { key: '3e97dfc4e1911f91ce8917faf981d436e71e5c24', class: "pds-dropdown-menu__items", role: "menu", "aria-orientation": "vertical" }, h("slot", { key: 'dfd77a8b73df5ce9eef3e661d3f9501cb8bfdba2', onSlotchange: this.handleSlotChange })))));
372
+ return (h(Host, { key: 'aa4ce84354cf87638ee46fd7ed69520228b7e656', id: this.componentId }, h("slot", { key: '948800e67d18c8c6cc9e77ef3aeab635c5035453', name: "trigger", onSlotchange: this.handleTriggerSlotChange }), h("pds-box", { key: '3ce2a2879b14aad975cea8ae72fcbf8c3abcf20c', "border-radius": "sm", display: "flex", direction: "column", class: "pds-dropdown-menu--panel is-hidden", shadow: "100", part: "menu-panel" }, h("div", { key: 'ad25fbf4f3c7a24653c229e64fe2ce893a92e4c8', class: { 'pds-dropdown-menu__header': true, 'is-hidden': !this.hasHeader } }, h("slot", { key: '35a5903543e08577997b456bcd312bdd009fd8fc', name: "header", onSlotchange: this.handleHeaderSlotChange })), h("div", { key: 'a9593adacf047918c1d87494f8c951a2a4a5f76f', class: "pds-dropdown-menu__items", role: "menu", "aria-orientation": "vertical" }, h("slot", { key: '8d2def1cc7020608220099f4ced0f44e9838cce8', onSlotchange: this.handleSlotChange })))));
361
373
  }
362
374
  static get is() { return "pds-dropdown-menu"; }
363
375
  static get encapsulation() { return "shadow"; }
@@ -435,6 +447,12 @@ export class PdsDropdownMenu {
435
447
  "target": "document",
436
448
  "capture": true,
437
449
  "passive": false
450
+ }, {
451
+ "name": "keydown",
452
+ "method": "handleTabKeyDown",
453
+ "target": "document",
454
+ "capture": true,
455
+ "passive": false
438
456
  }, {
439
457
  "name": "keydown",
440
458
  "method": "handleKeyDown",
@@ -2,7 +2,7 @@ import { computePosition, flip, offset, shift, } from "@floating-ui/dom";
2
2
  import { findPortalTarget, isTopLayerInDocument, onTopLayerClose } from "./portal-target";
3
3
  // Controls that clipped text can sit inside that already take focus, so the text adds no tab stop of its own.
4
4
  // Not any `[tabindex]`: a focusable scroll region full of chips would show every chip's tooltip at once.
5
- export const FOCUSABLE_WRAPPER = 'a[href], button, summary, pds-button, pds-link, pds-tab, pds-dropdown-menu-item, [role="option"]';
5
+ export const FOCUSABLE_WRAPPER = 'a[href], button, summary, pds-button, pds-link, pds-tab, pds-dropdown-menu-item, [role="option"], [role="treeitem"]';
6
6
  /** CSS class prefix used for truncation tooltip portal elements */
7
7
  const TOOLTIP_CLASS = 'pds-truncation-tooltip';
8
8
  /**
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-11-04T18:16:35",
2
+ "timestamp": "2026-11-09T22:28:16",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.43.5",
@@ -15253,7 +15253,7 @@
15253
15253
  "encapsulation": "shadow",
15254
15254
  "tag": "pds-datepicker",
15255
15255
  "readme": "# pds-datepicker\n\n\n",
15256
- "docs": "A field for one date, or in range mode a start and end date, with a calendar to pick from.\nThe value is `YYYY-MM-DD`, or `YYYY-MM-DD/YYYY-MM-DD` for a range; with the time picker, each\ndate carries a time, as `YYYY-MM-DDTHH:mm`.",
15256
+ "docs": "A field for one date, or in range mode a start and end date, with a calendar to pick from.\nThe value is `YYYY-MM-DD`, or `YYYY-MM-DD/YYYY-MM-DD` for a range; with the time picker, each\ndate carries a time, as `YYYY-MM-DDTHH:mm`. In month-day mode, for a date that comes round every\nyear such as a birthday, it's a month and day with no year, as `--MM-DD`.",
15257
15257
  "docsTags": [
15258
15258
  {
15259
15259
  "name": "part",
@@ -15572,7 +15572,7 @@
15572
15572
  "mutable": false,
15573
15573
  "attr": "format",
15574
15574
  "reflectToAttr": false,
15575
- "docs": "How the field shows dates and reads typed ones, as tokens: `YYYY`, `MMMM` or `MMM` (month name,\nin the locale), `MM` or `M` (month), `DD` or `D` (day), and with the time picker `HH` or `H`\n(24-hour), `hh` or `h` (12-hour), `mm` and `A` (AM/PM, in the locale), as `MM/DD/YYYY`,\n`MMM D, YYYY` or `D.M.YYYY h:mm A`. Put literal text in brackets, as `[Due] D/M/YYYY`. The value\nand the change event stay ISO (`YYYY-MM-DD`), whatever the format. Typed ISO dates are always\naccepted too.",
15575
+ "docs": "How the field shows dates and reads typed ones, as tokens: `YYYY`, `MMMM` or `MMM` (month name,\nin the locale), `MM` or `M` (month), `DD` or `D` (day), and with the time picker `HH` or `H`\n(24-hour), `hh` or `h` (12-hour), `mm` and `A` (AM/PM, in the locale), as `MM/DD/YYYY`,\n`MMM D, YYYY` or `D.M.YYYY h:mm A`. Put literal text in brackets, as `[Due] D/M/YYYY`. The value\nand the change event stay ISO (`YYYY-MM-DD`; `--MM-DD` with `month-day`), whatever the format.\nTyped ISO dates are always accepted too. With `month-day`, a format without `YYYY` is taken as a\nmonth-and-day format and used as given, rather than reordered for the locale; leave it unset to\nfollow the locale instead.",
15576
15576
  "docsTags": [
15577
15577
  {
15578
15578
  "name": "default",
@@ -15948,6 +15948,35 @@
15948
15948
  "getter": false,
15949
15949
  "setter": false
15950
15950
  },
15951
+ {
15952
+ "name": "monthDay",
15953
+ "type": "boolean",
15954
+ "complexType": {
15955
+ "original": "boolean",
15956
+ "resolved": "boolean",
15957
+ "references": {}
15958
+ },
15959
+ "mutable": false,
15960
+ "attr": "month-day",
15961
+ "reflectToAttr": false,
15962
+ "docs": "Picks a month and day with no year, for a date that comes round every year, such as a birthday.\nThe value is `--MM-DD` (ISO 8601 without a year), the field shows the date as the locale writes\na month and day, and the calendar shows each month without a year or weekdays, with February\nalways having its 29th. Turns off `range`, `time-picker`, `show-dropdowns`, `min` and `max`, and\nhas no effect with `auto-apply`, `presets`, `first-day-of-week`, `min-date-message` or\n`max-date-message`, which all depend on them.",
15963
+ "docsTags": [
15964
+ {
15965
+ "name": "default",
15966
+ "text": "false"
15967
+ }
15968
+ ],
15969
+ "default": "false",
15970
+ "values": [
15971
+ {
15972
+ "type": "boolean"
15973
+ }
15974
+ ],
15975
+ "optional": false,
15976
+ "required": false,
15977
+ "getter": false,
15978
+ "setter": false
15979
+ },
15951
15980
  {
15952
15981
  "name": "monthLabel",
15953
15982
  "type": "string",
@@ -16069,7 +16098,7 @@
16069
16098
  "mutable": false,
16070
16099
  "attr": "placeholder",
16071
16100
  "reflectToAttr": false,
16072
- "docs": "Specifies a short hint that describes the expected value of the field. Defaults to the format\nthe field takes, such as `YYYY-MM-DD`.",
16101
+ "docs": "Specifies a short hint that describes the expected value of the field. Defaults to the format\nthe field takes, such as `YYYY-MM-DD`; with `month-day`, to an example date instead, such as\n`December 31`.",
16073
16102
  "docsTags": [],
16074
16103
  "values": [
16075
16104
  {
@@ -16484,7 +16513,7 @@
16484
16513
  "mutable": true,
16485
16514
  "attr": "value",
16486
16515
  "reflectToAttr": false,
16487
- "docs": "The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with\nthe time picker, each date as `YYYY-MM-DDTHH:mm`. An empty string when nothing is selected.",
16516
+ "docs": "The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with\nthe time picker, each date as `YYYY-MM-DDTHH:mm`; with `month-day`, the month and day with no\nyear, as `--MM-DD` (a full date is read too, as that month and day, and rewritten to it). An\nempty string when nothing is selected.",
16488
16517
  "docsTags": [
16489
16518
  {
16490
16519
  "name": "default",
@@ -17370,6 +17399,12 @@
17370
17399
  "capture": true,
17371
17400
  "passive": false
17372
17401
  },
17402
+ {
17403
+ "event": "keydown",
17404
+ "target": "document",
17405
+ "capture": true,
17406
+ "passive": false
17407
+ },
17373
17408
  {
17374
17409
  "event": "keydown",
17375
17410
  "target": "window",
@@ -28271,7 +28306,7 @@
28271
28306
  "path": "src/utils/types.ts"
28272
28307
  },
28273
28308
  "src/components/pds-combobox/combobox-interface.ts::ComboboxOption": {
28274
- "declaration": "export interface ComboboxOption {\n id: string | number;\n text: string;\n /**\n * Label of the group this option belongs to. Options sharing a group are rendered together\n * under one group label, where the group first appears, the same as slotted `<optgroup>`\n * markup. With `group-display=\"inline\"`, options without a group are listed before every group;\n * with `flyout` and `drill-in`, options without a group and groups keep the array's order.\n */\n group?: string;\n /**\n * Pine icon name shown beside the group's label (letters, digits and hyphens, for example\n * `hashtag`); URLs and file paths are not accepted. The first option in the group that sets\n * one wins, the same as `data-icon` on a slotted `<optgroup>`.\n */\n groupIcon?: string;\n /**\n * Pine icon name shown before the option's text (letters, digits and hyphens, for example\n * `hashtag`); URLs and file paths are not accepted. The same as `data-icon` on a slotted `<option>`.\n * Once the option is chosen, the `button` trigger shows it too.\n */\n icon?: string;\n /**\n * When `true`, the option stays reachable by the arrow keys and the pointer, so assistive tech\n * announces it as unavailable, but it cannot be selected.\n */\n disabled?: boolean;\n [key: string]: unknown;\n}",
28309
+ "declaration": "export interface ComboboxOption {\n id: string | number;\n text: string;\n /**\n * Label of the group this option belongs to. Options sharing a group are rendered together\n * under one group label, where the group first appears, the same as slotted `<optgroup>`\n * markup. With `group-display=\"inline\"`, options without a group are listed before every group;\n * with `flyout` and `drill-in`, options without a group and groups keep the array's order.\n */\n group?: string;\n /**\n * Pine icon name shown beside the group's label (letters, digits and hyphens, for example\n * `hashtag`); URLs and file paths are not accepted. The first option in the group that sets\n * one wins, the same as `data-icon` on a slotted `<optgroup>`.\n */\n groupIcon?: string;\n /**\n * A small chip after the group's label, such as \"Premium\". `label` is its text; `sentiment` is a\n * `pds-chip` sentiment (`neutral` when unset or unknown); `icon` is a Pine icon name (letters,\n * digits and hyphens, for example `premium`); URLs and file paths are not accepted. The first\n * option in the group that sets one wins, the same as `data-badge`, `data-badge-sentiment` and\n * `data-badge-icon` on a slotted `<optgroup>`. It isn't matched by search, and screen readers\n * read it as part of the group's name. It isn't shown on a drilled-in group's row back.\n */\n groupBadge?: { label: string; sentiment?: string; icon?: string };\n /**\n * Pine icon name shown before the option's text (letters, digits and hyphens, for example\n * `hashtag`); URLs and file paths are not accepted. The same as `data-icon` on a slotted `<option>`.\n * Once the option is chosen, the `button` trigger shows it too.\n */\n icon?: string;\n /**\n * A small chip after the option's text, such as \"New\". `label` is its text; `sentiment` is a\n * `pds-chip` sentiment (`neutral` when unset or unknown); `icon` is a Pine icon name (letters,\n * digits and hyphens, for example `star`); URLs and file paths are not accepted. The same as\n * `data-badge`, `data-badge-sentiment` and `data-badge-icon` on a slotted `<option>`. It isn't\n * shown in the trigger or matched by search, and screen readers read it as part of the option's\n * name. Options that are chips or custom layouts don't show it.\n */\n badge?: { label: string; sentiment?: string; icon?: string };\n /**\n * When `true`, the option stays reachable by the arrow keys and the pointer, so assistive tech\n * announces it as unavailable, but it cannot be selected.\n */\n disabled?: boolean;\n [key: string]: unknown;\n}",
28275
28310
  "docstring": "",
28276
28311
  "path": "src/components/pds-combobox/combobox-interface.ts"
28277
28312
  },
@@ -28296,7 +28331,7 @@
28296
28331
  "path": "src/components/pds-datepicker/datepicker-interface.ts"
28297
28332
  },
28298
28333
  "src/components/pds-datepicker/datepicker-interface.ts::DatepickerChangeEventDetail": {
28299
- "declaration": "export interface DatepickerChangeEventDetail {\n /**\n * The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with\n * the time picker, each date as `YYYY-MM-DDTHH:mm`. An empty string when cleared.\n */\n value: string;\n /** Range mode: the first day of the range, `YYYY-MM-DD`, or `YYYY-MM-DDTHH:mm` with the time picker. */\n start?: string;\n /** Range mode: the last day of the range, `YYYY-MM-DD`, or `YYYY-MM-DDTHH:mm` with the time picker. */\n end?: string;\n /**\n * Range mode: the key of the preset the range matches, such as `last-30-days`, so a filter can\n * keep the range relative to today.\n */\n preset?: string;\n}",
28334
+ "declaration": "export interface DatepickerChangeEventDetail {\n /**\n * The selected date as `YYYY-MM-DD`, or in range mode the range as `YYYY-MM-DD/YYYY-MM-DD`; with\n * the time picker, each date as `YYYY-MM-DDTHH:mm`; with `month-day`, `--MM-DD`. An empty string\n * when cleared.\n */\n value: string;\n /** Range mode: the first day of the range, `YYYY-MM-DD`, or `YYYY-MM-DDTHH:mm` with the time picker. */\n start?: string;\n /** Range mode: the last day of the range, `YYYY-MM-DD`, or `YYYY-MM-DDTHH:mm` with the time picker. */\n end?: string;\n /**\n * Range mode: the key of the preset the range matches, such as `last-30-days`, so a filter can\n * keep the range relative to today.\n */\n preset?: string;\n}",
28300
28335
  "docstring": "",
28301
28336
  "path": "src/components/pds-datepicker/datepicker-interface.ts"
28302
28337
  },