@teaui/core 1.6.8 → 1.7.8

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 (91) hide show
  1. package/.dist/Screen.d.ts +4 -5
  2. package/.dist/Screen.js +4 -6
  3. package/.dist/Screen.js.map +1 -1
  4. package/.dist/Viewport.d.ts +6 -1
  5. package/.dist/Viewport.js +6 -2
  6. package/.dist/Viewport.js.map +1 -1
  7. package/.dist/components/Accordion.js +1 -1
  8. package/.dist/components/Accordion.js.map +1 -1
  9. package/.dist/components/Breadcrumb.d.ts +93 -0
  10. package/.dist/components/Breadcrumb.js +287 -0
  11. package/.dist/components/Breadcrumb.js.map +1 -0
  12. package/.dist/components/Button.js +23 -11
  13. package/.dist/components/Button.js.map +1 -1
  14. package/.dist/components/Calendar.d.ts +36 -0
  15. package/.dist/components/Calendar.js +721 -0
  16. package/.dist/components/Calendar.js.map +1 -0
  17. package/.dist/components/Canvas.d.ts +48 -0
  18. package/.dist/components/Canvas.js +230 -0
  19. package/.dist/components/Canvas.js.map +1 -0
  20. package/.dist/components/Checkbox.d.ts +3 -2
  21. package/.dist/components/Checkbox.js +31 -9
  22. package/.dist/components/Checkbox.js.map +1 -1
  23. package/.dist/components/Dropdown.d.ts +1 -1
  24. package/.dist/components/Dropdown.js +14 -6
  25. package/.dist/components/Dropdown.js.map +1 -1
  26. package/.dist/components/Geometry.d.ts +29 -0
  27. package/.dist/components/Geometry.js +40 -0
  28. package/.dist/components/Geometry.js.map +1 -0
  29. package/.dist/components/HotKey.js +1 -1
  30. package/.dist/components/HotKey.js.map +1 -1
  31. package/.dist/components/Log.js +1 -1
  32. package/.dist/components/Log.js.map +1 -1
  33. package/.dist/components/Logo.d.ts +19 -0
  34. package/.dist/components/Logo.js +204 -0
  35. package/.dist/components/Logo.js.map +1 -0
  36. package/.dist/components/Modal.d.ts +74 -0
  37. package/.dist/components/Modal.js +113 -0
  38. package/.dist/components/Modal.js.map +1 -0
  39. package/.dist/components/Pane.d.ts +43 -0
  40. package/.dist/components/Pane.js +321 -0
  41. package/.dist/components/Pane.js.map +1 -0
  42. package/.dist/components/Pin.d.ts +25 -0
  43. package/.dist/components/Pin.js +80 -0
  44. package/.dist/components/Pin.js.map +1 -0
  45. package/.dist/components/Plot.d.ts +25 -0
  46. package/.dist/components/Plot.js +171 -0
  47. package/.dist/components/Plot.js.map +1 -0
  48. package/.dist/components/Scrollable.d.ts +6 -0
  49. package/.dist/components/Scrollable.js +14 -2
  50. package/.dist/components/Scrollable.js.map +1 -1
  51. package/.dist/components/ScrollableList.d.ts +2 -2
  52. package/.dist/components/ScrollableList.js +4 -4
  53. package/.dist/components/ScrollableList.js.map +1 -1
  54. package/.dist/components/Slider.d.ts +2 -0
  55. package/.dist/components/Slider.js +11 -1
  56. package/.dist/components/Slider.js.map +1 -1
  57. package/.dist/components/Table.d.ts +79 -0
  58. package/.dist/components/Table.js +603 -0
  59. package/.dist/components/Table.js.map +1 -0
  60. package/.dist/components/Tabs.d.ts +2 -0
  61. package/.dist/components/Tabs.js +14 -1
  62. package/.dist/components/Tabs.js.map +1 -1
  63. package/.dist/components/ZStack.d.ts +19 -0
  64. package/.dist/components/ZStack.js +34 -0
  65. package/.dist/components/ZStack.js.map +1 -0
  66. package/.dist/components/charts/BarChart.d.ts +27 -0
  67. package/.dist/components/charts/BarChart.js +122 -0
  68. package/.dist/components/charts/BarChart.js.map +1 -0
  69. package/.dist/components/charts/Chart.d.ts +43 -0
  70. package/.dist/components/charts/Chart.js +41 -0
  71. package/.dist/components/charts/Chart.js.map +1 -0
  72. package/.dist/components/charts/LineChart.d.ts +23 -0
  73. package/.dist/components/charts/LineChart.js +109 -0
  74. package/.dist/components/charts/LineChart.js.map +1 -0
  75. package/.dist/components/charts/index.d.ts +3 -0
  76. package/.dist/components/charts/index.js +4 -0
  77. package/.dist/components/charts/index.js.map +1 -0
  78. package/.dist/components/index.d.ts +12 -0
  79. package/.dist/components/index.js +12 -0
  80. package/.dist/components/index.js.map +1 -1
  81. package/.dist/iTerm2.js +1 -3
  82. package/.dist/iTerm2.js.map +1 -1
  83. package/.dist/inspect.js +1 -1
  84. package/.dist/inspect.js.map +1 -1
  85. package/.dist/managers/ModalManager.d.ts +2 -1
  86. package/.dist/managers/ModalManager.js +23 -51
  87. package/.dist/managers/ModalManager.js.map +1 -1
  88. package/.dist/testing.d.ts +5 -0
  89. package/.dist/testing.js +19 -3
  90. package/.dist/testing.js.map +1 -1
  91. package/package.json +3 -3
