@teaui/core 1.7.8 → 1.9.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.
- package/.dist/Buffer.d.ts +5 -0
- package/.dist/Buffer.js +23 -0
- package/.dist/Buffer.js.map +1 -1
- package/.dist/Color.js.map +1 -1
- package/.dist/Screen.d.ts +2 -1
- package/.dist/Screen.js +24 -10
- package/.dist/Screen.js.map +1 -1
- package/.dist/Theme.d.ts +7 -3
- package/.dist/Theme.js +42 -25
- package/.dist/Theme.js.map +1 -1
- package/.dist/UI.js +3 -1
- package/.dist/UI.js.map +1 -1
- package/.dist/View.d.ts +29 -0
- package/.dist/View.js +40 -2
- package/.dist/View.js.map +1 -1
- package/.dist/Viewport.d.ts +17 -3
- package/.dist/Viewport.js +21 -11
- package/.dist/Viewport.js.map +1 -1
- package/.dist/components/Alert.d.ts +76 -0
- package/.dist/components/Alert.js +133 -0
- package/.dist/components/Alert.js.map +1 -0
- package/.dist/components/Box.d.ts +6 -2
- package/.dist/components/Box.js +51 -14
- package/.dist/components/Box.js.map +1 -1
- package/.dist/components/Button.d.ts +5 -2
- package/.dist/components/Button.js +44 -13
- package/.dist/components/Button.js.map +1 -1
- package/.dist/components/Calendar.d.ts +4 -1
- package/.dist/components/Calendar.js +371 -137
- package/.dist/components/Calendar.js.map +1 -1
- package/.dist/components/Callout.d.ts +12 -0
- package/.dist/components/Callout.js +82 -0
- package/.dist/components/Callout.js.map +1 -0
- package/.dist/components/Checkbox.js +1 -1
- package/.dist/components/Checkbox.js.map +1 -1
- package/.dist/components/Drawer.d.ts +3 -0
- package/.dist/components/Drawer.js +71 -2
- package/.dist/components/Drawer.js.map +1 -1
- package/.dist/components/Dropdown.js +20 -12
- package/.dist/components/Dropdown.js.map +1 -1
- package/.dist/components/Header.d.ts +1 -1
- package/.dist/components/Header.js +12 -10
- package/.dist/components/Header.js.map +1 -1
- package/.dist/components/Input.d.ts +1 -0
- package/.dist/components/Input.js +39 -15
- package/.dist/components/Input.js.map +1 -1
- package/.dist/components/Legend.d.ts +20 -0
- package/.dist/components/Legend.js +180 -0
- package/.dist/components/Legend.js.map +1 -0
- package/.dist/components/Log.js +3 -4
- package/.dist/components/Log.js.map +1 -1
- package/.dist/components/Modal.js +1 -1
- package/.dist/components/Modal.js.map +1 -1
- package/.dist/components/Notification.d.ts +35 -0
- package/.dist/components/Notification.js +120 -0
- package/.dist/components/Notification.js.map +1 -0
- package/.dist/components/Page.d.ts +55 -0
- package/.dist/components/Page.js +374 -0
- package/.dist/components/Page.js.map +1 -0
- package/.dist/components/Progress.d.ts +15 -0
- package/.dist/components/Progress.js +62 -16
- package/.dist/components/Progress.js.map +1 -1
- package/.dist/components/Scrollable.d.ts +60 -15
- package/.dist/components/Scrollable.js +209 -58
- package/.dist/components/Scrollable.js.map +1 -1
- package/.dist/components/ScrollableList.d.ts +16 -9
- package/.dist/components/ScrollableList.js +50 -19
- package/.dist/components/ScrollableList.js.map +1 -1
- package/.dist/components/Slider.d.ts +2 -1
- package/.dist/components/Slider.js +151 -46
- package/.dist/components/Slider.js.map +1 -1
- package/.dist/components/Space.d.ts +0 -3
- package/.dist/components/Space.js +0 -1
- package/.dist/components/Space.js.map +1 -1
- package/.dist/components/Spinner.js +1 -1
- package/.dist/components/Spinner.js.map +1 -1
- package/.dist/components/Stack.js +33 -2
- package/.dist/components/Stack.js.map +1 -1
- package/.dist/components/Table.js +43 -60
- package/.dist/components/Table.js.map +1 -1
- package/.dist/components/Tabs.d.ts +8 -0
- package/.dist/components/Tabs.js +22 -5
- package/.dist/components/Tabs.js.map +1 -1
- package/.dist/components/ToggleGroup.js +1 -1
- package/.dist/components/ToggleGroup.js.map +1 -1
- package/.dist/components/ZStack.d.ts +9 -0
- package/.dist/components/ZStack.js +68 -5
- package/.dist/components/ZStack.js.map +1 -1
- package/.dist/components/index.d.ts +5 -2
- package/.dist/components/index.js +4 -1
- package/.dist/components/index.js.map +1 -1
- package/.dist/events/index.d.ts +6 -2
- package/.dist/events/key.d.ts +18 -4
- package/.dist/events/key.js +42 -6
- package/.dist/events/key.js.map +1 -1
- package/.dist/events/mouse.d.ts +1 -0
- package/.dist/events/mouse.js.map +1 -1
- package/.dist/events/translate.js +17 -4
- package/.dist/events/translate.js.map +1 -1
- package/.dist/index.d.ts +1 -1
- package/.dist/index.js.map +1 -1
- package/.dist/inspect.js +3 -0
- package/.dist/inspect.js.map +1 -1
- package/.dist/managers/FocusManager.d.ts +2 -1
- package/.dist/managers/FocusManager.js +39 -4
- package/.dist/managers/FocusManager.js.map +1 -1
- package/.dist/managers/MouseManager.js +2 -0
- package/.dist/managers/MouseManager.js.map +1 -1
- package/.dist/renderToAnsi.js +1 -1
- package/.dist/renderToAnsi.js.map +1 -1
- package/.dist/terminal.d.ts +1 -0
- package/.dist/testing.d.ts +29 -2
- package/.dist/testing.js +34 -27
- package/.dist/testing.js.map +1 -1
- package/package.json +5 -4
- package/.dist/components/Pin.d.ts +0 -25
- package/.dist/components/Pin.js +0 -80
- package/.dist/components/Pin.js.map +0 -1
|
@@ -2,6 +2,7 @@ import { View } from '../View.js';
|
|
|
2
2
|
import { Point, Rect, Size } from '../geometry.js';
|
|
3
3
|
import { isMouseClicked, } from '../events/index.js';
|
|
4
4
|
import { Style } from '../Style.js';
|
|
5
|
+
import { lineWidth } from '@teaui/term';
|
|
5
6
|
const MONTH_NAMES = [
|
|
6
7
|
'January',
|
|
7
8
|
'February',
|
|
@@ -32,15 +33,21 @@ const MONTH_SHORT = [
|
|
|
32
33
|
];
|
|
33
34
|
const DAY_HEADERS_SUN = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
34
35
|
const DAY_HEADERS_MON = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
|
|
36
|
+
const CLOSE = '×';
|
|
37
|
+
const ARROW_UP = ' [ ↑ ] ';
|
|
38
|
+
const ARROW_DOWN = ' [ ↓ ] ';
|
|
35
39
|
// Width: " Su Mo Tu We Th Fr Sa " = 1 + 7*3 = 22
|
|
36
40
|
const CALENDAR_WIDTH = 22;
|
|
37
|
-
const NAV_BUTTON_WIDTH =
|
|
41
|
+
const NAV_BUTTON_WIDTH = 3;
|
|
38
42
|
const CLOSE_BUTTON_WIDTH = 3;
|
|
39
43
|
const MONTH_LABEL_X = 3;
|
|
40
44
|
// Height: header + weekday row + 6 week rows = 8
|
|
41
45
|
const CALENDAR_HEIGHT = 8;
|
|
42
46
|
export class Calendar extends View {
|
|
47
|
+
// configurable so that tests can test 'today' rendering
|
|
48
|
+
#today;
|
|
43
49
|
#date;
|
|
50
|
+
#cursorDate;
|
|
44
51
|
#visibleDate;
|
|
45
52
|
#onChangeVisible;
|
|
46
53
|
#onChange;
|
|
@@ -51,30 +58,39 @@ export class Calendar extends View {
|
|
|
51
58
|
// Range selection state
|
|
52
59
|
#rangeStart;
|
|
53
60
|
#rangeEnd;
|
|
61
|
+
#rangeNextSelection = 'start';
|
|
62
|
+
#shiftSelecting = false;
|
|
54
63
|
// Year picker state
|
|
55
64
|
#yearScrollOffset = 0;
|
|
56
65
|
#yearSearch = '';
|
|
57
66
|
// Mouse drag tracking
|
|
58
67
|
#dragStartDate;
|
|
59
68
|
// Mouse hover tracking
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
69
|
+
#hoverClose = false;
|
|
70
|
+
#hoverPrevButton = false;
|
|
71
|
+
#hoverNextButton = false;
|
|
72
|
+
#hoverMonthLabel;
|
|
73
|
+
#hoverYearLabel;
|
|
74
|
+
#hoverDate;
|
|
64
75
|
constructor(props = {}) {
|
|
65
76
|
super(props);
|
|
66
|
-
const now = new Date();
|
|
67
|
-
this.#
|
|
77
|
+
const now = props.now ?? new Date();
|
|
78
|
+
this.#today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
|
|
79
|
+
const date = props.date ?? this.#today;
|
|
80
|
+
this.#date = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
|
|
81
|
+
this.#cursorDate = this.#date;
|
|
68
82
|
this.#visibleDate =
|
|
69
|
-
props.visibleDate ??
|
|
83
|
+
props.visibleDate ??
|
|
84
|
+
new Date(this.#today.getFullYear(), this.#today.getMonth(), 1);
|
|
70
85
|
this.#onChangeVisible = props.onChangeVisible;
|
|
71
86
|
this.#onChange = props.onChange;
|
|
72
87
|
this.#selection = props.selection ?? 'single';
|
|
73
88
|
this.#firstDayOfWeek = props.firstDayOfWeek ?? 0;
|
|
74
89
|
}
|
|
75
90
|
update(props) {
|
|
76
|
-
if (props.date !== undefined)
|
|
77
|
-
this.#date = props.date;
|
|
91
|
+
if (props.date !== undefined) {
|
|
92
|
+
this.#date = new Date(props.date.getFullYear(), props.date.getMonth(), props.date.getDate(), 0, 0, 0, 0);
|
|
93
|
+
}
|
|
78
94
|
if (props.visibleDate !== undefined)
|
|
79
95
|
this.#visibleDate = props.visibleDate;
|
|
80
96
|
if (props.onChangeVisible !== undefined)
|
|
@@ -94,6 +110,13 @@ export class Calendar extends View {
|
|
|
94
110
|
this.#date = value;
|
|
95
111
|
this.invalidateRender();
|
|
96
112
|
}
|
|
113
|
+
get cursorDate() {
|
|
114
|
+
return this.#cursorDate;
|
|
115
|
+
}
|
|
116
|
+
set cursorDate(value) {
|
|
117
|
+
this.#cursorDate = value;
|
|
118
|
+
this.invalidateRender();
|
|
119
|
+
}
|
|
97
120
|
get visibleDate() {
|
|
98
121
|
return this.#visibleDate;
|
|
99
122
|
}
|
|
@@ -107,16 +130,65 @@ export class Calendar extends View {
|
|
|
107
130
|
naturalSize() {
|
|
108
131
|
return new Size(CALENDAR_WIDTH, CALENDAR_HEIGHT);
|
|
109
132
|
}
|
|
133
|
+
get #hoverStyle() {
|
|
134
|
+
return new Style({
|
|
135
|
+
foreground: this.theme.text().foreground,
|
|
136
|
+
background: this.theme.darkenColor,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
get #buttonHoverStyle() {
|
|
140
|
+
return this.theme.ui({ isHover: true });
|
|
141
|
+
}
|
|
142
|
+
get #rangeGapStyle() {
|
|
143
|
+
return new Style({ background: this.theme.darkenColor });
|
|
144
|
+
}
|
|
145
|
+
get #weekdayStyle() {
|
|
146
|
+
return new Style({
|
|
147
|
+
bold: true,
|
|
148
|
+
foreground: this.theme.highlightColor,
|
|
149
|
+
background: this.theme.text().background,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
get #rangeEndpointStyle() {
|
|
153
|
+
return new Style({
|
|
154
|
+
bold: true,
|
|
155
|
+
foreground: this.theme.textColor,
|
|
156
|
+
background: this.theme.darkenColor,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
get #inRangeStyle() {
|
|
160
|
+
return new Style({
|
|
161
|
+
foreground: this.theme.highlightColor,
|
|
162
|
+
background: this.theme.darkenColor,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
get #todayStyle() {
|
|
166
|
+
return new Style({
|
|
167
|
+
bold: true,
|
|
168
|
+
foreground: this.theme.contrastTextColor,
|
|
169
|
+
background: this.theme.text().background,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
get #cursorStyle() {
|
|
173
|
+
return new Style({
|
|
174
|
+
bold: true,
|
|
175
|
+
foreground: this.theme.text().foreground,
|
|
176
|
+
background: this.theme.darkenColor,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
get #selectedStyle() {
|
|
180
|
+
return new Style({
|
|
181
|
+
bold: true,
|
|
182
|
+
foreground: this.theme.textColor,
|
|
183
|
+
background: this.theme.highlightColor,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
110
186
|
#widgetRect(viewport) {
|
|
111
187
|
return new Rect(Point.zero, [
|
|
112
188
|
Math.min(CALENDAR_WIDTH, viewport.contentSize.width),
|
|
113
189
|
Math.min(CALENDAR_HEIGHT, viewport.contentSize.height),
|
|
114
190
|
]);
|
|
115
191
|
}
|
|
116
|
-
#today() {
|
|
117
|
-
const now = new Date();
|
|
118
|
-
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
119
|
-
}
|
|
120
192
|
#isSameDay(a, b) {
|
|
121
193
|
return (a.getFullYear() === b.getFullYear() &&
|
|
122
194
|
a.getMonth() === b.getMonth() &&
|
|
@@ -152,8 +224,10 @@ export class Calendar extends View {
|
|
|
152
224
|
}
|
|
153
225
|
#startRangeSelection(date, syncVisibleDate = true) {
|
|
154
226
|
this.#rangeStart = date;
|
|
155
|
-
this.#rangeEnd =
|
|
227
|
+
this.#rangeEnd = date;
|
|
228
|
+
this.#rangeNextSelection = 'end';
|
|
156
229
|
this.#date = date;
|
|
230
|
+
this.#cursorDate = date;
|
|
157
231
|
if (syncVisibleDate) {
|
|
158
232
|
this.#syncVisibleDate(date);
|
|
159
233
|
}
|
|
@@ -165,7 +239,9 @@ export class Calendar extends View {
|
|
|
165
239
|
return;
|
|
166
240
|
}
|
|
167
241
|
this.#rangeEnd = date;
|
|
242
|
+
this.#rangeNextSelection = 'start';
|
|
168
243
|
this.#date = date;
|
|
244
|
+
this.#cursorDate = date;
|
|
169
245
|
if (syncVisibleDate) {
|
|
170
246
|
this.#syncVisibleDate(date);
|
|
171
247
|
}
|
|
@@ -176,7 +252,7 @@ export class Calendar extends View {
|
|
|
176
252
|
}
|
|
177
253
|
#selectDate(date) {
|
|
178
254
|
if (this.#selection === 'range') {
|
|
179
|
-
if (
|
|
255
|
+
if (this.#rangeNextSelection === 'start') {
|
|
180
256
|
this.#startRangeSelection(date);
|
|
181
257
|
}
|
|
182
258
|
else {
|
|
@@ -185,24 +261,59 @@ export class Calendar extends View {
|
|
|
185
261
|
return;
|
|
186
262
|
}
|
|
187
263
|
this.#date = date;
|
|
264
|
+
this.#cursorDate = date;
|
|
188
265
|
this.#onChange?.(date, date);
|
|
189
266
|
this.#syncVisibleDate(date);
|
|
190
267
|
this.invalidateRender();
|
|
191
268
|
}
|
|
269
|
+
#selectCursorDate(date) {
|
|
270
|
+
this.#cursorDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
|
|
271
|
+
this.#syncVisibleDate(date);
|
|
272
|
+
this.invalidateRender();
|
|
273
|
+
}
|
|
274
|
+
#moveCursorDateBy(days) {
|
|
275
|
+
this.#shiftSelecting = false;
|
|
276
|
+
const nextDate = new Date(this.#cursorDate);
|
|
277
|
+
nextDate.setDate(nextDate.getDate() + days);
|
|
278
|
+
this.#cursorDate = nextDate;
|
|
279
|
+
// Navigate months if needed
|
|
280
|
+
if (nextDate.getMonth() !== this.#visibleDate.getMonth() ||
|
|
281
|
+
nextDate.getFullYear() !== this.#visibleDate.getFullYear()) {
|
|
282
|
+
const vis = new Date(nextDate.getFullYear(), nextDate.getMonth(), 1);
|
|
283
|
+
this.#visibleDate = vis;
|
|
284
|
+
this.#onChangeVisible?.(vis);
|
|
285
|
+
}
|
|
286
|
+
this.invalidateRender();
|
|
287
|
+
}
|
|
288
|
+
#shiftSelectBy(days) {
|
|
289
|
+
if (!this.#shiftSelecting) {
|
|
290
|
+
this.#rangeStart = new Date(this.#cursorDate);
|
|
291
|
+
}
|
|
292
|
+
this.#moveCursorDateBy(days);
|
|
293
|
+
this.#shiftSelecting = true;
|
|
294
|
+
this.#rangeEnd = new Date(this.#cursorDate);
|
|
295
|
+
this.#rangeNextSelection = 'start';
|
|
296
|
+
}
|
|
192
297
|
#selectMonth(month) {
|
|
193
298
|
const d = new Date(this.#visibleDate.getFullYear(), month, 1);
|
|
194
299
|
this.#visibleDate = d;
|
|
195
|
-
this.#
|
|
300
|
+
this.#selectDisplayMode('days');
|
|
196
301
|
this.#onChangeVisible?.(d);
|
|
197
302
|
this.invalidateRender();
|
|
198
303
|
}
|
|
199
304
|
#selectYear(year) {
|
|
200
305
|
const d = new Date(year, this.#visibleDate.getMonth(), 1);
|
|
201
306
|
this.#visibleDate = d;
|
|
202
|
-
this.#
|
|
307
|
+
this.#selectDisplayMode('days');
|
|
203
308
|
this.#onChangeVisible?.(d);
|
|
204
309
|
this.invalidateRender();
|
|
205
310
|
}
|
|
311
|
+
#selectDisplayMode(mode) {
|
|
312
|
+
this.#displayMode = mode;
|
|
313
|
+
this.#hoverClose = false;
|
|
314
|
+
this.#hoverMonthLabel = undefined;
|
|
315
|
+
this.#hoverYearLabel = undefined;
|
|
316
|
+
}
|
|
206
317
|
#getDateAtPosition(x, y) {
|
|
207
318
|
if (y < 2 || y > 7)
|
|
208
319
|
return undefined;
|
|
@@ -239,6 +350,9 @@ export class Calendar extends View {
|
|
|
239
350
|
}
|
|
240
351
|
receiveMouse(event, system) {
|
|
241
352
|
super.receiveMouse(event, system);
|
|
353
|
+
if (event.name === 'mouse.button.down') {
|
|
354
|
+
system.requestFocus();
|
|
355
|
+
}
|
|
242
356
|
if (this.#displayMode === 'days') {
|
|
243
357
|
this.#receiveMouseDays(event);
|
|
244
358
|
}
|
|
@@ -249,40 +363,55 @@ export class Calendar extends View {
|
|
|
249
363
|
this.#receiveMouseYears(event);
|
|
250
364
|
}
|
|
251
365
|
}
|
|
366
|
+
#formatMonthTitle(date) {
|
|
367
|
+
const totalWidth = 10;
|
|
368
|
+
const textWidth = lineWidth(MONTH_NAMES[date.getMonth()]);
|
|
369
|
+
const pad = Math.max(0, totalWidth - textWidth);
|
|
370
|
+
return (' '.repeat(Math.floor(pad / 2)) +
|
|
371
|
+
MONTH_NAMES[date.getMonth()] +
|
|
372
|
+
' '.repeat(Math.ceil(pad / 2)));
|
|
373
|
+
}
|
|
374
|
+
#formatYearTitle(date) {
|
|
375
|
+
return ` ${String(date.getFullYear())} `;
|
|
376
|
+
}
|
|
377
|
+
#yearLabelX(yearStr) {
|
|
378
|
+
return CALENDAR_WIDTH - NAV_BUTTON_WIDTH - lineWidth(yearStr);
|
|
379
|
+
}
|
|
252
380
|
#receiveMouseDays(event) {
|
|
253
381
|
const x = event.position.x;
|
|
254
382
|
const y = event.position.y;
|
|
255
383
|
// Reset hover states
|
|
256
|
-
this.#
|
|
257
|
-
this.#
|
|
258
|
-
this.#hoverMonthLabel =
|
|
259
|
-
this.#hoverYearLabel =
|
|
384
|
+
this.#hoverPrevButton = false;
|
|
385
|
+
this.#hoverNextButton = false;
|
|
386
|
+
this.#hoverMonthLabel = undefined;
|
|
387
|
+
this.#hoverYearLabel = undefined;
|
|
260
388
|
if (y === 0) {
|
|
261
|
-
const monthName =
|
|
262
|
-
const yearStr =
|
|
263
|
-
const yearStart =
|
|
389
|
+
const monthName = this.#formatMonthTitle(this.#visibleDate);
|
|
390
|
+
const yearStr = this.#formatYearTitle(this.#visibleDate);
|
|
391
|
+
const yearStart = this.#yearLabelX(yearStr);
|
|
264
392
|
if (x < NAV_BUTTON_WIDTH) {
|
|
265
|
-
this.#
|
|
393
|
+
this.#hoverPrevButton = true;
|
|
266
394
|
if (isMouseClicked(event)) {
|
|
267
395
|
this.#navigateMonth(-1);
|
|
268
396
|
}
|
|
269
397
|
}
|
|
270
398
|
else if (x >= CALENDAR_WIDTH - NAV_BUTTON_WIDTH) {
|
|
271
|
-
this.#
|
|
399
|
+
this.#hoverNextButton = true;
|
|
272
400
|
if (isMouseClicked(event)) {
|
|
273
401
|
this.#navigateMonth(1);
|
|
274
402
|
}
|
|
275
403
|
}
|
|
276
|
-
else if (x >= MONTH_LABEL_X &&
|
|
277
|
-
|
|
404
|
+
else if (x >= MONTH_LABEL_X &&
|
|
405
|
+
x < MONTH_LABEL_X + lineWidth(monthName)) {
|
|
406
|
+
this.#hoverMonthLabel = 'true';
|
|
278
407
|
if (isMouseClicked(event)) {
|
|
279
|
-
this.#
|
|
408
|
+
this.#selectDisplayMode('months');
|
|
280
409
|
}
|
|
281
410
|
}
|
|
282
|
-
else if (x >= yearStart && x < yearStart + yearStr
|
|
283
|
-
this.#hoverYearLabel = true;
|
|
411
|
+
else if (x >= yearStart && x < yearStart + lineWidth(yearStr)) {
|
|
412
|
+
this.#hoverYearLabel = 'true';
|
|
284
413
|
if (isMouseClicked(event)) {
|
|
285
|
-
this.#
|
|
414
|
+
this.#selectDisplayMode('years');
|
|
286
415
|
this.#yearScrollOffset = 0;
|
|
287
416
|
this.#yearSearch = '';
|
|
288
417
|
}
|
|
@@ -290,6 +419,7 @@ export class Calendar extends View {
|
|
|
290
419
|
return;
|
|
291
420
|
}
|
|
292
421
|
const date = this.#getDateAtPosition(x, y);
|
|
422
|
+
this.#hoverDate = date;
|
|
293
423
|
if (!date) {
|
|
294
424
|
if (event.name === 'mouse.button.cancel' ||
|
|
295
425
|
event.name === 'mouse.button.up') {
|
|
@@ -300,9 +430,7 @@ export class Calendar extends View {
|
|
|
300
430
|
if (this.#selection === 'range') {
|
|
301
431
|
switch (event.name) {
|
|
302
432
|
case 'mouse.button.down':
|
|
303
|
-
if (this.#rangeStart
|
|
304
|
-
!this.#rangeEnd &&
|
|
305
|
-
!this.#isSameDay(this.#rangeStart, date)) {
|
|
433
|
+
if (this.#rangeNextSelection === 'end' && this.#rangeStart) {
|
|
306
434
|
this.#dragStartDate = undefined;
|
|
307
435
|
this.#finishRangeSelection(date);
|
|
308
436
|
}
|
|
@@ -317,6 +445,7 @@ export class Calendar extends View {
|
|
|
317
445
|
this.#rangeStart = this.#dragStartDate;
|
|
318
446
|
this.#rangeEnd = date;
|
|
319
447
|
this.#date = date;
|
|
448
|
+
this.#cursorDate = date;
|
|
320
449
|
this.invalidateRender();
|
|
321
450
|
}
|
|
322
451
|
break;
|
|
@@ -342,50 +471,72 @@ export class Calendar extends View {
|
|
|
342
471
|
#receiveMouseMonths(event) {
|
|
343
472
|
const x = event.position.x;
|
|
344
473
|
const y = event.position.y;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
474
|
+
// Reset hover states
|
|
475
|
+
this.#hoverClose = false;
|
|
476
|
+
this.#hoverMonthLabel = undefined;
|
|
477
|
+
if (y === 0 && x >= CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH) {
|
|
478
|
+
this.#hoverClose = true;
|
|
479
|
+
if (isMouseClicked(event)) {
|
|
480
|
+
this.#selectDisplayMode('days');
|
|
349
481
|
this.invalidateRender();
|
|
350
482
|
return;
|
|
351
483
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
484
|
+
}
|
|
485
|
+
// Month grid: 4 rows × 3 columns, starting at y=2
|
|
486
|
+
if (y >= 2 && y <= 5) {
|
|
487
|
+
const col = Math.floor(x / 7);
|
|
488
|
+
const row = y - 2;
|
|
489
|
+
if (col >= 0 && col < 3 && row >= 0 && row < 4) {
|
|
490
|
+
const month = row * 3 + col;
|
|
491
|
+
this.#hoverMonthLabel = MONTH_SHORT[month];
|
|
492
|
+
if (isMouseClicked(event)) {
|
|
358
493
|
this.#selectMonth(month);
|
|
359
494
|
}
|
|
360
495
|
}
|
|
361
496
|
}
|
|
497
|
+
this.invalidateRender();
|
|
362
498
|
}
|
|
363
499
|
#receiveMouseYears(event) {
|
|
364
500
|
const x = event.position.x;
|
|
365
501
|
const y = event.position.y;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
502
|
+
// Reset hover states
|
|
503
|
+
this.#hoverClose = false;
|
|
504
|
+
this.#hoverPrevButton = false;
|
|
505
|
+
this.#hoverNextButton = false;
|
|
506
|
+
this.#hoverYearLabel = undefined;
|
|
507
|
+
if (y === 0 && x >= CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH) {
|
|
508
|
+
this.#hoverClose = true;
|
|
509
|
+
if (isMouseClicked(event)) {
|
|
510
|
+
this.#selectDisplayMode('days');
|
|
370
511
|
this.invalidateRender();
|
|
371
512
|
return;
|
|
372
513
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
// Scroll arrows
|
|
380
|
-
if (y === 1) {
|
|
514
|
+
}
|
|
515
|
+
// Scroll arrows
|
|
516
|
+
if (y === 1) {
|
|
517
|
+
this.#hoverPrevButton = true;
|
|
518
|
+
if (isMouseClicked(event)) {
|
|
381
519
|
this.#yearScrollOffset -= 5;
|
|
382
520
|
this.invalidateRender();
|
|
383
521
|
}
|
|
384
|
-
|
|
522
|
+
}
|
|
523
|
+
else if (y === 7) {
|
|
524
|
+
this.#hoverNextButton = true;
|
|
525
|
+
if (isMouseClicked(event)) {
|
|
385
526
|
this.#yearScrollOffset += 5;
|
|
386
527
|
this.invalidateRender();
|
|
387
528
|
}
|
|
388
529
|
}
|
|
530
|
+
// Year rows at y=2..6 (5 visible years)
|
|
531
|
+
if (y >= 2 && y <= 6) {
|
|
532
|
+
const yearIndex = y - 2;
|
|
533
|
+
const baseYear = this.#visibleDate.getFullYear() - 2 + this.#yearScrollOffset;
|
|
534
|
+
const year = baseYear + yearIndex;
|
|
535
|
+
this.#hoverYearLabel = String(year);
|
|
536
|
+
if (isMouseClicked(event)) {
|
|
537
|
+
this.#selectYear(year);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
389
540
|
// Scroll wheel
|
|
390
541
|
if (event.name === 'mouse.wheel.up') {
|
|
391
542
|
this.#yearScrollOffset -= 1;
|
|
@@ -395,6 +546,7 @@ export class Calendar extends View {
|
|
|
395
546
|
this.#yearScrollOffset += 1;
|
|
396
547
|
this.invalidateRender();
|
|
397
548
|
}
|
|
549
|
+
this.invalidateRender();
|
|
398
550
|
}
|
|
399
551
|
receiveKey(event) {
|
|
400
552
|
if (this.#displayMode === 'days') {
|
|
@@ -409,20 +561,43 @@ export class Calendar extends View {
|
|
|
409
561
|
}
|
|
410
562
|
#receiveKeyDays(event) {
|
|
411
563
|
switch (event.name) {
|
|
564
|
+
case 't':
|
|
565
|
+
this.#selectCursorDate(new Date());
|
|
566
|
+
break;
|
|
412
567
|
case 'left':
|
|
413
|
-
|
|
568
|
+
if (event.shift) {
|
|
569
|
+
this.#shiftSelectBy(-1);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
this.#moveCursorDateBy(-1);
|
|
573
|
+
}
|
|
414
574
|
break;
|
|
415
575
|
case 'right':
|
|
416
|
-
|
|
576
|
+
if (event.shift) {
|
|
577
|
+
this.#shiftSelectBy(1);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
this.#moveCursorDateBy(1);
|
|
581
|
+
}
|
|
417
582
|
break;
|
|
418
583
|
case 'up':
|
|
419
|
-
|
|
584
|
+
if (event.shift) {
|
|
585
|
+
this.#shiftSelectBy(-7);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
this.#moveCursorDateBy(-7);
|
|
589
|
+
}
|
|
420
590
|
break;
|
|
421
591
|
case 'down':
|
|
422
|
-
|
|
592
|
+
if (event.shift) {
|
|
593
|
+
this.#shiftSelectBy(7);
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
this.#moveCursorDateBy(7);
|
|
597
|
+
}
|
|
423
598
|
break;
|
|
424
599
|
case 'return':
|
|
425
|
-
this.#selectDate(this.#
|
|
600
|
+
this.#selectDate(this.#cursorDate);
|
|
426
601
|
break;
|
|
427
602
|
case 'escape':
|
|
428
603
|
// Do nothing in day view
|
|
@@ -434,14 +609,14 @@ export class Calendar extends View {
|
|
|
434
609
|
this.#navigateMonth(1);
|
|
435
610
|
break;
|
|
436
611
|
case 'home': {
|
|
437
|
-
const
|
|
438
|
-
this.#
|
|
612
|
+
const nextDate = new Date(this.#visibleDate.getFullYear(), this.#visibleDate.getMonth(), 1);
|
|
613
|
+
this.#cursorDate = nextDate;
|
|
439
614
|
this.invalidateRender();
|
|
440
615
|
break;
|
|
441
616
|
}
|
|
442
617
|
case 'end': {
|
|
443
|
-
const
|
|
444
|
-
this.#
|
|
618
|
+
const nextDate = new Date(this.#visibleDate.getFullYear(), this.#visibleDate.getMonth() + 1, 0);
|
|
619
|
+
this.#cursorDate = nextDate;
|
|
445
620
|
this.invalidateRender();
|
|
446
621
|
break;
|
|
447
622
|
}
|
|
@@ -450,7 +625,7 @@ export class Calendar extends View {
|
|
|
450
625
|
#receiveKeyMonths(event) {
|
|
451
626
|
switch (event.name) {
|
|
452
627
|
case 'escape':
|
|
453
|
-
this.#
|
|
628
|
+
this.#selectDisplayMode('days');
|
|
454
629
|
this.invalidateRender();
|
|
455
630
|
break;
|
|
456
631
|
case 'return':
|
|
@@ -493,7 +668,7 @@ export class Calendar extends View {
|
|
|
493
668
|
#receiveKeyYears(event) {
|
|
494
669
|
switch (event.name) {
|
|
495
670
|
case 'escape':
|
|
496
|
-
this.#
|
|
671
|
+
this.#selectDisplayMode('days');
|
|
497
672
|
this.#yearSearch = '';
|
|
498
673
|
this.invalidateRender();
|
|
499
674
|
break;
|
|
@@ -542,19 +717,6 @@ export class Calendar extends View {
|
|
|
542
717
|
}
|
|
543
718
|
this.invalidateRender();
|
|
544
719
|
}
|
|
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
720
|
render(viewport) {
|
|
559
721
|
const hasFocus = viewport.registerFocus();
|
|
560
722
|
this.#hasFocus = hasFocus;
|
|
@@ -574,54 +736,37 @@ export class Calendar extends View {
|
|
|
574
736
|
break;
|
|
575
737
|
}
|
|
576
738
|
}
|
|
577
|
-
get #selectedStyle() {
|
|
578
|
-
return new Style({
|
|
579
|
-
bold: true,
|
|
580
|
-
foreground: this.theme.textColor,
|
|
581
|
-
background: this.theme.highlightColor,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
739
|
#renderDays(viewport) {
|
|
585
|
-
const today = this.#today
|
|
586
|
-
const year = this.#visibleDate.getFullYear();
|
|
740
|
+
const today = this.#today;
|
|
587
741
|
const month = this.#visibleDate.getMonth();
|
|
588
|
-
const monthName =
|
|
589
|
-
const yearStr =
|
|
742
|
+
const monthName = this.#formatMonthTitle(this.#visibleDate);
|
|
743
|
+
const yearStr = this.#formatYearTitle(this.#visibleDate);
|
|
590
744
|
const textStyle = this.theme.text();
|
|
591
745
|
const dimStyle = this.theme.text({ isPlaceholder: true });
|
|
746
|
+
const hoverStyle = this.#hoverStyle;
|
|
592
747
|
const headerStyle = this.theme.ui({ isHover: false });
|
|
593
748
|
const selectedStyle = this.#selectedStyle;
|
|
594
|
-
const todayStyle =
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
background: textStyle.background,
|
|
598
|
-
});
|
|
599
|
-
const rangeStyle = new Style({
|
|
600
|
-
foreground: this.theme.highlightColor,
|
|
601
|
-
background: textStyle.background,
|
|
602
|
-
});
|
|
749
|
+
const todayStyle = this.#todayStyle;
|
|
750
|
+
const inRangeStyle = this.#inRangeStyle;
|
|
751
|
+
const rangeEndpointStyle = this.#rangeEndpointStyle;
|
|
603
752
|
viewport.paint(textStyle, this.#widgetRect(viewport));
|
|
604
753
|
// Header: "◃ June 2026 ▹"
|
|
605
|
-
const prevArrow = this.#
|
|
606
|
-
const nextArrow = this.#
|
|
754
|
+
const prevArrow = this.#hoverPrevButton ? ' ◂ ' : ' ◃ ';
|
|
755
|
+
const nextArrow = this.#hoverNextButton ? ' ▸ ' : ' ▹ ';
|
|
607
756
|
const monthLabelStyle = this.#hoverMonthLabel
|
|
608
|
-
? this
|
|
757
|
+
? this.#buttonHoverStyle
|
|
609
758
|
: headerStyle;
|
|
610
759
|
const yearLabelStyle = this.#hoverYearLabel
|
|
611
|
-
? this
|
|
760
|
+
? this.#buttonHoverStyle
|
|
612
761
|
: headerStyle;
|
|
613
|
-
const
|
|
614
|
-
viewport.write(prevArrow, new Point(0, 0), headerStyle);
|
|
762
|
+
const yearStart = this.#yearLabelX(yearStr);
|
|
763
|
+
viewport.write(prevArrow, new Point(0, 0), this.#hoverPrevButton ? this.#buttonHoverStyle : headerStyle);
|
|
615
764
|
viewport.write(monthName, new Point(MONTH_LABEL_X, 0), monthLabelStyle);
|
|
616
|
-
viewport.write(yearStr, new Point(
|
|
617
|
-
viewport.write(nextArrow, new Point(CALENDAR_WIDTH - NAV_BUTTON_WIDTH, 0), headerStyle);
|
|
765
|
+
viewport.write(yearStr, new Point(yearStart, 0), yearLabelStyle);
|
|
766
|
+
viewport.write(nextArrow, new Point(CALENDAR_WIDTH - NAV_BUTTON_WIDTH, 0), this.#hoverNextButton ? this.#buttonHoverStyle : headerStyle);
|
|
618
767
|
// Weekday headers
|
|
619
768
|
const dayHeaders = this.#firstDayOfWeek === 1 ? DAY_HEADERS_MON : DAY_HEADERS_SUN;
|
|
620
|
-
const weekdayStyle =
|
|
621
|
-
bold: true,
|
|
622
|
-
foreground: this.theme.highlightColor,
|
|
623
|
-
background: textStyle.background,
|
|
624
|
-
});
|
|
769
|
+
const weekdayStyle = this.#weekdayStyle;
|
|
625
770
|
for (let i = 0; i < 7; i++) {
|
|
626
771
|
viewport.write(dayHeaders[i], new Point(1 + i * 3, 1), weekdayStyle);
|
|
627
772
|
}
|
|
@@ -630,17 +775,30 @@ export class Calendar extends View {
|
|
|
630
775
|
for (let week = 0; week < 6; week++) {
|
|
631
776
|
for (let day = 0; day < 7; day++) {
|
|
632
777
|
const date = grid[week][day];
|
|
778
|
+
const isHover = this.#hoverDate && this.#isSameDay(date, this.#hoverDate);
|
|
633
779
|
const isCurrentMonth = date.getMonth() === month;
|
|
634
780
|
const isToday = this.#isSameDay(date, today);
|
|
635
|
-
const isSelected = this.#isSameDay(date, this.#date);
|
|
781
|
+
const isSelected = this.#isSameDay(date, this.#date) && this.#selection === 'single';
|
|
782
|
+
const isCursor = !this.#isSameDay(this.#cursorDate, this.#date) &&
|
|
783
|
+
this.#isSameDay(date, this.#cursorDate);
|
|
636
784
|
const isInRange = this.#isInRange(date);
|
|
637
785
|
const dayNum = String(date.getDate()).padStart(2, ' ');
|
|
786
|
+
const isRangeEndpoint = this.#rangeStart &&
|
|
787
|
+
this.#rangeEnd &&
|
|
788
|
+
(this.#isSameDay(date, this.#rangeStart) ||
|
|
789
|
+
this.#isSameDay(date, this.#rangeEnd));
|
|
638
790
|
let style;
|
|
639
|
-
if (
|
|
791
|
+
if (isRangeEndpoint) {
|
|
792
|
+
style = rangeEndpointStyle;
|
|
793
|
+
}
|
|
794
|
+
else if (isSelected) {
|
|
640
795
|
style = selectedStyle;
|
|
641
796
|
}
|
|
797
|
+
else if (isCursor) {
|
|
798
|
+
style = this.#cursorStyle;
|
|
799
|
+
}
|
|
642
800
|
else if (isInRange) {
|
|
643
|
-
style =
|
|
801
|
+
style = inRangeStyle;
|
|
644
802
|
}
|
|
645
803
|
else if (isToday) {
|
|
646
804
|
style = todayStyle;
|
|
@@ -648,32 +806,96 @@ export class Calendar extends View {
|
|
|
648
806
|
else if (!isCurrentMonth) {
|
|
649
807
|
style = dimStyle;
|
|
650
808
|
}
|
|
809
|
+
else if (isHover) {
|
|
810
|
+
style = hoverStyle;
|
|
811
|
+
}
|
|
651
812
|
else {
|
|
652
813
|
style = textStyle;
|
|
653
814
|
}
|
|
654
815
|
viewport.write(dayNum, new Point(1 + day * 3, 2 + week), style);
|
|
816
|
+
// Paint the gap between consecutive in-range days
|
|
817
|
+
if (day < 6) {
|
|
818
|
+
const nextDate = grid[week][day + 1];
|
|
819
|
+
const thisInRange = isSelected || isInRange;
|
|
820
|
+
const nextInRange = this.#isSameDay(nextDate, this.#date) || this.#isInRange(nextDate);
|
|
821
|
+
if (thisInRange && nextInRange) {
|
|
822
|
+
const gapStyle = this.#rangeGapStyle;
|
|
823
|
+
viewport.write(' ', new Point(1 + day * 3 + 2, 2 + week), gapStyle);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (this.#selection === 'range' &&
|
|
828
|
+
this.#rangeStart &&
|
|
829
|
+
this.#rangeEnd &&
|
|
830
|
+
!this.#isSameDay(this.#rangeStart, this.#rangeEnd)) {
|
|
831
|
+
// Paint the leading gap before the first in-range day of each row
|
|
832
|
+
// (the space at position 0 on the row, before the first day column)
|
|
833
|
+
const firstDate = grid[week][0];
|
|
834
|
+
const firstInRange = this.#isSameDay(firstDate, this.#date) || this.#isInRange(firstDate);
|
|
835
|
+
if (firstInRange) {
|
|
836
|
+
const gapStyle = this.#rangeGapStyle;
|
|
837
|
+
viewport.write(' ', new Point(0, 2 + week), gapStyle);
|
|
838
|
+
}
|
|
839
|
+
// Paint the trailing gap after the last in-range day of each row
|
|
840
|
+
const lastDate = grid[week][6];
|
|
841
|
+
const lastInRange = this.#isSameDay(lastDate, this.#date) || this.#isInRange(lastDate);
|
|
842
|
+
if (lastInRange) {
|
|
843
|
+
const gapStyle = this.#rangeGapStyle;
|
|
844
|
+
viewport.write(' ', new Point(1 + 6 * 3 + 2, 2 + week), gapStyle);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
// Focus border
|
|
849
|
+
if (this.#hasFocus) {
|
|
850
|
+
const borderStyle = weekdayStyle;
|
|
851
|
+
// Top row (weekday header row): ╭...╮ with ─ between headers
|
|
852
|
+
viewport.write('╭', new Point(0, 1), borderStyle);
|
|
853
|
+
viewport.write('╮', new Point(CALENDAR_WIDTH - 1, 1), borderStyle);
|
|
854
|
+
for (let i = 0; i < 6; i++) {
|
|
855
|
+
viewport.write('─', new Point(3 + i * 3, 1), borderStyle);
|
|
856
|
+
}
|
|
857
|
+
// Middle rows: │...│
|
|
858
|
+
for (let week = 0; week < 5; week++) {
|
|
859
|
+
viewport.write('│', new Point(0, 2 + week), borderStyle);
|
|
860
|
+
viewport.write('│', new Point(CALENDAR_WIDTH - 1, 2 + week), borderStyle);
|
|
861
|
+
}
|
|
862
|
+
// Bottom row (last week row): ╰...╯ with ─ between days
|
|
863
|
+
viewport.write('╰', new Point(0, 7), borderStyle);
|
|
864
|
+
viewport.write('╯', new Point(CALENDAR_WIDTH - 1, 7), borderStyle);
|
|
865
|
+
for (let i = 0; i < 6; i++) {
|
|
866
|
+
viewport.write('─', new Point(3 + i * 3, 7), borderStyle);
|
|
655
867
|
}
|
|
656
868
|
}
|
|
657
869
|
}
|
|
658
870
|
#renderMonths(viewport) {
|
|
659
871
|
const textStyle = this.theme.text();
|
|
660
872
|
const headerStyle = this.theme.ui({ isHover: false });
|
|
661
|
-
const selectedStyle = this.#selectedStyle;
|
|
662
873
|
const currentMonth = this.#visibleDate.getMonth();
|
|
663
874
|
viewport.paint(textStyle, this.#widgetRect(viewport));
|
|
664
875
|
// Header
|
|
665
|
-
const monthName =
|
|
876
|
+
const monthName = this.#formatMonthTitle(this.#visibleDate);
|
|
666
877
|
const headerText = monthName;
|
|
667
|
-
const headerX = Math.max(0, Math.floor((CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH - headerText
|
|
878
|
+
const headerX = Math.max(0, Math.floor((CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH - lineWidth(headerText)) / 2) + 1);
|
|
668
879
|
viewport.write(headerText, new Point(headerX, 0), headerStyle);
|
|
669
|
-
|
|
880
|
+
const closeStyle = this.#hoverClose ? this.#buttonHoverStyle : headerStyle;
|
|
881
|
+
viewport.write(` ${CLOSE} `, new Point(CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH, 0), closeStyle);
|
|
670
882
|
// Month grid: 4 rows × 3 columns
|
|
671
883
|
for (let row = 0; row < 4; row++) {
|
|
672
884
|
for (let col = 0; col < 3; col++) {
|
|
673
885
|
const m = row * 3 + col;
|
|
674
886
|
const name = MONTH_SHORT[m];
|
|
675
887
|
const isSelected = m === currentMonth;
|
|
676
|
-
const
|
|
888
|
+
const isHover = this.#hoverMonthLabel === name;
|
|
889
|
+
let style;
|
|
890
|
+
if (isSelected) {
|
|
891
|
+
style = this.#selectedStyle;
|
|
892
|
+
}
|
|
893
|
+
else if (isHover) {
|
|
894
|
+
style = this.#hoverStyle;
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
style = textStyle;
|
|
898
|
+
}
|
|
677
899
|
// Each column is ~7 chars wide (fits in 22 width: 7*3=21 + 1 padding)
|
|
678
900
|
const x = 1 + col * 7;
|
|
679
901
|
const y = 2 + row;
|
|
@@ -685,36 +907,48 @@ export class Calendar extends View {
|
|
|
685
907
|
#renderYears(viewport) {
|
|
686
908
|
const textStyle = this.theme.text();
|
|
687
909
|
const headerStyle = this.theme.ui({ isHover: false });
|
|
688
|
-
const selectedStyle = this.#selectedStyle;
|
|
689
910
|
const currentYear = this.#visibleDate.getFullYear();
|
|
690
911
|
viewport.paint(textStyle, this.#widgetRect(viewport));
|
|
691
912
|
// Header
|
|
692
913
|
const headerText = 'Year';
|
|
693
|
-
const headerX = Math.max(0, Math.floor((CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH - headerText
|
|
914
|
+
const headerX = Math.max(0, Math.floor((CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH - lineWidth(headerText)) / 2) + 1);
|
|
694
915
|
viewport.write(headerText, new Point(headerX, 0), headerStyle);
|
|
695
|
-
|
|
916
|
+
const closeStyle = this.#hoverClose ? this.#buttonHoverStyle : headerStyle;
|
|
917
|
+
viewport.write(` ${CLOSE} `, new Point(CALENDAR_WIDTH - CLOSE_BUTTON_WIDTH, 0), closeStyle);
|
|
696
918
|
// Scroll indicator
|
|
697
|
-
|
|
919
|
+
const prevStyle = this.#hoverPrevButton
|
|
920
|
+
? this.#buttonHoverStyle
|
|
921
|
+
: headerStyle;
|
|
922
|
+
viewport.write(ARROW_UP, new Point(Math.floor((CALENDAR_WIDTH - lineWidth(ARROW_UP)) / 2), 1), prevStyle);
|
|
698
923
|
// 5 visible years
|
|
699
924
|
const baseYear = currentYear - 2 + this.#yearScrollOffset;
|
|
700
925
|
for (let i = 0; i < 5; i++) {
|
|
701
926
|
const year = baseYear + i;
|
|
702
|
-
const isSelected = year === currentYear;
|
|
703
|
-
const style = isSelected ? selectedStyle : textStyle;
|
|
704
927
|
const yearStr = String(year);
|
|
705
|
-
const
|
|
928
|
+
const isSelected = year === currentYear;
|
|
929
|
+
const isHover = this.#hoverYearLabel === yearStr;
|
|
930
|
+
let style;
|
|
931
|
+
if (isSelected) {
|
|
932
|
+
style = this.#selectedStyle;
|
|
933
|
+
}
|
|
934
|
+
else if (isHover) {
|
|
935
|
+
style = this.#hoverStyle;
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
style = textStyle;
|
|
939
|
+
}
|
|
940
|
+
const x = Math.floor((CALENDAR_WIDTH - lineWidth(yearStr)) / 2);
|
|
706
941
|
viewport.write(yearStr, new Point(x, 2 + i), style);
|
|
707
942
|
}
|
|
708
943
|
// Down arrow
|
|
709
|
-
|
|
944
|
+
const nextStyle = this.#hoverNextButton
|
|
945
|
+
? this.#buttonHoverStyle
|
|
946
|
+
: headerStyle;
|
|
947
|
+
viewport.write(ARROW_DOWN, new Point(Math.floor((CALENDAR_WIDTH - lineWidth(ARROW_DOWN)) / 2), 7), nextStyle);
|
|
710
948
|
// Search line (if searching)
|
|
711
949
|
if (this.#yearSearch) {
|
|
712
950
|
const searchLabel = `Search: ${this.#yearSearch}`;
|
|
713
|
-
viewport.write(searchLabel, new Point(0, 7),
|
|
714
|
-
dim: true,
|
|
715
|
-
foreground: textStyle.foreground,
|
|
716
|
-
background: textStyle.background,
|
|
717
|
-
}));
|
|
951
|
+
viewport.write(searchLabel, new Point(0, 7), textStyle.merge({ dim: true }));
|
|
718
952
|
}
|
|
719
953
|
}
|
|
720
954
|
}
|