@pure-ds/storybook 0.1.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 (129) hide show
  1. package/.storybook/addons/description/preview.js +15 -0
  2. package/.storybook/addons/description/register.js +60 -0
  3. package/.storybook/addons/html-preview/Panel.jsx +327 -0
  4. package/.storybook/addons/html-preview/constants.js +6 -0
  5. package/.storybook/addons/html-preview/preview.js +178 -0
  6. package/.storybook/addons/html-preview/register.js +16 -0
  7. package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
  8. package/.storybook/addons/pds-configurator/Tool.js +30 -0
  9. package/.storybook/addons/pds-configurator/constants.js +9 -0
  10. package/.storybook/addons/pds-configurator/preview.js +159 -0
  11. package/.storybook/addons/pds-configurator/register.js +24 -0
  12. package/.storybook/docs.css +35 -0
  13. package/.storybook/htmlPreview.css +103 -0
  14. package/.storybook/htmlPreview.js +271 -0
  15. package/.storybook/main.js +160 -0
  16. package/.storybook/preview-body.html +48 -0
  17. package/.storybook/preview-head.html +11 -0
  18. package/.storybook/preview.js +1563 -0
  19. package/README.md +266 -0
  20. package/bin/index.js +40 -0
  21. package/dist/pds-reference.json +2101 -0
  22. package/package.json +45 -0
  23. package/pds.config.js +6 -0
  24. package/public/assets/css/app.css +1216 -0
  25. package/public/assets/data/auto-design-advanced.json +704 -0
  26. package/public/assets/data/auto-design-simple.json +123 -0
  27. package/public/assets/img/icon-512x512.png +0 -0
  28. package/public/assets/img/logo-trans.png +0 -0
  29. package/public/assets/img/logo.png +0 -0
  30. package/public/assets/js/app.js +15088 -0
  31. package/public/assets/js/app.js.map +7 -0
  32. package/public/assets/js/lit.js +1176 -0
  33. package/public/assets/js/lit.js.map +7 -0
  34. package/public/assets/js/pds.js +9801 -0
  35. package/public/assets/js/pds.js.map +7 -0
  36. package/public/assets/pds/components/pds-calendar.js +837 -0
  37. package/public/assets/pds/components/pds-drawer.js +857 -0
  38. package/public/assets/pds/components/pds-icon.js +338 -0
  39. package/public/assets/pds/components/pds-jsonform.js +1775 -0
  40. package/public/assets/pds/components/pds-richtext.js +1035 -0
  41. package/public/assets/pds/components/pds-scrollrow.js +331 -0
  42. package/public/assets/pds/components/pds-splitpanel.js +401 -0
  43. package/public/assets/pds/components/pds-tabstrip.js +251 -0
  44. package/public/assets/pds/components/pds-toaster.js +446 -0
  45. package/public/assets/pds/components/pds-upload.js +657 -0
  46. package/public/assets/pds/custom-elements.json +2003 -0
  47. package/public/assets/pds/icons/pds-icons.svg +498 -0
  48. package/public/assets/pds/pds-css-complete.json +1861 -0
  49. package/public/assets/pds/pds-runtime-config.json +11 -0
  50. package/public/assets/pds/pds.css-data.json +2152 -0
  51. package/public/assets/pds/styles/pds-components.css +1944 -0
  52. package/public/assets/pds/styles/pds-components.css.js +3895 -0
  53. package/public/assets/pds/styles/pds-primitives.css +352 -0
  54. package/public/assets/pds/styles/pds-primitives.css.js +711 -0
  55. package/public/assets/pds/styles/pds-styles.css +3761 -0
  56. package/public/assets/pds/styles/pds-styles.css.js +7529 -0
  57. package/public/assets/pds/styles/pds-tokens.css +699 -0
  58. package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
  59. package/public/assets/pds/styles/pds-utilities.css +763 -0
  60. package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
  61. package/public/assets/pds/vscode-custom-data.json +824 -0
  62. package/scripts/build-pds-reference.mjs +807 -0
  63. package/scripts/generate-stories.js +542 -0
  64. package/scripts/package-build.js +86 -0
  65. package/src/js/app.js +17 -0
  66. package/src/js/common/ask.js +208 -0
  67. package/src/js/common/common.js +20 -0
  68. package/src/js/common/font-loader.js +200 -0
  69. package/src/js/common/msg.js +90 -0
  70. package/src/js/lit.js +40 -0
  71. package/src/js/pds-core/pds-config.js +1162 -0
  72. package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
  73. package/src/js/pds-core/pds-enhancers.js +357 -0
  74. package/src/js/pds-core/pds-enums.js +86 -0
  75. package/src/js/pds-core/pds-generator.js +5317 -0
  76. package/src/js/pds-core/pds-ontology.js +256 -0
  77. package/src/js/pds-core/pds-paths.js +109 -0
  78. package/src/js/pds-core/pds-query.js +571 -0
  79. package/src/js/pds-core/pds-registry.js +129 -0
  80. package/src/js/pds-core/pds.d.ts +129 -0
  81. package/src/js/pds.d.ts +408 -0
  82. package/src/js/pds.js +1579 -0
  83. package/src/pds-core/pds-api.js +105 -0
  84. package/stories/GettingStarted.md +96 -0
  85. package/stories/GettingStarted.stories.js +144 -0
  86. package/stories/WhatIsPDS.md +194 -0
  87. package/stories/WhatIsPDS.stories.js +144 -0
  88. package/stories/components/PdsCalendar.stories.js +263 -0
  89. package/stories/components/PdsDrawer.stories.js +623 -0
  90. package/stories/components/PdsIcon.stories.js +78 -0
  91. package/stories/components/PdsJsonform.stories.js +1444 -0
  92. package/stories/components/PdsRichtext.stories.js +367 -0
  93. package/stories/components/PdsScrollrow.stories.js +140 -0
  94. package/stories/components/PdsSplitpanel.stories.js +502 -0
  95. package/stories/components/PdsTabstrip.stories.js +442 -0
  96. package/stories/components/PdsToaster.stories.js +186 -0
  97. package/stories/components/PdsUpload.stories.js +66 -0
  98. package/stories/enhancements/Dropdowns.stories.js +185 -0
  99. package/stories/enhancements/InteractiveStates.stories.js +625 -0
  100. package/stories/enhancements/MeshGradients.stories.js +320 -0
  101. package/stories/enhancements/OpenGroups.stories.js +227 -0
  102. package/stories/enhancements/RangeSliders.stories.js +232 -0
  103. package/stories/enhancements/RequiredFields.stories.js +189 -0
  104. package/stories/enhancements/Toggles.stories.js +167 -0
  105. package/stories/foundations/Colors.stories.js +283 -0
  106. package/stories/foundations/Icons.stories.js +305 -0
  107. package/stories/foundations/SmartSurfaces.stories.js +367 -0
  108. package/stories/foundations/Spacing.stories.js +175 -0
  109. package/stories/foundations/Typography.stories.js +960 -0
  110. package/stories/foundations/ZIndex.stories.js +325 -0
  111. package/stories/patterns/BorderEffects.stories.js +72 -0
  112. package/stories/patterns/Layout.stories.js +99 -0
  113. package/stories/patterns/Utilities.stories.js +107 -0
  114. package/stories/primitives/Accordion.stories.js +359 -0
  115. package/stories/primitives/Alerts.stories.js +64 -0
  116. package/stories/primitives/Badges.stories.js +183 -0
  117. package/stories/primitives/Buttons.stories.js +229 -0
  118. package/stories/primitives/Cards.stories.js +353 -0
  119. package/stories/primitives/FormGroups.stories.js +569 -0
  120. package/stories/primitives/Forms.stories.js +131 -0
  121. package/stories/primitives/Media.stories.js +203 -0
  122. package/stories/primitives/Tables.stories.js +232 -0
  123. package/stories/reference/ReferenceCatalog.stories.js +28 -0
  124. package/stories/reference/reference-catalog.js +413 -0
  125. package/stories/reference/reference-docs.js +302 -0
  126. package/stories/reference/reference-helpers.js +310 -0
  127. package/stories/utilities/GridSystem.stories.js +208 -0
  128. package/stories/utils/PdsAsk.stories.js +420 -0
  129. package/stories/utils/toast-utils.js +148 -0