@@ -0,0 +1,721 @@
1
+ import { View } from '../View.js';
2
+ import { Point, Rect, Size } from '../geometry.js';
3
+ import { isMouseClicked, } from '../events/index.js';
4
+ import { Style } from '../Style.js';
5
+ const MONTH_NAMES = [
6
+ 'January',
7
+ 'February',
8
+ 'March',
9
+ 'April',
10
+ 'May',
11
+ 'June',
12
+ 'July',
13
+ 'August',
14
+ 'September',
15
+ 'October',
16
+ 'November',
17
+ 'December',
18
+ ];
19
+ const MONTH_SHORT = [
20
+ 'Jan',
21
+ 'Feb',
22
+ 'Mar',
23
+ 'Apr',
24
+ 'May',
25
+ 'Jun',
26
+ 'Jul',
27
+ 'Aug',
28
+ 'Sep',
29
+ 'Oct',
30
+ 'Nov',
31
+ 'Dec',
32
+ ];
33
+ const DAY_HEADERS_SUN = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
34
+ const DAY_HEADERS_MON = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
35
+ // Width: " Su Mo Tu We Th Fr Sa " = 1 + 7*3 = 22
36
+ const CALENDAR_WIDTH = 22;
37
+ const NAV_BUTTON_WIDTH = 2;
38
+ const CLOSE_BUTTON_WIDTH = 3;
39
+ const MONTH_LABEL_X = 3;
40
+ // Height: header + weekday row + 6 week rows = 8
41
+ const CALENDAR_HEIGHT = 8;
42
+ export class Calendar extends View {
43
+ #date;
44
+ #visibleDate;
45
+ #onChangeVisible;
46
+ #onChange;
47
+ #selection;
48
+ #firstDayOfWeek;
49
+ #displayMode = 'days';
50
+ #hasFocus = false;
51
+ // Range selection state
52
+ #rangeStart;
53
+ #rangeEnd;
54
+ // Year picker state
55
+ #yearScrollOffset = 0;
56
+ #yearSearch = '';
57
+ // Mouse drag tracking
58
+ #dragStartDate;
59
+ // Mouse hover tracking
60
+ #hoverPrevMonth = false;
61
+ #hoverNextMonth = false;
62
+ #hoverMonthLabel = false;
63
+ #hoverYearLabel = false;
64
+ constructor(props = {}) {
65
+ super(props);
66
+ const now = new Date();
67
+ this.#date = props.date ?? now;
68
+ this.#visibleDate =
69
+ props.visibleDate ?? new Date(now.getFullYear(), now.getMonth(), 1);
70
+ this.#onChangeVisible = props.onChangeVisible;
71
+ this.#onChange = props.onChange;
72
+ this.#selection = props.selection ?? 'single';
73
+ this.#firstDayOfWeek = props.firstDayOfWeek ?? 0;
74
+ }
75
+ update(props) {
76
+ if (props.date !== undefined)
77
+ this.#date = props.date;
78
+ if (props.visibleDate !== undefined)
79
+ this.#visibleDate = props.visibleDate;
80
+ if (props.onChangeVisible !== undefined)
81
+ this.#onChangeVisible = props.onChangeVisible;
82
+ if (props.onChange !== undefined)
83
+ this.#onChange = props.onChange;
84
+ if (props.selection !== undefined)
85
+ this.#selection = props.selection;
86
+ if (props.firstDayOfWeek !== undefined)
87
+ this.#firstDayOfWeek = props.firstDayOfWeek;
88
+ super.update(props);
89
+ }
90
+ get date() {
91
+ return this.#date;
92
+ }
93
+ set date(value) {
94
+ this.#date = value;
95
+ this.invalidateRender();
96
+ }
97
+ get visibleDate() {
98
+ return this.#visibleDate;
99
+ }
100
+ set visibleDate(value) {
101
+ this.#visibleDate = value;
102
+ this.invalidateRender();
103
+ }
104
+ get displayMode() {
105
+ return this.#displayMode;
106
+ }
107
+ naturalSize() {
108
+ return new Size(CALENDAR_WIDTH, CALENDAR_HEIGHT);
109
+ }
110
+ #widgetRect(viewport) {
111
+ return new Rect(Point.zero, [
112
+ Math.min(CALENDAR_WIDTH, viewport.contentSize.width),
113
+ Math.min(CALENDAR_HEIGHT, viewport.contentSize.height),
114
+ ]);
115
+ }
116
+ #today() {
117
+ const now = new Date();
118
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
119
+ }
120
+ #isSameDay(a, b) {
121
+ return (a.getFullYear() === b.getFullYear() &&
122
+ a.getMonth() === b.getMonth() &&
123
+ a.getDate() === b.getDate());
124
+ }
125
+ #isInRange(date) {
126
+ if (!this.#rangeStart || !this.#rangeEnd)
127
+ return false;
128
+ const time = date.getTime();
129
+ const start = Math.min(this.#rangeStart.getTime(), this.#rangeEnd.getTime());
130
+ const end = Math.max(this.#rangeStart.getTime(), this.#rangeEnd.getTime());
131
+ return time >= start && time <= end;
132
+ }
133
+ #navigateMonth(delta) {
134
+ const d = new Date(this.#visibleDate.getFullYear(), this.#visibleDate.getMonth() + delta, 1);
135
+ this.#visibleDate = d;
136
+ this.#onChangeVisible?.(d);
137
+ this.invalidateRender();
138
+ }
139
+ #navigateYear(delta) {
140
+ const d = new Date(this.#visibleDate.getFullYear() + delta, this.#visibleDate.getMonth(), 1);
141
+ this.#visibleDate = d;
142
+ this.#onChangeVisible?.(d);
143
+ this.invalidateRender();
144
+ }
145
+ #syncVisibleDate(date) {
146
+ if (date.getMonth() !== this.#visibleDate.getMonth() ||
147
+ date.getFullYear() !== this.#visibleDate.getFullYear()) {
148
+ const d = new Date(date.getFullYear(), date.getMonth(), 1);
149
+ this.#visibleDate = d;
150
+ this.#onChangeVisible?.(d);
151
+ }
152
+ }
153
+ #startRangeSelection(date, syncVisibleDate = true) {
154
+ this.#rangeStart = date;
155
+ this.#rangeEnd = undefined;
156
+ this.#date = date;
157
+ if (syncVisibleDate) {
158
+ this.#syncVisibleDate(date);
159
+ }
160
+ this.invalidateRender();
161
+ }
162
+ #finishRangeSelection(date, syncVisibleDate = true) {
163
+ if (!this.#rangeStart) {
164
+ this.#startRangeSelection(date, syncVisibleDate);
165
+ return;
166
+ }
167
+ this.#rangeEnd = date;
168
+ this.#date = date;
169
+ if (syncVisibleDate) {
170
+ this.#syncVisibleDate(date);
171
+ }
172
+ const start = this.#rangeStart.getTime() <= date.getTime() ? this.#rangeStart : date;
173
+ const end = this.#rangeStart.getTime() <= date.getTime() ? date : this.#rangeStart;
174
+ this.#onChange?.(start, end);
175
+ this.invalidateRender();
176
+ }
177
+ #selectDate(date) {
178
+ if (this.#selection === 'range') {
179
+ if (!this.#rangeStart || this.#rangeEnd) {
180
+ this.#startRangeSelection(date);
181
+ }
182
+ else {
183
+ this.#finishRangeSelection(date);
184
+ }
185
+ return;
186
+ }
187
+ this.#date = date;
188
+ this.#onChange?.(date, date);
189
+ this.#syncVisibleDate(date);
190
+ this.invalidateRender();
191
+ }
192
+ #selectMonth(month) {
193
+ const d = new Date(this.#visibleDate.getFullYear(), month, 1);
194
+ this.#visibleDate = d;
195
+ this.#displayMode = 'days';
196
+ this.#onChangeVisible?.(d);
197
+ this.invalidateRender();
198
+ }
199
+ #selectYear(year) {
200
+ const d = new Date(year, this.#visibleDate.getMonth(), 1);
201
+ this.#visibleDate = d;
202
+ this.#displayMode = 'days';
203
+ this.#onChangeVisible?.(d);
204
+ this.invalidateRender();
205
+ }
206
+ #getDateAtPosition(x, y) {
207
+ if (y < 2 || y > 7)
208
+ return undefined;
209
+ const col = Math.floor(x / 3);
210
+ const weekRow = y - 2;
211
+ const grid = this.#getDayGrid();
212
+ if (col < 0 || col >= 7 || weekRow < 0 || weekRow >= grid.length) {
213
+ return undefined;
214
+ }
215
+ return grid[weekRow][col];
216
+ }
217
+ /** Get all day cells for the current visible month grid */
218
+ #getDayGrid() {
219
+ const year = this.#visibleDate.getFullYear();
220
+ const month = this.#visibleDate.getMonth();
221
+ const firstOfMonth = new Date(year, month, 1);
222
+ let startDow = firstOfMonth.getDay(); // 0=Sun
223
+ if (this.#firstDayOfWeek === 1) {
224
+ startDow = (startDow + 6) % 7; // Convert to Mon=0
225
+ }
226
+ const weeks = [];
227
+ // Start from the first day shown in the grid
228
+ const startDate = new Date(year, month, 1 - startDow);
229
+ for (let week = 0; week < 6; week++) {
230
+ const row = [];
231
+ for (let day = 0; day < 7; day++) {
232
+ const d = new Date(startDate);
233
+ d.setDate(startDate.getDate() + week * 7 + day);
234
+ row.push(d);
235
+ }
236
+ weeks.push(row);
237
+ }
238
+ return weeks;
239
+ }
240
+ receiveMouse(event, system) {
241
+ super.receiveMouse(event, system);
242
+ if (this.#displayMode === 'days') {
243
+ this.#receiveMouseDays(event);
244
+ }
245
+ else if (this.#displayMode === 'months') {
246
+ this.#receiveMouseMonths(event);
247
+ }
248
+ else {
249
+ this.#receiveMouseYears(event);
250
+ }
251
+ }
252
+ #receiveMouseDays(event) {
253
+ const x = event.position.x;
254
+ const y = event.position.y;
255
+ // Reset hover states
256
+ this.#hoverPrevMonth = false;
257
+ this.#hoverNextMonth = false;
258
+ this.#hoverMonthLabel = false;
259
+ this.#hoverYearLabel = false;
260
+ if (y === 0) {
261
+ const monthName = MONTH_NAMES[this.#visibleDate.getMonth()];
262
+ const yearStr = String(this.#visibleDate.getFullYear());
263
+ const yearStart = CALENDAR_WIDTH - NAV_BUTTON_WIDTH - 1 - yearStr.length;
264
+ if (x < NAV_BUTTON_WIDTH) {
265
+ this.#hoverPrevMonth = true;
266
+ if (isMouseClicked(event)) {
267
+ this.#navigateMonth(-1);
268
+ }
269
+ }
270
+ else if (x >= CALENDAR_WIDTH - NAV_BUTTON_WIDTH) {
271
+ this.#hoverNextMonth = true;
272
+ if (isMouseClicked(event)) {
273
+ this.#navigateMonth(1);
274
+ }
275
+ }
276
+ else if (x >= MONTH_LABEL_X && x < MONTH_LABEL_X + monthName.length) {
277
+ this.#hoverMonthLabel = true;
278
+ if (isMouseClicked(event)) {
279
+ this.#displayMode = 'months';
280
+ }
281
+ }
282
+ else if (x >= yearStart && x < yearStart + yearStr.length) {
283
+ this.#hoverYearLabel = true;
284
+ if (isMouseClicked(event)) {
285
+ this.#displayMode = 'years';
286
+ this.#yearScrollOffset = 0;
287
+ this.#yearSearch = '';
288
+ }
289
+ }
290
+ return;
291
+ }
292
+ const date = this.#getDateAtPosition(x, y);
293
+ if (!date) {
294
+ if (event.name === 'mouse.button.cancel' ||
295
+ event.name === 'mouse.button.up') {
296
+ this.#dragStartDate = undefined;
297
+ }
298
+ return;
299
+ }
300
+ if (this.#selection === 'range') {
301
+ switch (event.name) {
302
+ case 'mouse.button.down':
303
+ if (this.#rangeStart &&
304
+ !this.#rangeEnd &&
305
+ !this.#isSameDay(this.#rangeStart, date)) {
306
+ this.#dragStartDate = undefined;
307
+ this.#finishRangeSelection(date);
308
+ }
309
+ else {
310
+ this.#dragStartDate = date;
311
+ this.#startRangeSelection(date, false);
312
+ }
313
+ break;
314
+ case 'mouse.button.dragInside':
315
+ case 'mouse.button.enter':
316
+ if (this.#dragStartDate) {
317
+ this.#rangeStart = this.#dragStartDate;
318
+ this.#rangeEnd = date;
319
+ this.#date = date;
320
+ this.invalidateRender();
321
+ }
322
+ break;
323
+ case 'mouse.button.up':
324
+ if (this.#dragStartDate &&
325
+ !this.#isSameDay(this.#dragStartDate, date)) {
326
+ this.#rangeStart = this.#dragStartDate;
327
+ this.#finishRangeSelection(date);
328
+ }
329
+ this.#dragStartDate = undefined;
330
+ break;
331
+ case 'mouse.button.cancel':
332
+ this.#dragStartDate = undefined;
333
+ this.invalidateRender();
334
+ break;
335
+ }
336
+ return;
337
+ }
338
+ if (event.name === 'mouse.button.down') {
339
+ this.#selectDate(date);
340
+ }
341
+ }
342
+ #receiveMouseMonths(event) {
343
+ const x = event.position.x;
344
+ const y = event.position.y;
345
+ if (isMouseClicked(event)) {
346
+ if (y === 0 && x >= CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH) {
347
+ // Close button
348
+ this.#displayMode = 'days';
349
+ this.invalidateRender();
350
+ return;
351
+ }
352
+ // Month grid: 4 rows × 3 columns, starting at y=2
353
+ if (y >= 2 && y <= 5) {
354
+ const col = Math.floor(x / 7);
355
+ const row = y - 2;
356
+ if (col >= 0 && col < 3 && row >= 0 && row < 4) {
357
+ const month = row * 3 + col;
358
+ this.#selectMonth(month);
359
+ }
360
+ }
361
+ }
362
+ }
363
+ #receiveMouseYears(event) {
364
+ const x = event.position.x;
365
+ const y = event.position.y;
366
+ if (isMouseClicked(event)) {
367
+ if (y === 0 && x >= CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH) {
368
+ // Close button
369
+ this.#displayMode = 'days';
370
+ this.invalidateRender();
371
+ return;
372
+ }
373
+ // Year rows at y=2..6 (5 visible years)
374
+ if (y >= 2 && y <= 6) {
375
+ const yearIndex = y - 2;
376
+ const baseYear = this.#visibleDate.getFullYear() - 2 + this.#yearScrollOffset;
377
+ this.#selectYear(baseYear + yearIndex);
378
+ }
379
+ // Scroll arrows
380
+ if (y === 1) {
381
+ this.#yearScrollOffset -= 5;
382
+ this.invalidateRender();
383
+ }
384
+ else if (y === 7) {
385
+ this.#yearScrollOffset += 5;
386
+ this.invalidateRender();
387
+ }
388
+ }
389
+ // Scroll wheel
390
+ if (event.name === 'mouse.wheel.up') {
391
+ this.#yearScrollOffset -= 1;
392
+ this.invalidateRender();
393
+ }
394
+ else if (event.name === 'mouse.wheel.down') {
395
+ this.#yearScrollOffset += 1;
396
+ this.invalidateRender();
397
+ }
398
+ }
399
+ receiveKey(event) {
400
+ if (this.#displayMode === 'days') {
401
+ this.#receiveKeyDays(event);
402
+ }
403
+ else if (this.#displayMode === 'months') {
404
+ this.#receiveKeyMonths(event);
405
+ }
406
+ else {
407
+ this.#receiveKeyYears(event);
408
+ }
409
+ }
410
+ #receiveKeyDays(event) {
411
+ switch (event.name) {
412
+ case 'left':
413
+ this.#moveDateBy(-1);
414
+ break;
415
+ case 'right':
416
+ this.#moveDateBy(1);
417
+ break;
418
+ case 'up':
419
+ this.#moveDateBy(-7);
420
+ break;
421
+ case 'down':
422
+ this.#moveDateBy(7);
423
+ break;
424
+ case 'return':
425
+ this.#selectDate(this.#date);
426
+ break;
427
+ case 'escape':
428
+ // Do nothing in day view
429
+ break;
430
+ case 'pageup':
431
+ this.#navigateMonth(-1);
432
+ break;
433
+ case 'pagedown':
434
+ this.#navigateMonth(1);
435
+ break;
436
+ case 'home': {
437
+ const d = new Date(this.#visibleDate.getFullYear(), this.#visibleDate.getMonth(), 1);
438
+ this.#date = d;
439
+ this.invalidateRender();
440
+ break;
441
+ }
442
+ case 'end': {
443
+ const d = new Date(this.#visibleDate.getFullYear(), this.#visibleDate.getMonth() + 1, 0);
444
+ this.#date = d;
445
+ this.invalidateRender();
446
+ break;
447
+ }
448
+ }
449
+ }
450
+ #receiveKeyMonths(event) {
451
+ switch (event.name) {
452
+ case 'escape':
453
+ this.#displayMode = 'days';
454
+ this.invalidateRender();
455
+ break;
456
+ case 'return':
457
+ this.#selectMonth(this.#visibleDate.getMonth());
458
+ break;
459
+ case 'left': {
460
+ const m = this.#visibleDate.getMonth() - 1;
461
+ if (m >= 0) {
462
+ this.#visibleDate = new Date(this.#visibleDate.getFullYear(), m, 1);
463
+ this.invalidateRender();
464
+ }
465
+ break;
466
+ }
467
+ case 'right': {
468
+ const m = this.#visibleDate.getMonth() + 1;
469
+ if (m <= 11) {
470
+ this.#visibleDate = new Date(this.#visibleDate.getFullYear(), m, 1);
471
+ this.invalidateRender();
472
+ }
473
+ break;
474
+ }
475
+ case 'up': {
476
+ const m = this.#visibleDate.getMonth() - 3;
477
+ if (m >= 0) {
478
+ this.#visibleDate = new Date(this.#visibleDate.getFullYear(), m, 1);
479
+ this.invalidateRender();
480
+ }
481
+ break;
482
+ }
483
+ case 'down': {
484
+ const m = this.#visibleDate.getMonth() + 3;
485
+ if (m <= 11) {
486
+ this.#visibleDate = new Date(this.#visibleDate.getFullYear(), m, 1);
487
+ this.invalidateRender();
488
+ }
489
+ break;
490
+ }
491
+ }
492
+ }
493
+ #receiveKeyYears(event) {
494
+ switch (event.name) {
495
+ case 'escape':
496
+ this.#displayMode = 'days';
497
+ this.#yearSearch = '';
498
+ this.invalidateRender();
499
+ break;
500
+ case 'return': {
501
+ if (this.#yearSearch) {
502
+ const year = parseInt(this.#yearSearch, 10);
503
+ if (!isNaN(year)) {
504
+ this.#selectYear(year);
505
+ this.#yearSearch = '';
506
+ }
507
+ }
508
+ else {
509
+ this.#selectYear(this.#visibleDate.getFullYear());
510
+ }
511
+ break;
512
+ }
513
+ case 'up':
514
+ this.#yearScrollOffset -= 1;
515
+ this.invalidateRender();
516
+ break;
517
+ case 'down':
518
+ this.#yearScrollOffset += 1;
519
+ this.invalidateRender();
520
+ break;
521
+ case 'backspace':
522
+ this.#yearSearch = this.#yearSearch.slice(0, -1);
523
+ this.#updateYearFromSearch();
524
+ break;
525
+ default: {
526
+ // Accept digit input for year search
527
+ const char = event.char;
528
+ if (char && char >= '0' && char <= '9') {
529
+ this.#yearSearch += char;
530
+ this.#updateYearFromSearch();
531
+ }
532
+ break;
533
+ }
534
+ }
535
+ }
536
+ #updateYearFromSearch() {
537
+ if (this.#yearSearch) {
538
+ const year = parseInt(this.#yearSearch, 10);
539
+ if (!isNaN(year)) {
540
+ this.#yearScrollOffset = year - this.#visibleDate.getFullYear();
541
+ }
542
+ }
543
+ this.invalidateRender();
544
+ }
545
+ #moveDateBy(days) {
546
+ const d = new Date(this.#date);
547
+ d.setDate(d.getDate() + days);
548
+ this.#date = d;
549
+ // Navigate months if needed
550
+ if (d.getMonth() !== this.#visibleDate.getMonth() ||
551
+ d.getFullYear() !== this.#visibleDate.getFullYear()) {
552
+ const vis = new Date(d.getFullYear(), d.getMonth(), 1);
553
+ this.#visibleDate = vis;
554
+ this.#onChangeVisible?.(vis);
555
+ }
556
+ this.invalidateRender();
557
+ }
558
+ render(viewport) {
559
+ const hasFocus = viewport.registerFocus();
560
+ this.#hasFocus = hasFocus;
561
+ if (viewport.isEmpty) {
562
+ return;
563
+ }
564
+ viewport.registerMouse(['mouse.button.left', 'mouse.move', 'mouse.wheel'], this.#widgetRect(viewport));
565
+ switch (this.#displayMode) {
566
+ case 'days':
567
+ this.#renderDays(viewport);
568
+ break;
569
+ case 'months':
570
+ this.#renderMonths(viewport);
571
+ break;
572
+ case 'years':
573
+ this.#renderYears(viewport);
574
+ break;
575
+ }
576
+ }
577
+ get #selectedStyle() {
578
+ return new Style({
579
+ bold: true,
580
+ foreground: this.theme.textColor,
581
+ background: this.theme.highlightColor,
582
+ });
583
+ }
584
+ #renderDays(viewport) {
585
+ const today = this.#today();
586
+ const year = this.#visibleDate.getFullYear();
587
+ const month = this.#visibleDate.getMonth();
588
+ const monthName = MONTH_NAMES[month];
589
+ const yearStr = String(year);
590
+ const textStyle = this.theme.text();
591
+ const dimStyle = this.theme.text({ isPlaceholder: true });
592
+ const headerStyle = this.theme.ui({ isHover: false });
593
+ const selectedStyle = this.#selectedStyle;
594
+ const todayStyle = new Style({
595
+ bold: true,
596
+ foreground: this.theme.brightTextColor,
597
+ background: textStyle.background,
598
+ });
599
+ const rangeStyle = new Style({
600
+ foreground: this.theme.highlightColor,
601
+ background: textStyle.background,
602
+ });
603
+ viewport.paint(textStyle, this.#widgetRect(viewport));
604
+ // Header: "◃ June 2026 ▹"
605
+ const prevArrow = this.#hoverPrevMonth ? '◂ ' : '◃ ';
606
+ const nextArrow = this.#hoverNextMonth ? ' ▸' : ' ▹';
607
+ const monthLabelStyle = this.#hoverMonthLabel
608
+ ? this.theme.ui({ isHover: true })
609
+ : headerStyle;
610
+ const yearLabelStyle = this.#hoverYearLabel
611
+ ? this.theme.ui({ isHover: true })
612
+ : headerStyle;
613
+ const yearX = CALENDAR_WIDTH - NAV_BUTTON_WIDTH - 1 - yearStr.length;
614
+ viewport.write(prevArrow, new Point(0, 0), headerStyle);
615
+ viewport.write(monthName, new Point(MONTH_LABEL_X, 0), monthLabelStyle);
616
+ viewport.write(yearStr, new Point(yearX, 0), yearLabelStyle);
617
+ viewport.write(nextArrow, new Point(CALENDAR_WIDTH - NAV_BUTTON_WIDTH, 0), headerStyle);
618
+ // Weekday headers
619
+ const dayHeaders = this.#firstDayOfWeek === 1 ? DAY_HEADERS_MON : DAY_HEADERS_SUN;
620
+ const weekdayStyle = new Style({
621
+ bold: true,
622
+ foreground: this.theme.highlightColor,
623
+ background: textStyle.background,
624
+ });
625
+ for (let i = 0; i < 7; i++) {
626
+ viewport.write(dayHeaders[i], new Point(1 + i * 3, 1), weekdayStyle);
627
+ }
628
+ // Day grid
629
+ const grid = this.#getDayGrid();
630
+ for (let week = 0; week < 6; week++) {
631
+ for (let day = 0; day < 7; day++) {
632
+ const date = grid[week][day];
633
+ const isCurrentMonth = date.getMonth() === month;
634
+ const isToday = this.#isSameDay(date, today);
635
+ const isSelected = this.#isSameDay(date, this.#date);
636
+ const isInRange = this.#isInRange(date);
637
+ const dayNum = String(date.getDate()).padStart(2, ' ');
638
+ let style;
639
+ if (isSelected) {
640
+ style = selectedStyle;
641
+ }
642
+ else if (isInRange) {
643
+ style = rangeStyle;
644
+ }
645
+ else if (isToday) {
646
+ style = todayStyle;
647
+ }
648
+ else if (!isCurrentMonth) {
649
+ style = dimStyle;
650
+ }
651
+ else {
652
+ style = textStyle;
653
+ }
654
+ viewport.write(dayNum, new Point(1 + day * 3, 2 + week), style);
655
+ }
656
+ }
657
+ }
658
+ #renderMonths(viewport) {
659
+ const textStyle = this.theme.text();
660
+ const headerStyle = this.theme.ui({ isHover: false });
661
+ const selectedStyle = this.#selectedStyle;
662
+ const currentMonth = this.#visibleDate.getMonth();
663
+ viewport.paint(textStyle, this.#widgetRect(viewport));
664
+ // Header
665
+ const monthName = MONTH_NAMES[currentMonth];
666
+ const headerText = monthName;
667
+ const headerX = Math.max(0, Math.floor((CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH - headerText.length) / 2) + 1);
668
+ viewport.write(headerText, new Point(headerX, 0), headerStyle);
669
+ viewport.write(' × ', new Point(CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH, 0), headerStyle);
670
+ // Month grid: 4 rows × 3 columns
671
+ for (let row = 0; row < 4; row++) {
672
+ for (let col = 0; col < 3; col++) {
673
+ const m = row * 3 + col;
674
+ const name = MONTH_SHORT[m];
675
+ const isSelected = m === currentMonth;
676
+ const style = isSelected ? selectedStyle : textStyle;
677
+ // Each column is ~7 chars wide (fits in 22 width: 7*3=21 + 1 padding)
678
+ const x = 1 + col * 7;
679
+ const y = 2 + row;
680
+ const label = ` ${name} `;
681
+ viewport.write(label, new Point(x, y), style);
682
+ }
683
+ }
684
+ }
685
+ #renderYears(viewport) {
686
+ const textStyle = this.theme.text();
687
+ const headerStyle = this.theme.ui({ isHover: false });
688
+ const selectedStyle = this.#selectedStyle;
689
+ const currentYear = this.#visibleDate.getFullYear();
690
+ viewport.paint(textStyle, this.#widgetRect(viewport));
691
+ // Header
692
+ const headerText = 'Year';
693
+ const headerX = Math.max(0, Math.floor((CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH - headerText.length) / 2) + 1);
694
+ viewport.write(headerText, new Point(headerX, 0), headerStyle);
695
+ viewport.write(' × ', new Point(CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH, 0), headerStyle);
696
+ // Scroll indicator
697
+ viewport.write('↑', new Point(Math.floor(CALENDAR_WIDTH / 2), 1), headerStyle);
698
+ // 5 visible years
699
+ const baseYear = currentYear - 2 + this.#yearScrollOffset;
700
+ for (let i = 0; i < 5; i++) {
701
+ const year = baseYear + i;
702
+ const isSelected = year === currentYear;
703
+ const style = isSelected ? selectedStyle : textStyle;
704
+ const yearStr = String(year);
705
+ const x = Math.floor((CALENDAR_WIDTH - yearStr.length) / 2);
706
+ viewport.write(yearStr, new Point(x, 2 + i), style);
707
+ }
708
+ // Down arrow
709
+ viewport.write('↓', new Point(Math.floor(CALENDAR_WIDTH / 2), 7), headerStyle);
710
+ // Search line (if searching)
711
+ if (this.#yearSearch) {
712
+ const searchLabel = `Search: ${this.#yearSearch}`;
713
+ viewport.write(searchLabel, new Point(0, 7), new Style({
714
+ dim: true,
715
+ foreground: textStyle.foreground,
716
+ background: textStyle.background,
717
+ }));
718
+ }
719
+ }
720
+ }
721
+ //# sourceMappingURL=Calendar.js.map