@pure-ds/core 0.7.19 → 0.7.21

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 (33) hide show
  1. package/.cursorrules +10 -0
  2. package/.github/copilot-instructions.md +10 -0
  3. package/custom-elements.json +232 -25
  4. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  5. package/dist/types/public/assets/pds/components/pds-code.d.ts +19 -0
  6. package/dist/types/public/assets/pds/components/pds-code.d.ts.map +1 -0
  7. package/dist/types/public/assets/pds/components/pds-icon.d.ts +31 -1
  8. package/dist/types/public/assets/pds/components/pds-icon.d.ts.map +1 -1
  9. package/dist/types/public/assets/pds/components/pds-treeview.d.ts +271 -16
  10. package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -1
  11. package/dist/types/src/js/components/pds-code.d.ts +19 -0
  12. package/dist/types/src/js/components/pds-code.d.ts.map +1 -0
  13. package/dist/types/src/js/external/shiki.d.ts +3 -0
  14. package/dist/types/src/js/external/shiki.d.ts.map +1 -0
  15. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  16. package/package.json +1 -1
  17. package/packages/pds-cli/bin/templates/bootstrap/public/assets/my/my-home.js +1 -1
  18. package/public/assets/js/app.js +1 -1
  19. package/public/assets/js/pds-manager.js +138 -148
  20. package/public/assets/pds/components/pds-calendar.js +504 -16
  21. package/public/assets/pds/components/pds-code.js +203 -0
  22. package/public/assets/pds/components/pds-icon.js +102 -27
  23. package/public/assets/pds/components/pds-live-importer.js +2 -2
  24. package/public/assets/pds/components/pds-scrollrow.js +27 -2
  25. package/public/assets/pds/components/pds-treeview.js +185 -0
  26. package/public/assets/pds/core/pds-manager.js +138 -148
  27. package/public/assets/pds/custom-elements.json +263 -18
  28. package/public/assets/pds/external/shiki.js +32 -0
  29. package/public/assets/pds/pds-css-complete.json +1 -1
  30. package/public/assets/pds/templates/feedback-ops-dashboard.html +1 -1
  31. package/public/assets/pds/templates/release-readiness-radar.html +2 -2
  32. package/public/assets/pds/templates/support-command-center.html +1 -1
  33. package/src/js/pds-core/pds-generator.js +142 -152
