tyrell-components 1.0.0-RC14 → 1.0.0-RC15

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 (113) hide show
  1. package/README.md +9 -1
  2. package/css/tyrell-theme.css +25 -2
  3. package/css/tyrell.css +4 -0
  4. package/dist/tyrell-colors-static.css +1 -316
  5. package/dist/tyrell-theme.css +1 -1443
  6. package/dist/tyrell.css +1 -1390
  7. package/dist/tyrell.js +1 -1
  8. package/lib/base/ty-component.js +196 -196
  9. package/lib/components/button.d.ts +1 -1
  10. package/lib/components/button.js +261 -259
  11. package/lib/components/calendar-month.d.ts +2 -2
  12. package/lib/components/calendar-month.js +439 -416
  13. package/lib/components/calendar-navigation.js +238 -238
  14. package/lib/components/calendar.d.ts +5 -3
  15. package/lib/components/calendar.js +611 -602
  16. package/lib/components/checkbox.d.ts +1 -1
  17. package/lib/components/checkbox.js +273 -272
  18. package/lib/components/copy.d.ts +1 -1
  19. package/lib/components/copy.js +280 -278
  20. package/lib/components/date-picker.d.ts +8 -1
  21. package/lib/components/date-picker.js +1128 -1108
  22. package/lib/components/file-upload.js +404 -404
  23. package/lib/components/icon.js +141 -141
  24. package/lib/components/input.d.ts +1 -1
  25. package/lib/components/input.js +593 -596
  26. package/lib/components/modal.js +319 -319
  27. package/lib/components/option.js +167 -167
  28. package/lib/components/popup.js +233 -233
  29. package/lib/components/radio.d.ts +2 -2
  30. package/lib/components/radio.js +407 -406
  31. package/lib/components/resize-observer.js +67 -67
  32. package/lib/components/scroll-container.js +292 -294
  33. package/lib/components/select.d.ts +71 -12
  34. package/lib/components/select.js +1605 -1529
  35. package/lib/components/selected-tags.d.ts +1 -1
  36. package/lib/components/selected-tags.js +188 -182
  37. package/lib/components/step.js +62 -62
  38. package/lib/components/switch.d.ts +1 -1
  39. package/lib/components/switch.js +214 -213
  40. package/lib/components/tab.js +62 -62
  41. package/lib/components/tabs.d.ts +1 -0
  42. package/lib/components/tabs.js +523 -492
  43. package/lib/components/tag.d.ts +1 -1
  44. package/lib/components/tag.js +236 -235
  45. package/lib/components/textarea.d.ts +1 -1
  46. package/lib/components/textarea.js +457 -456
  47. package/lib/components/tooltip.js +283 -283
  48. package/lib/components/wizard.js +384 -384
  49. package/lib/icons/heroicons/micro.js +392 -392
  50. package/lib/icons/heroicons/mini.js +382 -382
  51. package/lib/icons/heroicons/outline.js +337 -337
  52. package/lib/icons/heroicons/solid.js +407 -407
  53. package/lib/icons/lucide.js +22843 -22843
  54. package/lib/icons/material/filled.js +9653 -9653
  55. package/lib/icons/material/outlined.js +10271 -10271
  56. package/lib/icons/material/round.js +10495 -10495
  57. package/lib/icons/material/sharp.js +9905 -9905
  58. package/lib/icons/material/two-tone.js +12792 -12792
  59. package/lib/index.js +21 -21
  60. package/lib/styles/button.js +200 -302
  61. package/lib/styles/calendar-month.js +123 -183
  62. package/lib/styles/calendar-navigation.d.ts +1 -1
  63. package/lib/styles/calendar-navigation.js +64 -79
  64. package/lib/styles/calendar.d.ts +1 -1
  65. package/lib/styles/calendar.js +9 -9
  66. package/lib/styles/checkbox.js +45 -72
  67. package/lib/styles/copy.js +67 -91
  68. package/lib/styles/custom-scrollbar.js +80 -100
  69. package/lib/styles/date-picker.js +261 -331
  70. package/lib/styles/field-size.d.ts +6 -0
  71. package/lib/styles/field-size.js +91 -0
  72. package/lib/styles/file-upload.js +139 -169
  73. package/lib/styles/icon.js +92 -156
  74. package/lib/styles/input.js +125 -241
  75. package/lib/styles/modal.js +67 -102
  76. package/lib/styles/option.d.ts +1 -1
  77. package/lib/styles/option.js +47 -67
  78. package/lib/styles/popup.js +34 -65
  79. package/lib/styles/radio.js +69 -96
  80. package/lib/styles/resize-observer.js +2 -5
  81. package/lib/styles/scroll-container.js +100 -130
  82. package/lib/styles/select-base.js +596 -773
  83. package/lib/styles/select.js +140 -228
  84. package/lib/styles/step.js +22 -29
  85. package/lib/styles/switch.js +34 -55
  86. package/lib/styles/tab.d.ts +1 -1
  87. package/lib/styles/tab.js +22 -29
  88. package/lib/styles/tabs.d.ts +1 -1
  89. package/lib/styles/tabs.js +186 -180
  90. package/lib/styles/tag.js +135 -188
  91. package/lib/styles/textarea.d.ts +1 -1
  92. package/lib/styles/textarea.js +205 -271
  93. package/lib/styles/tooltip.js +3 -3
  94. package/lib/styles/wizard.js +170 -226
  95. package/lib/types/common.d.ts +2 -0
  96. package/lib/types/common.js +6 -5
  97. package/lib/utils/calendar-utils.js +100 -100
  98. package/lib/utils/custom-scrollbar.js +272 -272
  99. package/lib/utils/flavor-sheet.js +14 -14
  100. package/lib/utils/icon-registry.js +127 -127
  101. package/lib/utils/loader-registry.js +3 -3
  102. package/lib/utils/locale.js +34 -34
  103. package/lib/utils/mobile.js +1 -1
  104. package/lib/utils/number-format.js +54 -54
  105. package/lib/utils/positioning.js +212 -212
  106. package/lib/utils/property-capture.js +36 -36
  107. package/lib/utils/property-manager.js +125 -125
  108. package/lib/utils/resize-observer.js +23 -23
  109. package/lib/utils/scroll-lock.js +57 -57
  110. package/lib/utils/styles.js +17 -17
  111. package/lib/version.d.ts +1 -1
  112. package/lib/version.js +1 -1
  113. package/package.json +4 -4
