@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.
- package/.cursorrules +10 -0
- package/.github/copilot-instructions.md +10 -0
- package/custom-elements.json +232 -25
- package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-code.d.ts +19 -0
- package/dist/types/public/assets/pds/components/pds-code.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-icon.d.ts +31 -1
- package/dist/types/public/assets/pds/components/pds-icon.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-treeview.d.ts +271 -16
- package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -1
- package/dist/types/src/js/components/pds-code.d.ts +19 -0
- package/dist/types/src/js/components/pds-code.d.ts.map +1 -0
- package/dist/types/src/js/external/shiki.d.ts +3 -0
- package/dist/types/src/js/external/shiki.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/packages/pds-cli/bin/templates/bootstrap/public/assets/my/my-home.js +1 -1
- package/public/assets/js/app.js +1 -1
- package/public/assets/js/pds-manager.js +138 -148
- package/public/assets/pds/components/pds-calendar.js +504 -16
- package/public/assets/pds/components/pds-code.js +203 -0
- package/public/assets/pds/components/pds-icon.js +102 -27
- package/public/assets/pds/components/pds-live-importer.js +2 -2
- package/public/assets/pds/components/pds-scrollrow.js +27 -2
- package/public/assets/pds/components/pds-treeview.js +185 -0
- package/public/assets/pds/core/pds-manager.js +138 -148
- package/public/assets/pds/custom-elements.json +263 -18
- package/public/assets/pds/external/shiki.js +32 -0
- package/public/assets/pds/pds-css-complete.json +1 -1
- package/public/assets/pds/templates/feedback-ops-dashboard.html +1 -1
- package/public/assets/pds/templates/release-readiness-radar.html +2 -2
- package/public/assets/pds/templates/support-command-center.html +1 -1
- 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:
|
|
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
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
<
|
|
831
|
-
<
|
|
832
|
-
|
|
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(
|
|
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
|
}
|