@@ -139,19 +139,26 @@ class PdsCalendar extends HTMLElement {
139
139
  #date;
140
140
  #dayNames;
141
141
  #monthNames;
142
+ #internals;
143
+ #initialDate;
144
+
145
+ static formAssociated = true;
142
146
 
143
147
  static get observedAttributes() {
144
- return ["date"];
148
+ return ["date", "required", "name", "disabled", "compact"];
145
149
  }
146
150
 
147
151
  constructor() {
148
152
  super();
149
153
  this.#date = new Date();
154
+ this.#initialDate = this.getAttribute("date") || "";
150
155
 
151
156
  this.dateHelper = new DateHelper();
152
157
  this.#dayNames = this.dateHelper.getDayNames();
153
158
  this.#monthNames = this.dateHelper.getMonthNames();
154
159
 
160
+ this.#internals = this.attachInternals?.() ?? null;
161
+
155
162
  this.attachShadow({ mode: "open" });
156
163
 
157
164
  this.reRender();
@@ -164,10 +171,23 @@ class PdsCalendar extends HTMLElement {
164
171
  * @param {String} newValue - New value
165
172
  */
166
173
  attributeChangedCallback(name, oldValue, newValue) {
174
+ if (oldValue === newValue) return;
175
+
167
176
  if (name === "date") {
168
177
  this.date = newValue;
169
178
  this.reRender();
179
+ return;
180
+ }
181
+
182
+ if (name === "compact") {
183
+ this.reRender();
170
184
  }
185
+
186
+ if (name === "disabled") {
187
+ this.toggleAttribute("aria-disabled", this.disabled);
188
+ }
189
+
190
+ this.updateFormState();
171
191
  }
172
192
 
173
193
  /**
@@ -180,6 +200,8 @@ class PdsCalendar extends HTMLElement {
180
200
  ? this.dateHelper.parseDate(value)
181
201
  : new Date(value);
182
202
 
203
+ this.updateFormState();
204
+
183
205
  if (this.isRendered) this.reRender();
184
206
  }
185
207
 
@@ -191,6 +213,66 @@ class PdsCalendar extends HTMLElement {
191
213
  return this.#date;
192
214
  }
193
215
 
216
+ get form() {
217
+ return this.#internals?.form ?? null;
218
+ }
219
+
220
+ get name() {
221
+ return this.getAttribute("name") || "";
222
+ }
223
+
224
+ set name(value) {
225
+ if (value == null || value === "") {
226
+ this.removeAttribute("name");
227
+ return;
228
+ }
229
+ this.setAttribute("name", value);
230
+ }
231
+
232
+ get type() {
233
+ return "pds-calendar";
234
+ }
235
+
236
+ get required() {
237
+ return this.hasAttribute("required");
238
+ }
239
+
240
+ set required(value) {
241
+ this.toggleAttribute("required", Boolean(value));
242
+ }
243
+
244
+ get disabled() {
245
+ return this.hasAttribute("disabled");
246
+ }
247
+
248
+ set disabled(value) {
249
+ this.toggleAttribute("disabled", Boolean(value));
250
+ }
251
+
252
+ get compact() {
253
+ return this.hasAttribute("compact");
254
+ }
255
+
256
+ set compact(value) {
257
+ this.toggleAttribute("compact", Boolean(value));
258
+ }
259
+
260
+ get value() {
261
+ return this.serializeDate();
262
+ }
263
+
264
+ set value(value) {
265
+ if (value == null || value === "") {
266
+ this.removeAttribute("date");
267
+ this.#date = new Date(Number.NaN);
268
+ this.updateFormState();
269
+ this.reRender();
270
+ return;
271
+ }
272
+
273
+ this.date = value;
274
+ }
275
+
194
276
  async connectedCallback() {
195
277
  const componentStyles = PDS.createStylesheet(
196
278
  /*css*/
@@ -206,6 +288,15 @@ class PdsCalendar extends HTMLElement {
206
288
  position: relative;
207
289
  }
208
290
 
291
+ .calendar.compact {
292
+ width: max-content;
293
+ min-height: auto;
294
+ grid-template-columns: repeat(7, var(--calendar-compact-cell-size, 2.25rem));
295
+ grid-template-rows: var(--calendar-compact-cell-size, 2.25rem);
296
+ grid-auto-rows: var(--calendar-compact-cell-size, 2.25rem);
297
+ overflow: hidden;
298
+ }
299
+
209
300
  .calendar-container {
210
301
  background-color: var(--surface-bg);
211
302
  margin: auto;
@@ -215,6 +306,11 @@ class PdsCalendar extends HTMLElement {
215
306
  position: relative;
216
307
  }
217
308
 
309
+ .calendar-container.compact {
310
+ width: max-content;
311
+ margin: 0;
312
+ }
313
+
218
314
  .calendar-header {
219
315
  display: grid;
220
316
  grid-template-columns: auto 1fr auto;
@@ -266,7 +362,7 @@ class PdsCalendar extends HTMLElement {
266
362
  box-sizing: border-box;
267
363
  color: var(--surface-text-secondary);
268
364
  position: relative;
269
- pointer-events: none;
365
+ pointer-events: all;
270
366
  z-index: 1;
271
367
  overflow: hidden;
272
368
 
@@ -331,7 +427,6 @@ class PdsCalendar extends HTMLElement {
331
427
 
332
428
  &.has-events {
333
429
  cursor: pointer;
334
- pointer-events: all;
335
430
  .nr {
336
431
  font-weight: var(--font-weight-bold);
337
432
  color: var(--color-primary-500);
@@ -339,6 +434,101 @@ class PdsCalendar extends HTMLElement {
339
434
  }
340
435
  }
341
436
 
437
+ .calendar.compact .day {
438
+ min-height: 0;
439
+ width: var(--calendar-compact-cell-size, 2.25rem);
440
+ height: var(--calendar-compact-cell-size, 2.25rem);
441
+ padding: 0;
442
+ display: flex;
443
+ align-items: center;
444
+ justify-content: center;
445
+ }
446
+
447
+ .calendar.compact .day .nr {
448
+ position: static;
449
+ font-size: var(--font-size-xs);
450
+ line-height: 1;
451
+ }
452
+
453
+ .calendar.compact .day.day-radio {
454
+ position: relative;
455
+ }
456
+
457
+ .day.day-radio {
458
+ position: relative;
459
+ }
460
+
461
+ .calendar.compact .day-radio-input {
462
+ position: absolute;
463
+ inset: 0;
464
+ opacity: 0;
465
+ margin: 0;
466
+ cursor: pointer;
467
+ }
468
+
469
+ .day-radio-input {
470
+ position: absolute;
471
+ inset: 0;
472
+ opacity: 0;
473
+ margin: 0;
474
+ cursor: pointer;
475
+ }
476
+
477
+ .day.day-radio[data-day]:has(.day-radio-input:focus-visible) {
478
+ outline: var(--border-width-medium) solid var(--color-secondary-500);
479
+ outline-offset: -2px;
480
+ }
481
+
482
+ .day.day-radio[data-day]:has(.day-radio-input:focus) {
483
+ outline: var(--border-width-medium) solid var(--color-secondary-500);
484
+ outline-offset: -2px;
485
+ }
486
+
487
+ .calendar.compact .day-radio-input:checked + .nr {
488
+ font-weight: var(--font-weight-bold);
489
+ color: var(--color-accent-700);
490
+ }
491
+
492
+ .calendar.compact .task {
493
+ display: none !important;
494
+ }
495
+
496
+ .calendar.compact .day-name {
497
+ width: var(--calendar-compact-cell-size, 2.25rem);
498
+ height: var(--calendar-compact-cell-size, 2.25rem);
499
+ line-height: 1;
500
+ padding: 0;
501
+ display: flex;
502
+ align-items: center;
503
+ justify-content: center;
504
+ font-size: var(--font-size-xs);
505
+ }
506
+
507
+ .day.day-selected .nr {
508
+ font-weight: var(--font-weight-bold);
509
+ color: var(--color-accent-700);
510
+ }
511
+
512
+ .calendar.compact .day.has-events .nr {
513
+ font-weight: var(--font-weight-bold);
514
+ }
515
+
516
+ .calendar.compact .day.has-events.event-primary .nr {
517
+ color: var(--color-primary-500);
518
+ }
519
+
520
+ .calendar.compact .day.has-events.event-info .nr {
521
+ color: var(--color-info-500);
522
+ }
523
+
524
+ .calendar.compact .day.has-events.event-warning .nr {
525
+ color: var(--color-warning-500);
526
+ }
527
+
528
+ .calendar.compact .day.has-events.event-danger .nr {
529
+ color: var(--color-danger-500);
530
+ }
531
+
342
532
  .day-disabled {
343
533
  opacity: 0.4;
344
534
  cursor: not-allowed;
@@ -478,9 +668,95 @@ class PdsCalendar extends HTMLElement {
478
668
 
479
669
  queueMicrotask(() => {
480
670
  this.setupPaging();
671
+ this.updateFormState();
481
672
  });
482
673
  }
483
674
 
675
+ formAssociatedCallback() {
676
+ this.updateFormState();
677
+ }
678
+
679
+ formDisabledCallback(disabled) {
680
+ this.disabled = disabled;
681
+ this.updateFormState();
682
+ }
683
+
684
+ formResetCallback() {
685
+ this.value = this.#initialDate || "";
686
+ this.updateFormState();
687
+ this.reRender();
688
+ }
689
+
690
+ formStateRestoreCallback(state) {
691
+ if (typeof state === "string" && state.length) {
692
+ this.value = state;
693
+ }
694
+ this.updateFormState();
695
+ }
696
+
697
+ checkValidity() {
698
+ if (!this.#internals) return true;
699
+ this.updateFormState();
700
+ return this.#internals.checkValidity();
701
+ }
702
+
703
+ reportValidity() {
704
+ if (!this.#internals) return true;
705
+ this.updateFormState();
706
+ return this.#internals.reportValidity();
707
+ }
708
+
709
+ focus(options) {
710
+ const focusTarget =
711
+ this.shadowRoot?.querySelector('.day-radio-input:checked') ||
712
+ this.shadowRoot?.querySelector('.day-radio-input') ||
713
+ this.shadowRoot?.querySelector("button.prev");
714
+
715
+ if (focusTarget) {
716
+ focusTarget.focus?.(options);
717
+ }
718
+ }
719
+
720
+ serializeDate() {
721
+ if (!(this.#date instanceof Date) || Number.isNaN(this.#date.getTime())) {
722
+ return "";
723
+ }
724
+
725
+ const year = this.#date.getFullYear();
726
+ const month = String(this.#date.getMonth() + 1).padStart(2, "0");
727
+ const day = String(this.#date.getDate()).padStart(2, "0");
728
+
729
+ return `${year}-${month}-${day}`;
730
+ }
731
+
732
+ updateFormState() {
733
+ if (!this.#internals) return;
734
+
735
+ if (!this.name || this.disabled) {
736
+ this.#internals.setFormValue(null);
737
+ this.#internals.setValidity({});
738
+ return;
739
+ }
740
+
741
+ const value = this.serializeDate();
742
+ this.#internals.setFormValue(value);
743
+
744
+ if (this.required && !value) {
745
+ const validationAnchor =
746
+ this.shadowRoot?.querySelector('.day-radio-input:checked') ||
747
+ this.shadowRoot?.querySelector('.day-radio-input') ||
748
+ this.shadowRoot?.querySelector("button.prev");
749
+ this.#internals.setValidity(
750
+ { valueMissing: true },
751
+ "Please select a date.",
752
+ validationAnchor
753
+ );
754
+ return;
755
+ }
756
+
757
+ this.#internals.setValidity({});
758
+ }
759
+
484
760
  /**
485
761
  * Attaches one or more event listeners with optional event delegation
486
762
  * @param {String} eventNames - name(s) of the event to listen to, space-separated
@@ -516,13 +792,19 @@ class PdsCalendar extends HTMLElement {
516
792
  * @private
517
793
  */
518
794
  render() {
519
- this.month = this.date.getMonth();
520
- this.year = this.date.getFullYear();
795
+ const hasValidDate =
796
+ this.#date instanceof Date && !Number.isNaN(this.#date.getTime());
797
+ const displayDate = hasValidDate ? this.#date : new Date();
798
+
799
+ this.month = displayDate.getMonth();
800
+ this.year = displayDate.getFullYear();
521
801
  this.daysInMonth = new Date(this.year, this.month + 1, 0).getDate();
522
802
  this.startDay = new Date(this.year, this.month, 0).getDay();
803
+ const compactMode = this.compact;
804
+ const calendarA11yAttrs = 'role="radiogroup" aria-label="Select a day"';
523
805
 
524
806
  const calendarHtml = /*html*/ `
525
- <div class="calendar-container" part="calendar-container">
807
+ <div class="calendar-container ${compactMode ? "compact" : ""}" part="calendar-container">
526
808
  <nav class="calendar-header" part="calendar-header">
527
809
  <button class="prev icon-only"><pds-icon icon="arrow-left" size="xs"></pds-icon></button>
528
810
  <div class="current-month">
@@ -532,7 +814,7 @@ class PdsCalendar extends HTMLElement {
532
814
  <button class="next icon-only"><pds-icon icon="arrow-right" size="xs"></pds-icon></button>
533
815
  </nav>
534
816
 
535
- <div class="calendar" part="calendar">
817
+ <div class="calendar ${compactMode ? "compact" : ""}" part="calendar" ${calendarA11yAttrs}>
536
818
  ${this.getDayNamesHtml()}
537
819
  ${this.getDaysHtml()}
538
820
  </div>
@@ -588,12 +870,64 @@ class PdsCalendar extends HTMLElement {
588
870
  let timeout,
589
871
  timeoutMs = -1,
590
872
  timeoutChangeMs = 2,
591
- direction;
873
+ direction,
874
+ activeKeyboardDirection = 0;
875
+
876
+ const getPreferredDay = () => {
877
+ const checkedRadio = this.shadowRoot?.querySelector('.day-radio-input:checked[data-day]');
878
+ const checkedDay = Number.parseInt(checkedRadio?.dataset?.day || '', 10);
879
+ if (Number.isInteger(checkedDay) && checkedDay > 0) return checkedDay;
880
+
881
+ if (this.#date instanceof Date && !Number.isNaN(this.#date.getTime())) {
882
+ return this.#date.getDate();
883
+ }
884
+
885
+ return 1;
886
+ },
887
+ stopKeyboardPaging = () => {
888
+ if (!activeKeyboardDirection) return;
889
+ activeKeyboardDirection = 0;
890
+ stopMoveDate();
891
+ window.removeEventListener("keyup", handleWindowKeyup, true);
892
+ window.removeEventListener("blur", handleWindowBlur, true);
893
+ },
894
+ handleWindowKeyup = (event) => {
895
+ if (event.key !== "PageUp" && event.key !== "PageDown") return;
896
+ event.preventDefault();
897
+ stopKeyboardPaging();
898
+ },
899
+ handleWindowBlur = () => {
900
+ stopKeyboardPaging();
901
+ };
592
902
 
593
903
  const moveDate = () => {
594
- this.date.setMonth(this.date.getMonth() + direction);
904
+ const preferredDay = getPreferredDay();
905
+ const currentDate =
906
+ this.#date instanceof Date && !Number.isNaN(this.#date.getTime())
907
+ ? this.#date
908
+ : new Date(this.year, this.month, 1);
909
+
910
+ const targetMonthDate = new Date(
911
+ currentDate.getFullYear(),
912
+ currentDate.getMonth() + direction,
913
+ 1
914
+ );
915
+ const maxTargetDay = new Date(
916
+ targetMonthDate.getFullYear(),
917
+ targetMonthDate.getMonth() + 1,
918
+ 0
919
+ ).getDate();
920
+ const targetDay = Math.min(preferredDay, maxTargetDay);
921
+
922
+ this.#date = new Date(
923
+ targetMonthDate.getFullYear(),
924
+ targetMonthDate.getMonth(),
925
+ targetDay
926
+ );
927
+ this.updateFormState();
928
+ this.reRender();
595
929
  queueMicrotask(() => {
596
- this.reRender();
930
+ this.focusDayRadio(targetDay);
597
931
  });
598
932
  },
599
933
  moveDateRecursive = () => {
@@ -639,6 +973,27 @@ class PdsCalendar extends HTMLElement {
639
973
  stopMoveDate();
640
974
  },
641
975
  });
976
+
977
+ this.shadowRoot.addEventListener("keydown", (event) => {
978
+ if (event.key !== "PageUp" && event.key !== "PageDown") return;
979
+
980
+ event.preventDefault();
981
+ const nextDirection = event.key === "PageUp" ? -1 : 1;
982
+ if (activeKeyboardDirection === nextDirection) return;
983
+
984
+ activeKeyboardDirection = nextDirection;
985
+ window.addEventListener("keyup", handleWindowKeyup, true);
986
+ window.addEventListener("blur", handleWindowBlur, true);
987
+ startMoveDate(nextDirection);
988
+ });
989
+
990
+ this.shadowRoot.addEventListener("keyup", (event) => {
991
+ if (event.key !== "PageUp" && event.key !== "PageDown") return;
992
+
993
+ event.preventDefault();
994
+ stopKeyboardPaging();
995
+ });
996
+
642
997
  }
643
998
 
644
999
  /**
@@ -660,6 +1015,9 @@ class PdsCalendar extends HTMLElement {
660
1015
  return;
661
1016
  }
662
1017
 
1018
+ const compactMode = this.compact;
1019
+ if (compactMode) return;
1020
+
663
1021
  const cell = e.target.closest(".day.has-events[data-day]");
664
1022
  if (!cell) return;
665
1023
 
@@ -682,6 +1040,104 @@ class PdsCalendar extends HTMLElement {
682
1040
  expandedDay = day;
683
1041
  }
684
1042
  });
1043
+
1044
+ this.shadowRoot.addEventListener('change', (event) => {
1045
+ const radio = event.target?.closest?.('.day-radio-input[data-day]');
1046
+ if (!radio) return;
1047
+
1048
+ const selectedDay = Number.parseInt(radio.dataset.day, 10);
1049
+ if (Number.isInteger(selectedDay) && selectedDay > 0) {
1050
+ this.selectDay(selectedDay);
1051
+ }
1052
+ });
1053
+
1054
+ this.shadowRoot.addEventListener('keydown', (event) => {
1055
+ const radio = event.target?.closest?.('.day-radio-input[data-day]');
1056
+ if (!radio) return;
1057
+
1058
+ let dayDelta = 0;
1059
+ if (event.key === 'ArrowLeft') dayDelta = -1;
1060
+ if (event.key === 'ArrowRight') dayDelta = 1;
1061
+ if (event.key === 'ArrowUp') dayDelta = -7;
1062
+ if (event.key === 'ArrowDown') dayDelta = 7;
1063
+ if (!dayDelta) return;
1064
+
1065
+ event.preventDefault();
1066
+ const currentDay = Number.parseInt(radio.dataset.day || '', 10);
1067
+ if (!Number.isInteger(currentDay)) return;
1068
+ this.moveSelectionByDays(currentDay, dayDelta);
1069
+ });
1070
+ }
1071
+
1072
+ /**
1073
+ * Moves calendar selection by a relative day offset, crossing month boundaries when needed.
1074
+ * @param {number} currentDay
1075
+ * @param {number} dayDelta
1076
+ * @private
1077
+ */
1078
+ moveSelectionByDays(currentDay, dayDelta) {
1079
+ const targetDate = new Date(this.year, this.month, currentDay + dayDelta);
1080
+ if (Number.isNaN(targetDate.getTime())) return;
1081
+
1082
+ const targetDay = targetDate.getDate();
1083
+ const sameRenderedMonth =
1084
+ targetDate.getMonth() === this.month && targetDate.getFullYear() === this.year;
1085
+
1086
+ if (sameRenderedMonth) {
1087
+ this.selectDay(targetDay);
1088
+ this.focusDayRadio(targetDay);
1089
+ return;
1090
+ }
1091
+
1092
+ this.#date = targetDate;
1093
+ this.updateFormState();
1094
+ this.reRender();
1095
+ queueMicrotask(() => {
1096
+ this.focusDayRadio(targetDay);
1097
+ });
1098
+ }
1099
+
1100
+ /**
1101
+ * Selects a day inside the currently displayed month.
1102
+ * @param {number} dayNumber
1103
+ * @private
1104
+ */
1105
+ selectDay(dayNumber) {
1106
+ const nextDate = new Date(this.year, this.month, dayNumber);
1107
+ if (Number.isNaN(nextDate.getTime())) return;
1108
+
1109
+ this.#date = nextDate;
1110
+ this.updateFormState();
1111
+ this.syncSelectedDayState(dayNumber);
1112
+ }
1113
+
1114
+ /**
1115
+ * Updates selected classes/checked state in-place to preserve native radio keyboard flow.
1116
+ * @param {number} dayNumber
1117
+ * @private
1118
+ */
1119
+ syncSelectedDayState(dayNumber) {
1120
+ const dayCells = this.shadowRoot?.querySelectorAll('.day[data-day]') || [];
1121
+ dayCells.forEach((cell) => {
1122
+ const cellDay = Number.parseInt(cell.dataset.day || '', 10);
1123
+ const isSelected = Number.isInteger(cellDay) && cellDay === dayNumber;
1124
+ cell.classList.toggle('day-selected', isSelected);
1125
+
1126
+ const radio = cell.querySelector('.day-radio-input');
1127
+ if (radio) {
1128
+ radio.checked = isSelected;
1129
+ }
1130
+ });
1131
+ }
1132
+
1133
+ /**
1134
+ * Focuses a day radio by day number in the current month view.
1135
+ * @param {number} dayNumber
1136
+ * @private
1137
+ */
1138
+ focusDayRadio(dayNumber) {
1139
+ const target = this.shadowRoot?.querySelector(`.day-radio-input[data-day="${dayNumber}"]`);
1140
+ target?.focus();
685
1141
  }
686
1142
 
687
1143
  /**
@@ -760,11 +1216,18 @@ class PdsCalendar extends HTMLElement {
760
1216
  if (!Number.isInteger(dayNumber) || dayNumber < 1) continue;
761
1217
 
762
1218
  const dayDiv = this.shadowRoot.querySelector(
763
- `div[data-day="${dayNumber}"]`
1219
+ `.day[data-day="${dayNumber}"]`
764
1220
  );
765
1221
  const list = data[day];
766
1222
  if (dayDiv && Array.isArray(list)) {
767
1223
  dayDiv.classList.add("has-events");
1224
+ const firstType = String(list[0]?.type || "info").toLowerCase();
1225
+ dayDiv.classList.add(`event-${firstType}`);
1226
+
1227
+ if (this.compact) {
1228
+ continue;
1229
+ }
1230
+
768
1231
  for (const item of list) {
769
1232
  const html = /*html*/ `<div class="task task--${
770
1233
  item.type || "info"
@@ -813,28 +1276,53 @@ class PdsCalendar extends HTMLElement {
813
1276
  */
814
1277
  getDaysHtml() {
815
1278
  const html = new HTMLBuilder();
1279
+ const radioGroupName = `pds-calendar-day-${this.id || 'default'}-${this.year}-${this.month}`;
816
1280
  const now = new Date();
817
1281
  const todayDay = now.getDate();
818
1282
  const todayMonth = now.getMonth();
819
1283
  const todayYear = now.getFullYear();
820
1284
  const isCurrentMonth =
821
1285
  this.month === todayMonth && this.year === todayYear;
1286
+ const hasValidSelection =
1287
+ this.#date instanceof Date && !Number.isNaN(this.#date.getTime());
1288
+ const selectedDay = hasValidSelection ? this.#date.getDate() : -1;
1289
+ const selectedMonth = hasValidSelection ? this.#date.getMonth() : -1;
1290
+ const selectedYear = hasValidSelection ? this.#date.getFullYear() : -1;
1291
+ const isSelectedMonth =
1292
+ this.month === selectedMonth && this.year === selectedYear;
822
1293
 
823
1294
  for (let i = 0; i < this.startDay; i++) {
824
- html.add(/*html*/ `<div class="day day-disabled" part="day"></div>`);
1295
+ html.add(
1296
+ /*html*/ `<label class="day day-disabled" part="day" aria-hidden="true"></label>`
1297
+ );
825
1298
  }
826
1299
  for (let i = 1; i <= this.daysInMonth; i++) {
827
1300
  const isTodayClass =
828
1301
  isCurrentMonth && i === todayDay ? "day-today" : "";
1302
+ const isSelectedClass =
1303
+ isSelectedMonth && i === selectedDay ? "day-selected" : "";
1304
+ const checked = isSelectedMonth && i === selectedDay ? 'checked' : '';
1305
+ const radioId = `${radioGroupName}-day-${i}`;
829
1306
  html.add(/*html*/ `
830
- <div data-day="${i}" class="day ${isTodayClass}" part="day">
831
- <span class="nr">${i}<span>
832
- </div>`);
1307
+ <label data-day="${i}" class="day day-radio ${isTodayClass} ${isSelectedClass}" part="day">
1308
+ <input
1309
+ id="${radioId}"
1310
+ class="day-radio-input"
1311
+ type="radio"
1312
+ name="${radioGroupName}"
1313
+ data-day="${i}"
1314
+ aria-label="Select ${this.#monthNames[this.month]} ${i}, ${this.year}"
1315
+ ${checked}
1316
+ />
1317
+ <span class="nr">${i}</span>
1318
+ </label>`);
833
1319
  }
834
1320
  const endDay =
835
1321
  6 - new Date(this.year, this.month, this.daysInMonth).getDay();
836
1322
  for (let i = 1; i <= endDay; i++) {
837
- html.add(/*html*/ `<div class="day day-disabled" part="day"></div>`);
1323
+ html.add(
1324
+ /*html*/ `<label class="day day-disabled" part="day" aria-hidden="true"></label>`
1325
+ );
838
1326
  }
839
1327
  return html.toHTML();
840
1328
  }