@@ -0,0 +1,837 @@
1
+ function parseHTML(html) {
2
+ return new DOMParser().parseFromString(html, "text/html").body.childNodes;
3
+ }
4
+
5
+ const PDS = window.PDS;
6
+ /**
7
+ * Simple class for efficient HTML string building
8
+ * @private
9
+ */
10
+ export class HTMLBuilder {
11
+ #lines = [];
12
+ #container = "";
13
+
14
+ /**
15
+ * Constructor
16
+ * @param {String} container - use '{html}' for the built-up string to be placed in
17
+ */
18
+ constructor(container = "{html}") {
19
+ this.#container = container;
20
+ }
21
+
22
+ /**
23
+ * Adds a string to the builder
24
+ * @param {String} htmlPart
25
+ */
26
+ add(htmlPart) {
27
+ this.#lines.push(htmlPart);
28
+ }
29
+
30
+ /**
31
+ * Returns the built-up string, optionally using the given container for enclosing.
32
+ * @returns {String} html string
33
+ */
34
+ toHTML() {
35
+ const html = this.#lines.join("");
36
+
37
+ if (this.#container.length) return this.#container.replace("{html}", html);
38
+
39
+ return html;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Helper class for date operations
45
+ * @private
46
+ */
47
+ class DateHelper {
48
+ #locale;
49
+
50
+ constructor(locale = "en-US") {
51
+ this.#locale = locale;
52
+ }
53
+
54
+ parseDate(dateString) {
55
+ const timestamp = Date.parse(dateString);
56
+ return new Date(timestamp);
57
+ }
58
+
59
+ getMonthNames() {
60
+ return Array.from({ length: 12 }, (_, i) => {
61
+ return new Date(0, i).toLocaleString(this.#locale, { month: "long" });
62
+ });
63
+ }
64
+
65
+ getDayNames(format = "long") {
66
+ return Array.from({ length: 7 }, (_, i) => {
67
+ // Create a new Date object for each day of the week, starting from Sunday
68
+ const date = new Date(Date.UTC(2024, 0, i + 15));
69
+ return new Intl.DateTimeFormat(this.#locale, { weekday: format }).format(
70
+ date
71
+ );
72
+ });
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @component pds-calendar
78
+ * @description A fully featured calendar component with month navigation, event display, and expandable day views
79
+ *
80
+ * @fires pds-calendar#month-rendered - Dispatched after the calendar month is rendered with event fill capability
81
+ *
82
+ * @attr {String} date - The date to display (defaults to current date). Accepts any valid date string
83
+ *
84
+ * @prop {Date} date - Gets or sets the current date being displayed
85
+ *
86
+ * @example
87
+ * <caption>Basic calendar</caption>
88
+ * <pds-calendar></pds-calendar>
89
+ *
90
+ * @example
91
+ * <caption>Calendar with specific date</caption>
92
+ * <pds-calendar date="2024-12-25"></pds-calendar>
93
+ *
94
+ * @example
95
+ * <caption>Calendar with event handling</caption>
96
+ * <pds-calendar id="my-calendar"></pds-calendar>
97
+ * <script>
98
+ * const calendar = document.getElementById('my-calendar');
99
+ * calendar.addEventListener('month-rendered', (e) => {
100
+ * e.detail.fill({
101
+ * '15': [
102
+ * { title: 'Team Meeting', type: 'primary' },
103
+ * { title: 'Code Review', type: 'info' }
104
+ * ],
105
+ * '20': [
106
+ * { title: 'Project Deadline', type: 'danger' }
107
+ * ]
108
+ * });
109
+ * });
110
+ * </script>
111
+ *
112
+ * @example
113
+ * <caption>Event types and styling</caption>
114
+ * // Event objects support the following types:
115
+ * // - 'primary' (blue)
116
+ * // - 'info' (light blue)
117
+ * // - 'warning' (yellow)
118
+ * // - 'danger' (red)
119
+ * {
120
+ * title: 'Event Title',
121
+ * type: 'primary' // optional, defaults to 'info'
122
+ * }
123
+ *
124
+ * @cssprop --surface-bg - Calendar background color
125
+ * @cssprop --surface-border - Border color for calendar elements
126
+ * @cssprop --surface-text-secondary - Secondary text color
127
+ * @cssprop --color-primary-500 - Primary color for events and highlights
128
+ * @cssprop --color-warning-500 - Warning event color
129
+ * @cssprop --color-danger-500 - Danger event color
130
+ * @cssprop --color-info-500 - Info event color
131
+ *
132
+ * @csspart calendar-container - The main container for the calendar
133
+ * @csspart calendar-header - The header containing month name and navigation
134
+ * @csspart calendar - The grid container for the calendar days
135
+ * @csspart day - Individual day cells
136
+ * @csspart task - Event items within days
137
+ */
138
+ customElements.define(
139
+ "pds-calendar",
140
+
141
+ class extends HTMLElement {
142
+ #date;
143
+ #dayNames;
144
+ #monthNames;
145
+
146
+ static get observedAttributes() {
147
+ return ["date"];
148
+ }
149
+
150
+ constructor() {
151
+ super();
152
+ this.#date = new Date();
153
+
154
+ this.dateHelper = new DateHelper();
155
+ this.#dayNames = this.dateHelper.getDayNames();
156
+ this.#monthNames = this.dateHelper.getMonthNames();
157
+
158
+ this.attachShadow({ mode: "open" });
159
+
160
+ this.reRender();
161
+ }
162
+
163
+ /**
164
+ * Called when observed attributes change
165
+ * @param {String} name - Attribute name
166
+ * @param {String} oldValue - Previous value
167
+ * @param {String} newValue - New value
168
+ */
169
+ attributeChangedCallback(name, oldValue, newValue) {
170
+ if (name === "date") {
171
+ this.date = newValue;
172
+ this.reRender();
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Sets the current date for the calendar
178
+ * @param {String|Date} value - Date string or Date object
179
+ */
180
+ set date(value) {
181
+ this.#date =
182
+ typeof value === "string"
183
+ ? this.dateHelper.parseDate(value)
184
+ : new Date(value);
185
+
186
+ if (this.isRendered) this.reRender();
187
+ }
188
+
189
+ /**
190
+ * Gets the current date
191
+ * @returns {Date} The current date object
192
+ */
193
+ get date() {
194
+ return this.#date;
195
+ }
196
+
197
+ async connectedCallback() {
198
+ const componentStyles = PDS.createStylesheet(
199
+ /*css*/
200
+ `
201
+ .calendar {
202
+ display: grid;
203
+ width: 100%;
204
+ min-height: 400px;
205
+ grid-template-columns: repeat(7, minmax(50px, 1fr));
206
+ grid-template-rows: 50px;
207
+ grid-auto-rows: 60px;
208
+ overflow: auto;
209
+ position: relative;
210
+ }
211
+
212
+ .calendar-container {
213
+ background-color: var(--surface-bg);
214
+ margin: auto;
215
+ overflow: visible;
216
+ box-shadow: var(--shadow-lg);
217
+ border-radius: var(--radius-lg);
218
+ position: relative;
219
+ }
220
+
221
+ .calendar-header {
222
+ display: grid;
223
+ grid-template-columns: auto 1fr auto;
224
+
225
+ padding: var(--spacing-5) 0;
226
+ border-bottom: var(--border-width-thin) solid var(--surface-border);
227
+
228
+ .month-name {
229
+ margin: 0;
230
+ font-size: var(--font-size-lg);
231
+ }
232
+ }
233
+
234
+ button.btn-xs {
235
+ min-height: 0 !important;
236
+ }
237
+
238
+ .day-name {
239
+ text-align: center;
240
+ font-size: var(--font-size-sm);
241
+ }
242
+
243
+ .current-month {
244
+ cursor: pointer;
245
+ text-align: center;
246
+ font-size: var(--font-size-xs);
247
+ h3,
248
+ h3 {
249
+ margin: 0;
250
+ }
251
+ }
252
+
253
+ .year {
254
+ margin: var(--spacing-1) 0 0 0;
255
+ font-size: var(--font-size-sm);
256
+ font-weight: var(--font-weight-semibold);
257
+ }
258
+
259
+ .day {
260
+ border-bottom: var(--border-width-thin) solid var(--surface-base-border);
261
+ border-right: var(--border-width-thin) solid var(--surface-base-border);
262
+ padding: var(--spacing-1);
263
+ font-size: var(--font-size-xs);
264
+ box-sizing: border-box;
265
+ color: var(--surface-text-secondary);
266
+ position: relative;
267
+ pointer-events: none;
268
+ z-index: 1;
269
+ overflow: hidden;
270
+
271
+ &:nth-of-type(7n + 7) {
272
+ border-right: 0;
273
+ }
274
+
275
+ &:nth-of-type(n + 1):nth-of-type(-n + 7) {
276
+ grid-row: 2;
277
+ }
278
+
279
+ &:nth-of-type(n + 8):nth-of-type(-n + 14) {
280
+ grid-row: 3;
281
+ }
282
+
283
+ &:nth-of-type(n + 15):nth-of-type(-n + 21) {
284
+ grid-row: 4;
285
+ }
286
+
287
+ &:nth-of-type(n + 22):nth-of-type(-n + 28) {
288
+ grid-row: 5;
289
+ }
290
+
291
+ &:nth-of-type(n + 29):nth-of-type(-n + 35) {
292
+ grid-row: 6;
293
+ }
294
+
295
+ &:nth-of-type(7n + 1) {
296
+ grid-column: 1/1;
297
+ }
298
+
299
+ &:nth-of-type(7n + 2) {
300
+ grid-column: 2/2;
301
+ }
302
+
303
+ &:nth-of-type(7n + 3) {
304
+ grid-column: 3/3;
305
+ }
306
+
307
+ &:nth-of-type(7n + 4) {
308
+ grid-column: 4/4;
309
+ }
310
+
311
+ &:nth-of-type(7n + 5) {
312
+ grid-column: 5/5;
313
+ }
314
+
315
+ &:nth-of-type(7n + 6) {
316
+ grid-column: 6/6;
317
+ }
318
+
319
+ &:nth-of-type(7n + 7) {
320
+ grid-column: 7/7;
321
+ }
322
+
323
+ .nr {
324
+ position: absolute;
325
+ top: var(--spacing-1);
326
+ right: var(--spacing-1);
327
+ z-index: 2;
328
+ }
329
+
330
+ &.has-events {
331
+ cursor: pointer;
332
+ pointer-events: all;
333
+ .nr {
334
+ font-weight: var(--font-weight-bold);
335
+ color: var(--color-primary-500);
336
+ }
337
+ }
338
+ }
339
+
340
+ .day-disabled {
341
+ opacity: 0.4;
342
+ cursor: not-allowed;
343
+ }
344
+
345
+ .day-name {
346
+ font-size: var(--font-size-xs);
347
+ text-transform: uppercase;
348
+ color: var(--surface-text-secondary);
349
+ text-align: center;
350
+ border-bottom: var(--border-width-thin) solid var(--surface-border);
351
+ line-height: 50px;
352
+ font-weight: var(--font-weight-medium);
353
+ }
354
+
355
+
356
+ .day-today {
357
+ border: var(--border-width-thick) solid var(--color-primary-500) !important;
358
+ }
359
+
360
+ .task {
361
+ border-left-width: var(--border-width-thick);
362
+ padding: var(--spacing-1) var(--spacing-2);
363
+ border-left-style: solid;
364
+ font-size: var(--font-size-sm);
365
+ position: relative;
366
+ margin-bottom: var(--spacing-1);
367
+ background: var(--surface-hover);
368
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
369
+ }
370
+
371
+ .task--warning {
372
+ border-left-color: var(--color-warning-500);
373
+ }
374
+
375
+ .task--danger {
376
+ border-left-color: var(--color-danger-500);
377
+ }
378
+
379
+ .task--info {
380
+ border-left-color: var(--color-info-500);
381
+ }
382
+
383
+ .task--primary {
384
+ border-left-color: var(--color-primary-500);
385
+ }
386
+
387
+ .task__detail {
388
+ color: var(--surface-text);
389
+ box-sizing: border-box;
390
+ z-index: 2;
391
+
392
+ h3 {
393
+ font-weight: var(--font-weight-normal);
394
+ font-size: var(--font-size-xs);
395
+ margin: 0;
396
+
397
+ &:hover {
398
+ + p {
399
+ display: block;
400
+ }
401
+ }
402
+ }
403
+
404
+ p {
405
+ display: none;
406
+ margin-top: var(--spacing-1);
407
+ font-size: var(--font-size-xs);
408
+ margin-bottom: 0;
409
+ font-weight: var(--font-weight-medium);
410
+ }
411
+ }
412
+
413
+ /* Day expansion styles */
414
+ .calendar.day-expanded {
415
+ .day-name {
416
+ opacity: 0;
417
+ pointer-events: none;
418
+ transition: opacity 0.2s;
419
+ }
420
+
421
+ .day:not(.expanded-cell) {
422
+ opacity: 0;
423
+ pointer-events: none;
424
+ transition: opacity 0.2s;
425
+ }
426
+ }
427
+
428
+ .day.has-events {
429
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
430
+ }
431
+
432
+ .expanded-cell {
433
+ position: absolute !important;
434
+ top: 0 !important;
435
+ left: 0 !important;
436
+ right: 0 !important;
437
+ bottom: 0 !important;
438
+ width: auto !important;
439
+ height: auto !important;
440
+ grid-column: unset !important;
441
+ grid-row: unset !important;
442
+ z-index: 1000 !important;
443
+ background: var(--surface-bg) !important;
444
+ padding: 2rem !important;
445
+ overflow-y: auto !important;
446
+ border: none !important;
447
+ cursor: pointer !important;
448
+
449
+ .nr {
450
+ font-size: var(--font-size-2xl) !important;
451
+ font-weight: var(--font-weight-bold) !important;
452
+ }
453
+
454
+ .task {
455
+ font-size: var(--font-size-base) !important;
456
+ padding: var(--spacing-4) !important;
457
+ margin-bottom: var(--spacing-3) !important;
458
+
459
+ h3 {
460
+ font-size: var(--font-size-lg) !important;
461
+ }
462
+ }
463
+ }
464
+
465
+
466
+
467
+ `
468
+ );
469
+
470
+ // Adopt primitives (buttons), components (.alert classes), and toaster-specific styles
471
+ await PDS.adoptLayers(
472
+ this.shadowRoot,
473
+ ["primitives", "components"],
474
+ [componentStyles]
475
+ );
476
+
477
+ queueMicrotask(() => {
478
+ this.setupPaging();
479
+ });
480
+ }
481
+
482
+ /**
483
+ * Attaches one or more event listeners with optional event delegation
484
+ * @param {String} eventNames - name(s) of the event to listen to, space-separated
485
+ * @param {Function|Object} funcOrObject - function, or object with selectors as keys and functions as values
486
+ * @private
487
+ */
488
+ on(eventNames, funcOrObject) {
489
+ eventNames.split(" ").forEach((eventName) => {
490
+ if (typeof funcOrObject === "function")
491
+ this.addEventListener(eventName, funcOrObject);
492
+ else {
493
+ this.addEventListener(eventName, (e) => {
494
+ // Walk up the composed path to find matching elements
495
+ const path = e.composedPath();
496
+
497
+ Object.keys(funcOrObject).forEach((selector) => {
498
+ // Check each element in the path for a match
499
+ for (const element of path) {
500
+ if (element.matches && element.matches(selector)) {
501
+ funcOrObject[selector].apply(this, [e]);
502
+ break; // Stop after first match
503
+ }
504
+ }
505
+ });
506
+ });
507
+ }
508
+ });
509
+ }
510
+
511
+ /**
512
+ * Renders the calendar HTML structure
513
+ * @returns {String} HTML string for the calendar
514
+ * @private
515
+ */
516
+ render() {
517
+ this.month = this.date.getMonth();
518
+ this.year = this.date.getFullYear();
519
+ this.daysInMonth = new Date(this.year, this.month + 1, 0).getDate();
520
+ this.startDay = new Date(this.year, this.month, 0).getDay();
521
+
522
+ const calendarHtml = /*html*/ `
523
+ <div class="calendar-container">
524
+ <nav class="calendar-header">
525
+ <button class="btn-outline prev btn-xs"><pds-icon icon="arrow-left" size="xs"></pds-icon></button>
526
+ <div class="current-month">
527
+ <h3 class="month-name">${this.#monthNames[this.month]}</h3>
528
+ <h4 class="year">${this.year}</h4>
529
+ </div>
530
+ <button class="btn-outline next btn-xs"><pds-icon icon="arrow-right" size="xs"></pds-icon></button>
531
+ </nav>
532
+
533
+ <div class="calendar">
534
+ ${this.getDayNamesHtml()}
535
+ ${this.getDaysHtml()}
536
+ </div>
537
+ </div>
538
+ `;
539
+
540
+ clearTimeout(this.renderedTimeout);
541
+
542
+ this.renderedTimeout = setTimeout(() => {
543
+ this.dispatchRendered();
544
+ }, 100);
545
+
546
+ return calendarHtml;
547
+ }
548
+
549
+ /**
550
+ * Marks the component as rendered
551
+ * @private
552
+ */
553
+ rendered() {
554
+ this.isRendered = true;
555
+ }
556
+
557
+ /**
558
+ * Re-renders the calendar
559
+ * @private
560
+ */
561
+ reRender() {
562
+ this.shadowRoot.innerHTML = this.render();
563
+ queueMicrotask(() => {
564
+ this.setupDayExpansion();
565
+ });
566
+ }
567
+
568
+ /**
569
+ * Refreshes the calendar display
570
+ * @public
571
+ */
572
+ refresh() {
573
+ this.reRender();
574
+ }
575
+
576
+ /**
577
+ * Sets up month navigation with accelerating paging on hold
578
+ * @private
579
+ */
580
+ setupPaging() {
581
+ const MIN_CHANGE_MS = 2,
582
+ logGrowth = (x, base = 10, growthRate = 4) => {
583
+ return Math.exp((Math.log(x) / Math.log(base)) * growthRate);
584
+ };
585
+
586
+ let timeout,
587
+ timeoutMs = -1,
588
+ timeoutChangeMs = 2,
589
+ direction;
590
+
591
+ const moveDate = () => {
592
+ this.date.setMonth(this.date.getMonth() + direction);
593
+ queueMicrotask(() => {
594
+ this.reRender();
595
+ });
596
+ },
597
+ moveDateRecursive = () => {
598
+ moveDate();
599
+ timeout = setTimeout(() => {
600
+ moveDateRecursive();
601
+
602
+ timeoutChangeMs = logGrowth(timeoutChangeMs);
603
+ timeoutMs -= timeoutChangeMs;
604
+ }, timeoutMs);
605
+ },
606
+ startMoveDate = (newDirection) => {
607
+ timeoutChangeMs = MIN_CHANGE_MS;
608
+ direction = newDirection;
609
+ timeoutMs = 200;
610
+ timeout = setTimeout(() => {
611
+ moveDateRecursive();
612
+ }, timeoutMs);
613
+ },
614
+ stopMoveDate = () => {
615
+ if (timeoutChangeMs === MIN_CHANGE_MS) {
616
+ moveDate();
617
+ }
618
+ clearTimeout(timeout);
619
+ timeoutMs = -1;
620
+ timeout = null;
621
+ };
622
+
623
+ this.on("mousedown touchstart", {
624
+ ".next": (e) => {
625
+ e.preventDefault();
626
+ startMoveDate(1);
627
+ },
628
+ ".prev": (e) => {
629
+ e.preventDefault();
630
+ startMoveDate(-1);
631
+ },
632
+ });
633
+
634
+ this.on("mouseup pointerup", {
635
+ ".next, .prev": (e) => {
636
+ e.preventDefault();
637
+ stopMoveDate();
638
+ },
639
+ });
640
+ }
641
+
642
+ /**
643
+ * Sets up day expansion functionality for viewing events
644
+ * @private
645
+ */
646
+ setupDayExpansion() {
647
+ if (this.expansionSetup) return; // Prevent multiple setups
648
+ this.expansionSetup = true;
649
+
650
+ let expandedDay = null;
651
+
652
+ // Use direct event delegation on shadowRoot
653
+ this.shadowRoot.addEventListener("click", async (e) => {
654
+ const month = e.target.closest(".current-month");
655
+ if (month) {
656
+ this.date = Date.now();
657
+ this.reRender();
658
+ return;
659
+ }
660
+
661
+ const cell = e.target.closest(".day.has-events[data-day]");
662
+ if (!cell) return;
663
+
664
+ const day = cell.dataset.day;
665
+ const calendar = this.shadowRoot.querySelector(".calendar");
666
+
667
+ // Toggle if clicking same day
668
+ if (expandedDay === day) {
669
+ await this.collapseDay(calendar, cell);
670
+ expandedDay = null;
671
+ } else {
672
+ // Collapse previous day if exists
673
+ if (expandedDay) {
674
+ const prevCell = this.shadowRoot.querySelector(
675
+ `[data-day="${expandedDay}"]`
676
+ );
677
+ await this.collapseDay(calendar, prevCell);
678
+ }
679
+ await this.expandDay(calendar, cell);
680
+ expandedDay = day;
681
+ }
682
+ });
683
+ }
684
+
685
+ /**
686
+ * Expands a day cell to full view
687
+ * @param {HTMLElement} calendar - The calendar container element
688
+ * @param {HTMLElement} cell - The day cell to expand
689
+ * @private
690
+ */
691
+ async expandDay(calendar, cell) {
692
+ // Capture the cell's current position and size
693
+ const calendarRect = calendar.getBoundingClientRect();
694
+ const cellRect = cell.getBoundingClientRect();
695
+
696
+ const relativeTop = cellRect.top - calendarRect.top;
697
+ const relativeLeft = cellRect.left - calendarRect.left;
698
+
699
+ // Set initial absolute position to match grid position
700
+ cell.style.position = "absolute";
701
+ cell.style.top = `${relativeTop}px`;
702
+ cell.style.left = `${relativeLeft}px`;
703
+ cell.style.width = `${cellRect.width}px`;
704
+ cell.style.height = `${cellRect.height}px`;
705
+ cell.style.gridColumn = "unset";
706
+ cell.style.gridRow = "unset";
707
+
708
+ // Force a reflow
709
+ cell.offsetHeight;
710
+
711
+ // Now add classes to trigger transition to expanded state
712
+ calendar.classList.add("day-expanded");
713
+ cell.classList.add("expanded-cell");
714
+ }
715
+
716
+ /**
717
+ * Collapses an expanded day cell back to grid view
718
+ * @param {HTMLElement} calendar - The calendar container element
719
+ * @param {HTMLElement} cell - The day cell to collapse
720
+ * @private
721
+ */
722
+ async collapseDay(calendar, cell) {
723
+ if (!cell) return;
724
+
725
+ // Remove expanded classes
726
+ calendar.classList.remove("day-expanded");
727
+ cell.classList.remove("expanded-cell");
728
+
729
+ // Wait for transition to complete
730
+ await new Promise((resolve) => setTimeout(resolve, 400));
731
+
732
+ // Reset to grid positioning
733
+ cell.style.position = "";
734
+ cell.style.top = "";
735
+ cell.style.left = "";
736
+ cell.style.width = "";
737
+ cell.style.height = "";
738
+ cell.style.gridColumn = "";
739
+ cell.style.gridRow = "";
740
+ }
741
+
742
+ /**
743
+ * Dispatches the month-rendered event with fill capability
744
+ * @fires pds-calendar#month-rendered
745
+ * @private
746
+ */
747
+ dispatchRendered() {
748
+ this.dispatchEvent(
749
+ new CustomEvent("month-rendered", {
750
+ detail: {
751
+ date: this.date,
752
+ year: this.year,
753
+ month: this.month,
754
+ fill: (data) => {
755
+ if (!data) return;
756
+ let dayDivs = this.shadowRoot.querySelectorAll(`div[data-day]`);
757
+
758
+ for (const day of Object.keys(data)) {
759
+ const dayDiv = dayDivs[parseInt(day)];
760
+ const list = data[day];
761
+ if (list) {
762
+ dayDiv.classList.add("has-events");
763
+ for (const item of list) {
764
+ const html = /*html*/ `<div class="task task--${
765
+ item.type || "info"
766
+ }">
767
+ <div class="task__detail">
768
+ <h3>
769
+ ${item.title}
770
+ </h3>
771
+ <p>
772
+ Test
773
+ </p>
774
+ </div>
775
+ </div>`;
776
+
777
+ dayDiv.appendChild(parseHTML(html)[0]);
778
+ }
779
+ }
780
+ }
781
+ },
782
+ },
783
+
784
+ bubbles: true
785
+ })
786
+ );
787
+ }
788
+
789
+ /**
790
+ * Generates HTML for day name headers
791
+ * @returns {String} HTML string for day names
792
+ * @private
793
+ */
794
+ getDayNamesHtml() {
795
+ const html = new HTMLBuilder();
796
+ //let n = this.startDay;
797
+ for (let i = 0; i <= 6; i++) {
798
+ const dayName = this.#dayNames[i].substring(0, 3);
799
+ html.add(/*html*/ `<span class="day-name">${dayName}</span>`);
800
+ }
801
+ return html.toHTML();
802
+ }
803
+
804
+ /**
805
+ * Generates HTML for calendar day cells
806
+ * @returns {String} HTML string for day cells
807
+ * @private
808
+ */
809
+ getDaysHtml() {
810
+ const html = new HTMLBuilder();
811
+ const now = new Date();
812
+ const todayDay = now.getDate();
813
+ const todayMonth = now.getMonth();
814
+ const todayYear = now.getFullYear();
815
+ const isCurrentMonth =
816
+ this.month === todayMonth && this.year === todayYear;
817
+
818
+ for (let i = 0; i < this.startDay; i++) {
819
+ html.add(/*html*/ `<div class="day day-disabled"></div>`);
820
+ }
821
+ for (let i = 1; i <= this.daysInMonth; i++) {
822
+ const isTodayClass =
823
+ isCurrentMonth && i === todayDay ? "day-today" : "";
824
+ html.add(/*html*/ `
825
+ <div data-day="${i}" class="day ${isTodayClass}">
826
+ <span class="nr">${i}<span>
827
+ </div>`);
828
+ }
829
+ const endDay =
830
+ 6 - new Date(this.year, this.month, this.daysInMonth).getDay();
831
+ for (let i = 1; i <= endDay; i++) {
832
+ html.add(/*html*/ `<div class="day day-disabled"></div>`);
833
+ }
834
+ return html.toHTML();
835
+ }
836
+ }
837
+ );