@@ -4,432 +4,455 @@ import { calendarMonthStyles, calendarMonthCustomFlavorCss } from '../styles/cal
4
4
  import { getCalendarMonthDays, getLocalizedWeekdays, parseISODate } from '../utils/calendar-utils.js';
5
5
  import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
6
6
  function defaultDayContent(dayContext) {
7
- return dayContext.dayInMonth.toString();
7
+ return dayContext.dayInMonth.toString();
8
8
  }
9
9
  function getDefaultDayClasses(dayContext) {
10
- const classes = ['calendar-day'];
11
- if (dayContext.today)
12
- classes.push('today');
13
- if (dayContext.weekend)
14
- classes.push('weekend');
15
- if (dayContext.otherMonth)
16
- classes.push('other-month');
17
- if (dayContext.isSelected)
18
- classes.push('selected');
19
- return classes;
10
+ const classes = ['calendar-day'];
11
+ if (dayContext.today)
12
+ classes.push('today');
13
+ if (dayContext.weekend)
14
+ classes.push('weekend');
15
+ if (dayContext.otherMonth)
16
+ classes.push('other-month');
17
+ if (dayContext.isSelected)
18
+ classes.push('selected');
19
+ return classes;
20
20
  }
21
21
  function isDOMElement(value) {
22
- return value instanceof HTMLElement;
22
+ return value instanceof HTMLElement;
23
23
  }
24
24
  function isoToUTCTimestamp(iso) {
25
- if (!iso)
26
- return null;
27
- const parsed = parseISODate(iso);
28
- if (!parsed)
29
- return null;
30
- return Date.UTC(parsed.year, parsed.month - 1, parsed.day);
25
+ if (!iso)
26
+ return null;
27
+ const parsed = parseISODate(iso);
28
+ if (!parsed)
29
+ return null;
30
+ return Date.UTC(parsed.year, parsed.month - 1, parsed.day);
31
31
  }
32
32
  function normalizeSizeValue(value) {
33
- if (!value || value === '')
34
- return null;
35
- const strValue = String(value);
36
- if (/^\d+(\.\d+)?$/.test(strValue)) {
37
- return `${strValue}px`;
38
- }
39
- return strValue;
33
+ if (!value || value === '')
34
+ return null;
35
+ const strValue = String(value);
36
+ if (/^\d+(\.\d+)?$/.test(strValue)) {
37
+ return `${strValue}px`;
38
+ }
39
+ return strValue;
40
40
  }
41
41
  export class TyCalendarMonth extends HTMLElement {
42
- static get observedAttributes() {
43
- return ['locale', 'size', 'min', 'max', 'flavor'];
44
- }
45
- constructor() {
46
- super();
47
- this._locale = 'en-US';
48
- this._size = 'md';
49
- this._customFlavorSheet = null;
50
- this._focusedIndex = null;
51
- this._cellElements = [];
52
- const today = new Date();
53
- this._displayYear = today.getFullYear();
54
- this._displayMonth = today.getMonth() + 1;
55
- this.attachShadow({ mode: 'open' });
56
- }
57
- connectedCallback() {
58
- if (!this._displayMonth) {
59
- this._displayMonth = new Date().getMonth() + 1;
60
- }
61
- if (!this._displayYear) {
62
- this._displayYear = new Date().getFullYear();
63
- }
64
- if (!this._locale) {
65
- this._locale = this.getAttribute('locale') || 'en-US';
66
- }
67
- const sizeAttr = this.getAttribute('size');
68
- if (sizeAttr && (sizeAttr === 'sm' || sizeAttr === 'md' || sizeAttr === 'lg')) {
69
- this._size = sizeAttr;
70
- }
71
- const plainDayContentFn = this.dayContentFn;
72
- if (plainDayContentFn && !this._dayContentFn) {
73
- this._dayContentFn = plainDayContentFn;
74
- delete this.dayContentFn;
75
- }
76
- const plainCustomCSS = this.customCSS;
77
- if (plainCustomCSS && !this._customCSS) {
78
- this._customCSS = plainCustomCSS;
79
- delete this.customCSS;
80
- }
81
- this._localeObserver = observeLocaleChanges(this, () => {
82
- this.render();
83
- });
84
- this._syncCustomFlavor();
85
- this.render();
86
- }
87
- disconnectedCallback() {
88
- if (this._localeObserver) {
89
- this._localeObserver();
90
- this._localeObserver = undefined;
91
- }
92
- }
93
- attributeChangedCallback(name, _oldValue, newValue) {
94
- if (name === 'locale') {
95
- this._locale = newValue || 'en-US';
96
- this.render();
97
- }
98
- else if (name === 'size') {
99
- if (newValue === 'sm' || newValue === 'md' || newValue === 'lg') {
100
- this._size = newValue;
101
- this.render();
102
- }
103
- }
104
- else if (name === 'min' || name === 'max') {
105
- this[name === 'min' ? '_min' : '_max'] = newValue ?? undefined;
106
- this.render();
107
- }
108
- else if (name === 'flavor') {
109
- this._syncCustomFlavor();
110
- }
111
- }
112
- _syncCustomFlavor() {
113
- this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.getAttribute('flavor'), ({ base }) => calendarMonthCustomFlavorCss(base));
114
- }
115
- get displayYear() {
116
- return this._displayYear;
117
- }
118
- set displayYear(value) {
119
- if (this._displayYear !== value) {
120
- this._displayYear = value;
121
- this._focusedIndex = null;
122
- this.render();
123
- }
124
- }
125
- get displayMonth() {
126
- return this._displayMonth;
127
- }
128
- set displayMonth(value) {
129
- if (this._displayMonth !== value) {
130
- this._displayMonth = value;
131
- this._focusedIndex = null;
132
- this.render();
133
- }
134
- }
135
- get locale() {
136
- return getEffectiveLocale(this, this.getAttribute('locale'));
137
- }
138
- set locale(value) {
139
- if (this._locale !== value) {
140
- this._locale = value;
141
- this.render();
142
- }
143
- }
144
- get size() {
145
- return this._size;
146
- }
147
- set size(value) {
148
- if (this._size !== value) {
149
- this._size = value;
150
- this.render();
151
- }
152
- }
153
- get flavor() {
154
- return this.getAttribute('flavor') || 'primary';
155
- }
156
- set flavor(value) {
157
- if (value)
158
- this.setAttribute('flavor', value);
159
- else
160
- this.removeAttribute('flavor');
161
- }
162
- get width() {
163
- return this._width;
164
- }
165
- set width(value) {
166
- if (this._width !== value) {
167
- this._width = value;
168
- this.applyWidthProperties();
169
- }
170
- }
171
- get minWidth() {
172
- return this._minWidth;
173
- }
174
- set minWidth(value) {
175
- if (this._minWidth !== value) {
176
- this._minWidth = value;
177
- this.applyWidthProperties();
178
- }
179
- }
180
- get maxWidth() {
181
- return this._maxWidth;
182
- }
183
- set maxWidth(value) {
184
- if (this._maxWidth !== value) {
185
- this._maxWidth = value;
186
- this.applyWidthProperties();
187
- }
188
- }
189
- get dayContentFn() {
190
- return this._dayContentFn;
191
- }
192
- set dayContentFn(fn) {
193
- if (this._dayContentFn !== fn) {
194
- this._dayContentFn = fn;
195
- this.render();
196
- }
197
- }
198
- get customCSS() {
199
- return this._customCSS;
200
- }
201
- set customCSS(sheet) {
202
- if (this._customCSS !== sheet) {
203
- this._customCSS = sheet;
204
- this.applyCustomCSS();
205
- }
206
- }
207
- get min() {
208
- return this._min;
209
- }
210
- set min(value) {
211
- if (this._min !== value) {
212
- this._min = value;
213
- this.render();
214
- }
215
- }
216
- get max() {
217
- return this._max;
218
- }
219
- set max(value) {
220
- if (this._max !== value) {
221
- this._max = value;
222
- this.render();
223
- }
224
- }
225
- get value() {
226
- return this._value;
227
- }
228
- set value(timestamp) {
229
- const newValue = timestamp ?? undefined;
230
- if (this._value !== newValue) {
231
- this._value = newValue;
232
- this.render();
233
- }
234
- }
235
- applyWidthProperties() {
236
- const normalizedWidth = normalizeSizeValue(this._width);
237
- const normalizedMinWidth = normalizeSizeValue(this._minWidth);
238
- const normalizedMaxWidth = normalizeSizeValue(this._maxWidth);
239
- if (normalizedWidth) {
240
- this.style.setProperty('--calendar-width', normalizedWidth);
241
- }
242
- else {
243
- this.style.removeProperty('--calendar-width');
244
- }
245
- if (normalizedMinWidth) {
246
- this.style.setProperty('--calendar-min-width', normalizedMinWidth);
247
- }
248
- else {
249
- this.style.removeProperty('--calendar-min-width');
250
- }
251
- if (normalizedMaxWidth) {
252
- this.style.setProperty('--calendar-max-width', normalizedMaxWidth);
253
- }
254
- else {
255
- this.style.removeProperty('--calendar-max-width');
256
- }
257
- }
258
- applyCustomCSS() {
259
- if (!this._customCSS || !this.shadowRoot)
260
- return;
261
- const existing = this.shadowRoot.adoptedStyleSheets;
262
- if (!existing.includes(this._customCSS)) {
263
- this.shadowRoot.adoptedStyleSheets = [...existing, this._customCSS];
264
- }
265
- }
266
- refresh() {
267
- this.render();
268
- }
269
- dispatchDayClick(dayContext, _domEvent) {
270
- const detail = {
271
- dayContext,
272
- value: dayContext.value,
273
- year: dayContext.year,
274
- month: dayContext.month,
275
- day: dayContext.dayInMonth,
276
- isHoliday: dayContext.holiday,
277
- isToday: dayContext.today,
278
- isWeekend: dayContext.weekend,
279
- isOtherMonth: dayContext.otherMonth,
280
- };
281
- for (const eventType of ['day-click', 'day-select']) {
282
- const event = new CustomEvent(eventType, {
283
- detail,
284
- bubbles: true,
285
- cancelable: true,
286
- });
287
- this.dispatchEvent(event);
288
- }
289
- }
290
- renderDayCell(dayContext, disabled, isRoving) {
291
- const dayElement = document.createElement('div');
292
- const content = this._dayContentFn
293
- ? this._dayContentFn(dayContext)
294
- : defaultDayContent(dayContext);
295
- if (this._dayContentFn && content && !isDOMElement(content) && typeof content !== 'string') {
296
- throw new Error(`Custom dayContentFn must return a DOM element or string, got: ${typeof content}. ` +
297
- `Use document.createElement() to create DOM elements.`);
298
- }
299
- const classes = getDefaultDayClasses(dayContext);
300
- if (disabled)
301
- classes.push('disabled');
302
- dayElement.className = classes.join(' ');
303
- if (typeof content === 'string') {
304
- dayElement.textContent = content;
305
- }
306
- else if (isDOMElement(content)) {
307
- dayElement.appendChild(content);
308
- }
309
- dayElement.setAttribute('role', 'gridcell');
310
- dayElement.setAttribute('aria-label', new Intl.DateTimeFormat(this._locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })
311
- .format(new Date(dayContext.localValue)));
312
- dayElement.setAttribute('aria-selected', String(!!dayContext.isSelected));
313
- if (dayContext.today)
314
- dayElement.setAttribute('aria-current', 'date');
315
- if (disabled) {
316
- dayElement.setAttribute('aria-disabled', 'true');
317
- dayElement.setAttribute('tabindex', '-1');
318
- }
319
- else {
320
- dayElement.setAttribute('tabindex', isRoving ? '0' : '-1');
321
- dayElement.addEventListener('pointerdown', (event) => {
322
- event.preventDefault();
323
- this.dispatchDayClick(dayContext, event);
324
- });
325
- dayElement.addEventListener('keydown', (event) => {
326
- const ke = event;
327
- if (ke.key === 'Enter' || ke.key === ' ') {
328
- ke.preventDefault();
329
- this.dispatchDayClick(dayContext, event);
330
- }
331
- });
332
- }
333
- return dayElement;
334
- }
335
- render() {
336
- const root = this.shadowRoot;
337
- if (!root)
338
- return;
339
- ensureStyles(root, { css: calendarMonthStyles, id: 'ty-calendar-month' });
340
- this.applyCustomCSS();
341
- this.applyWidthProperties();
342
- let selection;
343
- if (this._value) {
344
- const date = new Date(this._value);
345
- selection = {
346
- year: date.getFullYear(),
347
- month: date.getMonth() + 1,
348
- day: date.getDate()
349
- };
350
- }
351
- const days = getCalendarMonthDays(this._displayYear, this._displayMonth, selection);
352
- const weekdays = getLocalizedWeekdays(this._locale, 'short');
353
- root.innerHTML = '';
354
- const calendarContainer = document.createElement('div');
355
- calendarContainer.className = `calendar-flex-container calendar-size-${this._size}`;
356
- calendarContainer.setAttribute('role', 'grid');
357
- calendarContainer.setAttribute('aria-label', new Intl.DateTimeFormat(this._locale, { month: 'long', year: 'numeric' })
358
- .format(new Date(this._displayYear, this._displayMonth - 1, 1)));
359
- const headerRow = document.createElement('div');
360
- headerRow.className = 'calendar-row calendar-header-row';
361
- headerRow.setAttribute('role', 'row');
362
- weekdays.forEach(weekday => {
363
- const headerCell = document.createElement('div');
364
- headerCell.className = 'calendar-cell calendar-header-cell';
365
- headerCell.setAttribute('role', 'columnheader');
366
- headerCell.textContent = weekday;
367
- headerRow.appendChild(headerCell);
368
- });
369
- calendarContainer.appendChild(headerRow);
370
- const dayWeeks = [];
371
- for (let i = 0; i < 42; i += 7) {
372
- dayWeeks.push(days.slice(i, i + 7));
373
- }
374
- const minTs = isoToUTCTimestamp(this._min);
375
- const maxTs = isoToUTCTimestamp(this._max);
376
- const disabledAt = (i) => {
377
- const v = days[i].value;
378
- return (minTs !== null && v < minTs) || (maxTs !== null && v > maxTs);
379
- };
380
- let defaultIndex = days.findIndex(d => d.isSelected);
381
- if (defaultIndex === -1)
382
- defaultIndex = days.findIndex(d => d.today);
383
- if (defaultIndex === -1)
384
- defaultIndex = 0;
385
- if (this._focusedIndex === null ||
386
- this._focusedIndex < 0 ||
387
- this._focusedIndex > 41 ||
388
- disabledAt(this._focusedIndex)) {
389
- this._focusedIndex = defaultIndex;
390
- }
391
- this._cellElements = [];
392
- let flatIndex = 0;
393
- dayWeeks.forEach(week => {
394
- const dayRow = document.createElement('div');
395
- dayRow.className = 'calendar-row calendar-day-row';
396
- dayRow.setAttribute('role', 'row');
397
- week.forEach(dayContext => {
398
- const disabled = (minTs !== null && dayContext.value < minTs) ||
399
- (maxTs !== null && dayContext.value > maxTs);
400
- const dayCell = this.renderDayCell(dayContext, disabled, flatIndex === this._focusedIndex);
401
- dayCell.className = `${dayCell.className} calendar-cell calendar-day-cell`;
402
- dayRow.appendChild(dayCell);
403
- this._cellElements[flatIndex] = dayCell;
404
- flatIndex++;
405
- });
406
- calendarContainer.appendChild(dayRow);
407
- });
408
- calendarContainer.addEventListener('keydown', (event) => {
409
- const ke = event;
410
- const delta = ke.key === 'ArrowLeft' ? -1 :
411
- ke.key === 'ArrowRight' ? 1 :
412
- ke.key === 'ArrowUp' ? -7 :
413
- ke.key === 'ArrowDown' ? 7 :
414
- 0;
415
- if (delta === 0)
416
- return;
417
- const currentIndex = this._focusedIndex ?? defaultIndex;
418
- const nextIndex = currentIndex + delta;
419
- if (nextIndex < 0 || nextIndex > 41 || disabledAt(nextIndex))
420
- return;
421
- ke.preventDefault();
422
- this._cellElements[currentIndex]?.setAttribute('tabindex', '-1');
423
- this._focusedIndex = nextIndex;
424
- const nextCell = this._cellElements[nextIndex];
425
- if (nextCell) {
426
- nextCell.setAttribute('tabindex', '0');
427
- nextCell.focus();
428
- }
429
- });
430
- root.appendChild(calendarContainer);
431
- }
42
+ static get observedAttributes() {
43
+ return ['display-year', 'display-month', 'locale', 'size', 'min', 'max', 'flavor'];
44
+ }
45
+ constructor() {
46
+ super();
47
+ this._locale = 'en-US';
48
+ this._size = 'md';
49
+ this._customFlavorSheet = null;
50
+ this._focusedIndex = null;
51
+ this._cellElements = [];
52
+ const today = new Date();
53
+ this._displayYear = today.getFullYear();
54
+ this._displayMonth = today.getMonth() + 1;
55
+ this.attachShadow({ mode: 'open' });
56
+ }
57
+ connectedCallback() {
58
+ if (!this._displayMonth) {
59
+ this._displayMonth = new Date().getMonth() + 1;
60
+ }
61
+ if (!this._displayYear) {
62
+ this._displayYear = new Date().getFullYear();
63
+ }
64
+ if (!this._locale) {
65
+ this._locale = this.getAttribute('locale') || 'en-US';
66
+ }
67
+ const sizeAttr = this.getAttribute('size');
68
+ if (sizeAttr && (sizeAttr === 'sm' || sizeAttr === 'md' || sizeAttr === 'lg')) {
69
+ this._size = sizeAttr;
70
+ }
71
+ const plainDayContentFn = this.dayContentFn;
72
+ if (plainDayContentFn && !this._dayContentFn) {
73
+ this._dayContentFn = plainDayContentFn;
74
+ delete this.dayContentFn;
75
+ }
76
+ const plainCustomCSS = this.customCSS;
77
+ if (plainCustomCSS && !this._customCSS) {
78
+ this._customCSS = plainCustomCSS;
79
+ delete this.customCSS;
80
+ }
81
+ this._localeObserver = observeLocaleChanges(this, () => {
82
+ this.render();
83
+ });
84
+ this._syncCustomFlavor();
85
+ this.render();
86
+ }
87
+ disconnectedCallback() {
88
+ if (this._localeObserver) {
89
+ this._localeObserver();
90
+ this._localeObserver = undefined;
91
+ }
92
+ }
93
+ attributeChangedCallback(name, _oldValue, newValue) {
94
+ if (name === 'display-year' || name === 'display-month') {
95
+ const n = Number(newValue);
96
+ const valid = Number.isInteger(n) && (name === 'display-year' ? n > 0 : n >= 1 && n <= 12);
97
+ if (valid) {
98
+ if (name === 'display-year')
99
+ this.displayYear = n;
100
+ else
101
+ this.displayMonth = n;
102
+ }
103
+ }
104
+ else if (name === 'locale') {
105
+ this._locale = newValue || 'en-US';
106
+ this.render();
107
+ }
108
+ else if (name === 'size') {
109
+ if (newValue === 'sm' || newValue === 'md' || newValue === 'lg') {
110
+ this._size = newValue;
111
+ this.render();
112
+ }
113
+ }
114
+ else if (name === 'min' || name === 'max') {
115
+ this[name === 'min' ? '_min' : '_max'] = newValue ?? undefined;
116
+ this.render();
117
+ }
118
+ else if (name === 'flavor') {
119
+ this._syncCustomFlavor();
120
+ }
121
+ }
122
+ _syncCustomFlavor() {
123
+ this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.getAttribute('flavor'), ({ base }) => calendarMonthCustomFlavorCss(base));
124
+ }
125
+ get displayYear() {
126
+ return this._displayYear;
127
+ }
128
+ set displayYear(value) {
129
+ if (this._displayYear !== value) {
130
+ this._displayYear = value;
131
+ this._focusedIndex = null;
132
+ this.render();
133
+ }
134
+ }
135
+ get displayMonth() {
136
+ return this._displayMonth;
137
+ }
138
+ set displayMonth(value) {
139
+ if (this._displayMonth !== value) {
140
+ this._displayMonth = value;
141
+ this._focusedIndex = null;
142
+ this.render();
143
+ }
144
+ }
145
+ get locale() {
146
+ return getEffectiveLocale(this, this.getAttribute('locale'));
147
+ }
148
+ set locale(value) {
149
+ if (this._locale !== value) {
150
+ this._locale = value;
151
+ this.render();
152
+ }
153
+ }
154
+ get size() {
155
+ return this._size;
156
+ }
157
+ set size(value) {
158
+ if (this._size !== value) {
159
+ this._size = value;
160
+ this.render();
161
+ }
162
+ }
163
+ get flavor() {
164
+ return this.getAttribute('flavor') || 'primary';
165
+ }
166
+ set flavor(value) {
167
+ if (value)
168
+ this.setAttribute('flavor', value);
169
+ else
170
+ this.removeAttribute('flavor');
171
+ }
172
+ get width() {
173
+ return this._width;
174
+ }
175
+ set width(value) {
176
+ if (this._width !== value) {
177
+ this._width = value;
178
+ this.applyWidthProperties();
179
+ }
180
+ }
181
+ get minWidth() {
182
+ return this._minWidth;
183
+ }
184
+ set minWidth(value) {
185
+ if (this._minWidth !== value) {
186
+ this._minWidth = value;
187
+ this.applyWidthProperties();
188
+ }
189
+ }
190
+ get maxWidth() {
191
+ return this._maxWidth;
192
+ }
193
+ set maxWidth(value) {
194
+ if (this._maxWidth !== value) {
195
+ this._maxWidth = value;
196
+ this.applyWidthProperties();
197
+ }
198
+ }
199
+ get dayContentFn() {
200
+ return this._dayContentFn;
201
+ }
202
+ set dayContentFn(fn) {
203
+ if (this._dayContentFn !== fn) {
204
+ this._dayContentFn = fn;
205
+ this.render();
206
+ }
207
+ }
208
+ get customCSS() {
209
+ return this._customCSS;
210
+ }
211
+ set customCSS(sheet) {
212
+ if (this._customCSS !== sheet) {
213
+ this._customCSS = sheet;
214
+ this.applyCustomCSS();
215
+ }
216
+ }
217
+ get min() {
218
+ return this._min;
219
+ }
220
+ set min(value) {
221
+ if (this._min !== value) {
222
+ this._min = value;
223
+ this.render();
224
+ }
225
+ }
226
+ get max() {
227
+ return this._max;
228
+ }
229
+ set max(value) {
230
+ if (this._max !== value) {
231
+ this._max = value;
232
+ this.render();
233
+ }
234
+ }
235
+ get value() {
236
+ return this._value;
237
+ }
238
+ set value(timestamp) {
239
+ const newValue = timestamp ?? undefined;
240
+ if (this._value !== newValue) {
241
+ this._value = newValue;
242
+ this.render();
243
+ }
244
+ }
245
+ applyWidthProperties() {
246
+ const normalizedWidth = normalizeSizeValue(this._width);
247
+ const normalizedMinWidth = normalizeSizeValue(this._minWidth);
248
+ const normalizedMaxWidth = normalizeSizeValue(this._maxWidth);
249
+ if (normalizedWidth) {
250
+ this.style.setProperty('--calendar-width', normalizedWidth);
251
+ }
252
+ else {
253
+ this.style.removeProperty('--calendar-width');
254
+ }
255
+ if (normalizedMinWidth) {
256
+ this.style.setProperty('--calendar-min-width', normalizedMinWidth);
257
+ }
258
+ else {
259
+ this.style.removeProperty('--calendar-min-width');
260
+ }
261
+ if (normalizedMaxWidth) {
262
+ this.style.setProperty('--calendar-max-width', normalizedMaxWidth);
263
+ }
264
+ else {
265
+ this.style.removeProperty('--calendar-max-width');
266
+ }
267
+ }
268
+ applyCustomCSS() {
269
+ if (!this._customCSS || !this.shadowRoot)
270
+ return;
271
+ const existing = this.shadowRoot.adoptedStyleSheets;
272
+ if (!existing.includes(this._customCSS)) {
273
+ this.shadowRoot.adoptedStyleSheets = [...existing, this._customCSS];
274
+ }
275
+ }
276
+ refresh() {
277
+ this.render();
278
+ }
279
+ dispatchDayClick(dayContext, _domEvent) {
280
+ const detail = {
281
+ dayContext,
282
+ value: dayContext.value,
283
+ year: dayContext.year,
284
+ month: dayContext.month,
285
+ day: dayContext.dayInMonth,
286
+ isHoliday: dayContext.holiday,
287
+ isToday: dayContext.today,
288
+ isWeekend: dayContext.weekend,
289
+ isOtherMonth: dayContext.otherMonth,
290
+ };
291
+ for (const eventType of ['day-click', 'day-select']) {
292
+ const event = new CustomEvent(eventType, {
293
+ detail,
294
+ bubbles: true,
295
+ cancelable: true,
296
+ });
297
+ this.dispatchEvent(event);
298
+ }
299
+ }
300
+ renderDayCell(dayContext, disabled, isRoving) {
301
+ const dayElement = document.createElement('div');
302
+ const content = this._dayContentFn
303
+ ? this._dayContentFn(dayContext)
304
+ : defaultDayContent(dayContext);
305
+ if (this._dayContentFn && content && !isDOMElement(content) && typeof content !== 'string') {
306
+ throw new Error(`Custom dayContentFn must return a DOM element or string, got: ${typeof content}. ` +
307
+ `Use document.createElement() to create DOM elements.`);
308
+ }
309
+ const classes = getDefaultDayClasses(dayContext);
310
+ if (disabled)
311
+ classes.push('disabled');
312
+ dayElement.className = classes.join(' ');
313
+ if (typeof content === 'string') {
314
+ dayElement.textContent = content;
315
+ }
316
+ else if (isDOMElement(content)) {
317
+ dayElement.appendChild(content);
318
+ }
319
+ dayElement.setAttribute('role', 'gridcell');
320
+ dayElement.setAttribute('aria-label', new Intl.DateTimeFormat(this._locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })
321
+ .format(new Date(dayContext.localValue)));
322
+ dayElement.setAttribute('aria-selected', String(!!dayContext.isSelected));
323
+ if (dayContext.today)
324
+ dayElement.setAttribute('aria-current', 'date');
325
+ if (disabled) {
326
+ dayElement.setAttribute('aria-disabled', 'true');
327
+ dayElement.setAttribute('tabindex', '-1');
328
+ }
329
+ else {
330
+ dayElement.setAttribute('tabindex', isRoving ? '0' : '-1');
331
+ dayElement.addEventListener('pointerdown', (event) => {
332
+ event.preventDefault();
333
+ this.dispatchDayClick(dayContext, event);
334
+ });
335
+ dayElement.addEventListener('keydown', (event) => {
336
+ const ke = event;
337
+ if (ke.key === 'Enter' || ke.key === ' ') {
338
+ ke.preventDefault();
339
+ this.dispatchDayClick(dayContext, event);
340
+ }
341
+ });
342
+ }
343
+ return dayElement;
344
+ }
345
+ render() {
346
+ const root = this.shadowRoot;
347
+ if (!root)
348
+ return;
349
+ ensureStyles(root, { css: calendarMonthStyles, id: 'ty-calendar-month' });
350
+ this.applyCustomCSS();
351
+ this.applyWidthProperties();
352
+ let selection;
353
+ if (this._value) {
354
+ const date = new Date(this._value);
355
+ selection = {
356
+ year: date.getFullYear(),
357
+ month: date.getMonth() + 1,
358
+ day: date.getDate()
359
+ };
360
+ }
361
+ const days = getCalendarMonthDays(this._displayYear, this._displayMonth, selection);
362
+ const weekdays = getLocalizedWeekdays(this._locale, 'short');
363
+ root.innerHTML = '';
364
+ const calendarContainer = document.createElement('div');
365
+ calendarContainer.className = `calendar-flex-container calendar-size-${this._size}`;
366
+ calendarContainer.setAttribute('role', 'grid');
367
+ calendarContainer.setAttribute('aria-label', new Intl.DateTimeFormat(this._locale, { month: 'long', year: 'numeric' })
368
+ .format(new Date(this._displayYear, this._displayMonth - 1, 1)));
369
+ const headerRow = document.createElement('div');
370
+ headerRow.className = 'calendar-row calendar-header-row';
371
+ headerRow.setAttribute('role', 'row');
372
+ weekdays.forEach(weekday => {
373
+ const headerCell = document.createElement('div');
374
+ headerCell.className = 'calendar-cell calendar-header-cell';
375
+ headerCell.setAttribute('role', 'columnheader');
376
+ headerCell.textContent = weekday;
377
+ headerRow.appendChild(headerCell);
378
+ });
379
+ calendarContainer.appendChild(headerRow);
380
+ const dayWeeks = [];
381
+ for (let i = 0; i < 42; i += 7) {
382
+ dayWeeks.push(days.slice(i, i + 7));
383
+ }
384
+ const minTs = isoToUTCTimestamp(this._min);
385
+ const maxTs = isoToUTCTimestamp(this._max);
386
+ const disabledAt = (i) => {
387
+ const v = days[i].value;
388
+ return (minTs !== null && v < minTs) || (maxTs !== null && v > maxTs);
389
+ };
390
+ const firstEnabled = (pred) => {
391
+ for (let i = 0; i < 42; i++) {
392
+ if (pred(i) && !disabledAt(i))
393
+ return i;
394
+ }
395
+ return -1;
396
+ };
397
+ let defaultIndex = firstEnabled(i => !!days[i].isSelected);
398
+ let provisional = false;
399
+ if (defaultIndex === -1)
400
+ defaultIndex = firstEnabled(i => !!days[i].today);
401
+ if (defaultIndex === -1) {
402
+ defaultIndex = firstEnabled(() => true);
403
+ provisional = true;
404
+ }
405
+ if (defaultIndex === -1)
406
+ defaultIndex = 0;
407
+ if (this._focusedIndex === null ||
408
+ this._focusedIndex < 0 ||
409
+ this._focusedIndex > 41 ||
410
+ disabledAt(this._focusedIndex)) {
411
+ this._focusedIndex = provisional ? null : defaultIndex;
412
+ }
413
+ const effectiveFocusedIndex = this._focusedIndex ?? defaultIndex;
414
+ this._cellElements = [];
415
+ let flatIndex = 0;
416
+ dayWeeks.forEach(week => {
417
+ const dayRow = document.createElement('div');
418
+ dayRow.className = 'calendar-row calendar-day-row';
419
+ dayRow.setAttribute('role', 'row');
420
+ week.forEach(dayContext => {
421
+ const disabled = (minTs !== null && dayContext.value < minTs) ||
422
+ (maxTs !== null && dayContext.value > maxTs);
423
+ const dayCell = this.renderDayCell(dayContext, disabled, flatIndex === effectiveFocusedIndex);
424
+ dayCell.className = `${dayCell.className} calendar-cell calendar-day-cell`;
425
+ dayRow.appendChild(dayCell);
426
+ this._cellElements[flatIndex] = dayCell;
427
+ flatIndex++;
428
+ });
429
+ calendarContainer.appendChild(dayRow);
430
+ });
431
+ calendarContainer.addEventListener('keydown', (event) => {
432
+ const ke = event;
433
+ const delta = ke.key === 'ArrowLeft' ? -1 :
434
+ ke.key === 'ArrowRight' ? 1 :
435
+ ke.key === 'ArrowUp' ? -7 :
436
+ ke.key === 'ArrowDown' ? 7 :
437
+ 0;
438
+ if (delta === 0)
439
+ return;
440
+ const currentIndex = this._focusedIndex ?? defaultIndex;
441
+ const nextIndex = currentIndex + delta;
442
+ if (nextIndex < 0 || nextIndex > 41 || disabledAt(nextIndex))
443
+ return;
444
+ ke.preventDefault();
445
+ this._cellElements[currentIndex]?.setAttribute('tabindex', '-1');
446
+ this._focusedIndex = nextIndex;
447
+ const nextCell = this._cellElements[nextIndex];
448
+ if (nextCell) {
449
+ nextCell.setAttribute('tabindex', '0');
450
+ nextCell.focus();
451
+ }
452
+ });
453
+ root.appendChild(calendarContainer);
454
+ }
432
455
  }
433
456
  if (!customElements.get('ty-calendar-month')) {
434
- customElements.define('ty-calendar-month', TyCalendarMonth);
457
+ customElements.define('ty-calendar-month', TyCalendarMonth);
435
458
  }