@vanilla-bean/components 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Component/Component.js +598 -0
- package/Component/Component.scenarios.js +88 -0
- package/Component/Component.test.js +717 -0
- package/Component/README.md +455 -0
- package/Component/index.js +3 -0
- package/Component/observeElementConnection.js +52 -0
- package/Component/observeElementConnection.test.js +121 -0
- package/Elem/Elem.js +304 -0
- package/Elem/Elem.test.js +679 -0
- package/Elem/README.md +373 -0
- package/Elem/index.js +1 -0
- package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
- package/LICENSE +21 -0
- package/README.md +413 -0
- package/components/BottomSheet/BottomSheet.js +192 -0
- package/components/BottomSheet/BottomSheet.lld.md +25 -0
- package/components/BottomSheet/README.md +66 -0
- package/components/BottomSheet/index.js +1 -0
- package/components/Button/Button.js +53 -0
- package/components/Button/Button.lld.md +21 -0
- package/components/Button/index.js +1 -0
- package/components/Calendar/Calendar.js +720 -0
- package/components/Calendar/Calendar.lld.md +22 -0
- package/components/Calendar/CalendarEvent.js +102 -0
- package/components/Calendar/Toolbar.js +78 -0
- package/components/Calendar/index.js +2 -0
- package/components/Calendar/utils.js +56 -0
- package/components/Code/Code.js +84 -0
- package/components/Code/Code.lld.md +21 -0
- package/components/Code/index.js +1 -0
- package/components/ColorPicker/ColorPicker.js +445 -0
- package/components/ColorPicker/ColorPicker.lld.md +21 -0
- package/components/ColorPicker/index.js +1 -0
- package/components/ColorPicker/svg.js +5 -0
- package/components/Dialog/Dialog.js +278 -0
- package/components/Dialog/Dialog.lld.md +20 -0
- package/components/Dialog/README.md +96 -0
- package/components/Dialog/index.js +1 -0
- package/components/Form/Form.js +257 -0
- package/components/Form/Form.lld.md +21 -0
- package/components/Form/README.md +87 -0
- package/components/Form/index.js +1 -0
- package/components/Icon/Icon.js +54 -0
- package/components/Icon/Icon.lld.md +21 -0
- package/components/Icon/index.js +1 -0
- package/components/Input/Input.js +173 -0
- package/components/Input/Input.lld.md +28 -0
- package/components/Input/README.md +97 -0
- package/components/Input/index.js +2 -0
- package/components/Input/utils.js +122 -0
- package/components/Keyboard/Key.js +38 -0
- package/components/Keyboard/Keyboard.js +173 -0
- package/components/Keyboard/Keyboard.lld.md +21 -0
- package/components/Keyboard/index.js +1 -0
- package/components/Label/Label.js +214 -0
- package/components/Label/Label.lld.md +20 -0
- package/components/Label/index.js +1 -0
- package/components/Link/Link.js +43 -0
- package/components/Link/Link.lld.md +15 -0
- package/components/Link/index.js +1 -0
- package/components/List/List.js +82 -0
- package/components/List/List.lld.md +19 -0
- package/components/List/index.js +1 -0
- package/components/Menu/Menu.js +93 -0
- package/components/Menu/Menu.lld.md +15 -0
- package/components/Menu/index.js +1 -0
- package/components/Notify/Notify.js +96 -0
- package/components/Notify/Notify.lld.md +20 -0
- package/components/Notify/index.js +1 -0
- package/components/Page/Page.js +67 -0
- package/components/Page/Page.lld.md +20 -0
- package/components/Page/index.js +1 -0
- package/components/Popover/Popover.js +175 -0
- package/components/Popover/Popover.lld.md +19 -0
- package/components/Popover/index.js +1 -0
- package/components/RadioButton/RadioButton.js +108 -0
- package/components/RadioButton/RadioButton.lld.md +15 -0
- package/components/RadioButton/index.js +1 -0
- package/components/Router/README.md +160 -0
- package/components/Router/Router.js +150 -0
- package/components/Router/Router.lld.md +31 -0
- package/components/Router/View.js +15 -0
- package/components/Router/index.js +2 -0
- package/components/Router/utils.js +17 -0
- package/components/Select/README.md +88 -0
- package/components/Select/Select.js +74 -0
- package/components/Select/Select.lld.md +20 -0
- package/components/Select/index.js +1 -0
- package/components/Table/README.md +94 -0
- package/components/Table/Table.js +171 -0
- package/components/Table/Table.lld.md +21 -0
- package/components/Table/index.js +1 -0
- package/components/TagList/Tag.js +84 -0
- package/components/TagList/TagList.js +118 -0
- package/components/TagList/TagList.lld.md +30 -0
- package/components/TagList/design.excalidraw.png +0 -0
- package/components/TagList/index.js +2 -0
- package/components/Tooltip/Tooltip.js +139 -0
- package/components/Tooltip/Tooltip.lld.md +22 -0
- package/components/Tooltip/index.js +1 -0
- package/components/TooltipWrapper/TooltipWrapper.js +89 -0
- package/components/TooltipWrapper/TooltipWrapper.lld.md +21 -0
- package/components/TooltipWrapper/index.js +1 -0
- package/components/Whiteboard/Whiteboard.js +198 -0
- package/components/Whiteboard/Whiteboard.lld.md +35 -0
- package/components/Whiteboard/index.js +1 -0
- package/components/index.js +27 -0
- package/eslint.config.cjs +118 -0
- package/index.d.ts +635 -0
- package/index.js +19 -0
- package/package.json +123 -0
- package/plugins/asText.js +38 -0
- package/plugins/loadPlugins.js +5 -0
- package/plugins/markdownLoader.js +121 -0
- package/prettier.config.cjs +7 -0
- package/spellcheck.config.cjs +227 -0
- package/styled/README.md +329 -0
- package/styled/appendStyles.js +26 -0
- package/styled/appendStyles.test.js +45 -0
- package/styled/index.js +4 -0
- package/styled/shimCSS.js +31 -0
- package/styled/shimCSS.test.js +103 -0
- package/styled/styled.js +91 -0
- package/styled/styled.test.js +586 -0
- package/styled/themeStyles.js +36 -0
- package/styled/themeStyles.test.js +135 -0
- package/test-setup.js +123 -0
- package/theme/.test.js +69 -0
- package/theme/README.md +607 -0
- package/theme/button.js +100 -0
- package/theme/code.js +123 -0
- package/theme/colors.js +42 -0
- package/theme/fonts.js +42 -0
- package/theme/index.js +33 -0
- package/theme/input.js +64 -0
- package/theme/page.js +208 -0
- package/theme/scrollbar.js +24 -0
- package/theme/table.js +53 -0
- package/utils/README.md +176 -0
- package/utils/browser.js +92 -0
- package/utils/class.js +30 -0
- package/utils/color.js +81 -0
- package/utils/data.js +164 -0
- package/utils/element.js +55 -0
- package/utils/index.js +7 -0
- package/utils/rand.js +12 -0
- package/utils/string.js +72 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
/* eslint-disable spellcheck/spell-checker */
|
|
2
|
+
import { styled } from '../../styled';
|
|
3
|
+
import { Component } from '../../Component';
|
|
4
|
+
|
|
5
|
+
import CalendarEvent from './CalendarEvent';
|
|
6
|
+
import Toolbar from './Toolbar';
|
|
7
|
+
import { getDaysInMonth, toNth } from './utils';
|
|
8
|
+
|
|
9
|
+
const StyledComponent = styled(
|
|
10
|
+
Component,
|
|
11
|
+
({ colors }) => `
|
|
12
|
+
user-select: none;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: 6px;
|
|
16
|
+
|
|
17
|
+
&.month {
|
|
18
|
+
& tr.title {
|
|
19
|
+
font-size: 1.3em;
|
|
20
|
+
height: 1.3em;
|
|
21
|
+
color: ${colors.black};
|
|
22
|
+
background-color: ${colors.lightest(colors.gray)};
|
|
23
|
+
|
|
24
|
+
& td {
|
|
25
|
+
text-align: center;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&.day {
|
|
31
|
+
& div.event-container {
|
|
32
|
+
position: absolute;
|
|
33
|
+
height: 100%;
|
|
34
|
+
width: 90%;
|
|
35
|
+
top: 0;
|
|
36
|
+
right: 0;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
|
|
39
|
+
& div.event {
|
|
40
|
+
position: absolute;
|
|
41
|
+
pointer-events: all;
|
|
42
|
+
width: 100%;
|
|
43
|
+
text-indent: 6px;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
& div.time-block {
|
|
48
|
+
height: 3em;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
color: black;
|
|
51
|
+
background-color: ${colors.light(colors.gray)};
|
|
52
|
+
|
|
53
|
+
&:nth-child(odd) {
|
|
54
|
+
background-color: ${colors.lighter(colors.gray)};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&:last-of-type{
|
|
58
|
+
border-bottom: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
& span.time {
|
|
62
|
+
font-size: 12px;
|
|
63
|
+
margin: 2px;
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const CalendarWrapper = styled(
|
|
72
|
+
Component,
|
|
73
|
+
() => `
|
|
74
|
+
position: relative;
|
|
75
|
+
overflow: auto;
|
|
76
|
+
width: 100%;
|
|
77
|
+
flex: 1;
|
|
78
|
+
`,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const MonthCalendar = styled(
|
|
82
|
+
Component,
|
|
83
|
+
() => `
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 100%;
|
|
86
|
+
|
|
87
|
+
td {
|
|
88
|
+
max-width: 0;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
text-overflow: ellipsis;
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
}
|
|
93
|
+
`,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const MonthDayCell = styled(
|
|
97
|
+
Component,
|
|
98
|
+
({ colors }) => `
|
|
99
|
+
position: relative;
|
|
100
|
+
border-collapse: collapse;
|
|
101
|
+
vertical-align: top;
|
|
102
|
+
background-color: ${colors.lighter(colors.gray)};
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
padding: 3px;
|
|
105
|
+
|
|
106
|
+
& span.day-title {
|
|
107
|
+
display: block;
|
|
108
|
+
font-size: 1em;
|
|
109
|
+
padding: 2px;
|
|
110
|
+
background-color: ${colors.black.setAlpha(0.2)};
|
|
111
|
+
color: ${colors.black};
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&:hover {
|
|
116
|
+
background-color: ${colors.blue};
|
|
117
|
+
|
|
118
|
+
& span.day-title {
|
|
119
|
+
background-color: ${colors.white.setAlpha(0.3)};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&.today {
|
|
124
|
+
background-color: ${colors.purple};
|
|
125
|
+
|
|
126
|
+
& span.day-title {
|
|
127
|
+
background-color: ${colors.white.setAlpha(0.3)};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&.not-in-month {
|
|
132
|
+
background: #222;
|
|
133
|
+
|
|
134
|
+
& span.day-title {
|
|
135
|
+
color: ${colors.gray};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const WeekDayCell = styled(
|
|
142
|
+
Component,
|
|
143
|
+
({ colors }) => `
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
background-color: ${colors.lighter(colors.gray)};
|
|
146
|
+
min-height: 14.285714285714286%;
|
|
147
|
+
|
|
148
|
+
&.today {
|
|
149
|
+
background-color: ${colors.blue};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
& div.day-title {
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
padding: 6px;
|
|
155
|
+
background-color: ${colors.black.setAlpha(0.2)};
|
|
156
|
+
font-size: 1em;
|
|
157
|
+
color: black;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
& .event {
|
|
161
|
+
text-indent: 6px;
|
|
162
|
+
}
|
|
163
|
+
`,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const DayNowIndicator = styled(
|
|
167
|
+
Component,
|
|
168
|
+
({ colors }) => `
|
|
169
|
+
position: absolute;
|
|
170
|
+
width: 105%;
|
|
171
|
+
height: 2px;
|
|
172
|
+
background-color: ${colors.red};
|
|
173
|
+
z-index: 34;
|
|
174
|
+
left: -5em;
|
|
175
|
+
`,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
export const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
179
|
+
export const MONTHS = [
|
|
180
|
+
'January',
|
|
181
|
+
'February',
|
|
182
|
+
'March',
|
|
183
|
+
'April',
|
|
184
|
+
'May',
|
|
185
|
+
'June',
|
|
186
|
+
'July',
|
|
187
|
+
'August',
|
|
188
|
+
'September',
|
|
189
|
+
'October',
|
|
190
|
+
'November',
|
|
191
|
+
'December',
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Interactive calendar component supporting month, week, and day views with event management.
|
|
196
|
+
*
|
|
197
|
+
* Provides full calendar functionality with navigation, event display, and user interaction.
|
|
198
|
+
* Supports multiple view modes, event rendering, and date selection with customizable styling.
|
|
199
|
+
* @param {object} [options={}] - Calendar configuration options
|
|
200
|
+
* @param {('month'|'week'|'day')} [options.view='month'] - Initial calendar view mode
|
|
201
|
+
* @param {string} [options.height='420px'] - Calendar component height
|
|
202
|
+
* @param {number} [options.year] - Initial year to display, defaults to current year
|
|
203
|
+
* @param {number} [options.month] - Initial month to display (0-11), defaults to current month
|
|
204
|
+
* @param {number} [options.day] - Initial day to display, defaults to current day
|
|
205
|
+
* @param {Array<object>} [options.events=[]] - Calendar events array, automatically converted to CalendarEvent instances
|
|
206
|
+
* @param {Array<string>} [options.views] - Available view modes for toolbar
|
|
207
|
+
* @param {boolean} [options.display24h] - Whether to display time in 24-hour format in day view
|
|
208
|
+
* @param {...(Component|HTMLElement|string)} children - Child elements to append
|
|
209
|
+
* @returns {Calendar} Calendar component instance
|
|
210
|
+
*/
|
|
211
|
+
class Calendar extends StyledComponent {
|
|
212
|
+
constructor(options = {}, ...children) {
|
|
213
|
+
super(
|
|
214
|
+
{
|
|
215
|
+
view: 'month',
|
|
216
|
+
height: '420px',
|
|
217
|
+
registeredEvents: new Set(['selectDay', 'selectTime', 'selectEvent', 'newEvent']),
|
|
218
|
+
...options,
|
|
219
|
+
events: (options.events || []).map(eventItem => new CalendarEvent(eventItem)),
|
|
220
|
+
},
|
|
221
|
+
...children,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static handlers = {
|
|
226
|
+
height(value) {
|
|
227
|
+
this.elem.style.height = value;
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
build() {
|
|
232
|
+
this.toolbar = new Toolbar({ appendTo: this, calendar: this, views: this.options.views, view: this.options.view });
|
|
233
|
+
this.wrapper = new CalendarWrapper({ appendTo: this });
|
|
234
|
+
|
|
235
|
+
if (this.options.month == null) {
|
|
236
|
+
const now = new Date();
|
|
237
|
+
|
|
238
|
+
this.setDate(now.getFullYear(), now.getMonth(), now.getDate());
|
|
239
|
+
|
|
240
|
+
this.adjustDateToView();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (this.options.view === 'day') this.renderDay();
|
|
244
|
+
else if (this.options.view === 'week') this.renderWeek();
|
|
245
|
+
else this.renderMonth();
|
|
246
|
+
|
|
247
|
+
this.wrapper.elem.scrollTop = 0;
|
|
248
|
+
|
|
249
|
+
if (this.elem.contains(document.activeElement)) document.activeElement.blur();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
renderDay() {
|
|
253
|
+
this.removeClass('month', 'week');
|
|
254
|
+
this.addClass('day');
|
|
255
|
+
|
|
256
|
+
this.toolbar.title.elem.textContent = `${MONTHS[this.options.month]} ${this.options.day}${toNth(
|
|
257
|
+
this.options.day,
|
|
258
|
+
)}, ${this.options.year}`;
|
|
259
|
+
|
|
260
|
+
const eventContainer = new Component({ addClass: 'event-container', appendTo: this.wrapper });
|
|
261
|
+
const events = this.eventsAt(new Date(this.options.year, this.options.month, this.options.day));
|
|
262
|
+
const minGap = 30;
|
|
263
|
+
const gapsPerHour = Math.ceil(60 / minGap);
|
|
264
|
+
|
|
265
|
+
for (let x = 0; x < 24; ++x) {
|
|
266
|
+
for (let y = 0; y < gapsPerHour; ++y) {
|
|
267
|
+
const minutesBlock = new Component({
|
|
268
|
+
addClass: 'time-block',
|
|
269
|
+
appendTo: this.wrapper,
|
|
270
|
+
onPointerPress: event => {
|
|
271
|
+
this.emit('selectTime', event);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
const timeSpan = new Component({ tag: 'span', addClass: 'time', appendTo: minutesBlock });
|
|
275
|
+
|
|
276
|
+
const mins = y * minGap;
|
|
277
|
+
let h = x;
|
|
278
|
+
let am_pm = '';
|
|
279
|
+
|
|
280
|
+
// minutesBlock.style.height = (this.options.dayViewGapHeight - 1) +'px';
|
|
281
|
+
|
|
282
|
+
if (!this.options.display24h) {
|
|
283
|
+
am_pm = h < 12 ? 'AM' : 'PM';
|
|
284
|
+
h = h < 12 ? h : h % 12;
|
|
285
|
+
h = h === 0 ? 12 : h;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
timeSpan.elem.textContent = `${h}:${mins < 10 ? '0' : ''}${mins} ${am_pm}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const groups = {};
|
|
293
|
+
let smallestGap = -1;
|
|
294
|
+
|
|
295
|
+
events.forEach(event => {
|
|
296
|
+
const elem = event.render(this.options.view, eventContainer.elem, this);
|
|
297
|
+
event.elem = elem;
|
|
298
|
+
event.ratio = 100;
|
|
299
|
+
elem.style.left = '0';
|
|
300
|
+
|
|
301
|
+
let totalGaps = event.gapCount;
|
|
302
|
+
|
|
303
|
+
if (Math.floor(event.gapCell) !== event.gapCell) ++totalGaps;
|
|
304
|
+
|
|
305
|
+
for (let x = 0; x < totalGaps; ++x) {
|
|
306
|
+
const gap = Math.floor(event.gapCell + x);
|
|
307
|
+
|
|
308
|
+
groups[gap] = groups[gap] || [];
|
|
309
|
+
|
|
310
|
+
groups[gap].push(event);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (smallestGap === -1 || smallestGap > event.gapCell) smallestGap = event.gapCell;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
Object.keys(groups).forEach(gap => {
|
|
317
|
+
const total = groups[gap].length;
|
|
318
|
+
const ratio = 100 / total;
|
|
319
|
+
|
|
320
|
+
groups[gap].forEach((event, index) => {
|
|
321
|
+
const minLeft = index * ratio;
|
|
322
|
+
const minRight = ratio * (total - index - 1);
|
|
323
|
+
|
|
324
|
+
if (ratio < event.ratio) {
|
|
325
|
+
event.ratio = ratio;
|
|
326
|
+
event.elem.style.left = minLeft + '%';
|
|
327
|
+
event.elem.style.right = minRight + '%';
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
new DayNowIndicator({
|
|
333
|
+
style: { top: `${(new Date().getHours() * 2 + new Date().getMinutes() / 30) * 3}em` },
|
|
334
|
+
appendTo: eventContainer,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
renderWeek() {
|
|
339
|
+
this.removeClass('month', 'day');
|
|
340
|
+
this.addClass('week');
|
|
341
|
+
|
|
342
|
+
const dayMs = 1000 * 60 * 60 * 24;
|
|
343
|
+
const now = new Date();
|
|
344
|
+
const day = new Date(this.options.year, this.options.month, this.options.day);
|
|
345
|
+
const firstDay = new Date(day.getTime() - day.getDay() * dayMs);
|
|
346
|
+
const lastDay = new Date(firstDay.getTime() + 6 * dayMs);
|
|
347
|
+
|
|
348
|
+
let cDay = new Date(firstDay.getTime());
|
|
349
|
+
|
|
350
|
+
for (let x = 0; x < 7; ++x) {
|
|
351
|
+
const y = cDay.getFullYear();
|
|
352
|
+
const m = cDay.getMonth();
|
|
353
|
+
const d = cDay.getDate();
|
|
354
|
+
const w = cDay.getDay();
|
|
355
|
+
const t = cDay.getTime();
|
|
356
|
+
|
|
357
|
+
const cellDate = new Date(y, m, d);
|
|
358
|
+
const events = this.eventsAt(cellDate);
|
|
359
|
+
const isToday = y === now.getFullYear() && m === now.getMonth() && d === now.getDate();
|
|
360
|
+
|
|
361
|
+
const weekdayCell = new WeekDayCell({
|
|
362
|
+
addClass: isToday ? 'today' : undefined,
|
|
363
|
+
data: { at: cDay.getTime() },
|
|
364
|
+
onPointerPress: ({ target }) => {
|
|
365
|
+
this.emit('selectDay', { target, date: cellDate });
|
|
366
|
+
},
|
|
367
|
+
appendTo: this.wrapper,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
new Component({
|
|
371
|
+
textContent: `${DAYS[w]}, ${MONTHS[m]} ${d}${toNth(d)}`,
|
|
372
|
+
addClass: 'day-title',
|
|
373
|
+
appendTo: weekdayCell,
|
|
374
|
+
onPointerPress: ({ target }) => {
|
|
375
|
+
this.emit('selectDay', { target, isTitle: true, date: cellDate });
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
for (let y = 0; y < events.length; ++y) events[y].render(this.options.view, weekdayCell.elem, this);
|
|
380
|
+
|
|
381
|
+
cDay = new Date(t + dayMs);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const firstDayAppend = toNth(firstDay.getDate());
|
|
385
|
+
const lastDayAppend = toNth(lastDay.getDate());
|
|
386
|
+
|
|
387
|
+
if (firstDay.getMonth() === lastDay.getMonth()) {
|
|
388
|
+
this.toolbar.title.elem.textContent =
|
|
389
|
+
MONTHS[firstDay.getMonth()] +
|
|
390
|
+
' ' +
|
|
391
|
+
firstDay.getDate() +
|
|
392
|
+
firstDayAppend +
|
|
393
|
+
' - ' +
|
|
394
|
+
lastDay.getDate() +
|
|
395
|
+
lastDayAppend +
|
|
396
|
+
', ' +
|
|
397
|
+
this.options.year;
|
|
398
|
+
} else if (firstDay.getFullYear() === lastDay.getFullYear()) {
|
|
399
|
+
this.toolbar.title.elem.textContent =
|
|
400
|
+
MONTHS[firstDay.getMonth()] +
|
|
401
|
+
' ' +
|
|
402
|
+
firstDay.getDate() +
|
|
403
|
+
firstDayAppend +
|
|
404
|
+
' - ' +
|
|
405
|
+
MONTHS[lastDay.getMonth()] +
|
|
406
|
+
' ' +
|
|
407
|
+
lastDay.getDate() +
|
|
408
|
+
lastDayAppend +
|
|
409
|
+
', ' +
|
|
410
|
+
this.options.year;
|
|
411
|
+
} else {
|
|
412
|
+
this.toolbar.title.elem.textContent =
|
|
413
|
+
MONTHS[firstDay.getMonth()] +
|
|
414
|
+
' ' +
|
|
415
|
+
firstDay.getDate() +
|
|
416
|
+
firstDayAppend +
|
|
417
|
+
', ' +
|
|
418
|
+
firstDay.getFullYear() +
|
|
419
|
+
' - ' +
|
|
420
|
+
MONTHS[lastDay.getMonth()] +
|
|
421
|
+
' ' +
|
|
422
|
+
lastDay.getDate() +
|
|
423
|
+
lastDayAppend +
|
|
424
|
+
', ' +
|
|
425
|
+
lastDay.getFullYear();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
renderMonth() {
|
|
430
|
+
this.removeClass('week', 'day');
|
|
431
|
+
this.addClass('month');
|
|
432
|
+
|
|
433
|
+
const month = getDaysInMonth(this.options.year, this.options.month);
|
|
434
|
+
const now = new Date();
|
|
435
|
+
const isCurrentMonth = now.getMonth() === this.options.month && now.getFullYear() === this.options.year;
|
|
436
|
+
const currentDay = now.getDate();
|
|
437
|
+
const table = new MonthCalendar({ tag: 'table', appendTo: this.wrapper });
|
|
438
|
+
const titleRow = new Component({ tag: 'tr', addClass: 'title', appendTo: table });
|
|
439
|
+
let dayX = -month.firstDay;
|
|
440
|
+
|
|
441
|
+
for (let day = 0; day < 7; ++day) new Component({ tag: 'td', textContent: DAYS[day], appendTo: titleRow });
|
|
442
|
+
|
|
443
|
+
for (let row = 0; row < 6; ++row) {
|
|
444
|
+
const tr = new Component({ tag: 'tr', appendTo: table });
|
|
445
|
+
|
|
446
|
+
for (let col = 0; col < 7; ++col) {
|
|
447
|
+
const date = new Date(this.options.year, this.options.month, ++dayX);
|
|
448
|
+
const eventContainer = new Component({ addClass: 'event-container' });
|
|
449
|
+
const td = new MonthDayCell({
|
|
450
|
+
tag: 'td',
|
|
451
|
+
addClass: isCurrentMonth && currentDay === dayX ? 'today' : undefined,
|
|
452
|
+
data: { at: date.getTime() },
|
|
453
|
+
appendTo: tr,
|
|
454
|
+
onPointerPress: event => {
|
|
455
|
+
this.emit('selectDay', { target: event.target, date });
|
|
456
|
+
},
|
|
457
|
+
append: [
|
|
458
|
+
new Component({
|
|
459
|
+
tag: 'span',
|
|
460
|
+
textContent: date.getDate(),
|
|
461
|
+
addClass: 'day-title',
|
|
462
|
+
onPointerPress: event => {
|
|
463
|
+
this.emit('selectDay', { target: event.target, isTitle: true, date });
|
|
464
|
+
},
|
|
465
|
+
}),
|
|
466
|
+
eventContainer,
|
|
467
|
+
],
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
if (dayX <= 0 || dayX > month.numberOfDays) td.addClass('not-in-month');
|
|
471
|
+
|
|
472
|
+
this.eventsAt(date).forEach(calendarEvent => {
|
|
473
|
+
if (calendarEvent.render) calendarEvent.render(this.options.view, eventContainer.elem, this);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
this.toolbar.title.elem.textContent = MONTHS[this.options.month] + ' ' + this.options.year;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
adjustDateToView() {
|
|
482
|
+
if (this.options.view !== 'week' || this.options.weekday >= this.options.day || this.options.day > 8) return;
|
|
483
|
+
|
|
484
|
+
let { year, month } = this.options;
|
|
485
|
+
--month;
|
|
486
|
+
if (month < 0) {
|
|
487
|
+
--year;
|
|
488
|
+
month = 11;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
this.setDate(year, month, getDaysInMonth(year, month).numberOfDays);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Sets the calendar to display a specific date.
|
|
496
|
+
* @param {number} year - Year to set
|
|
497
|
+
* @param {number} month - Month to set (0-11)
|
|
498
|
+
* @param {number} day - Day to set
|
|
499
|
+
* @param {boolean} [reRender] - Whether to re-render the calendar after setting date
|
|
500
|
+
*/
|
|
501
|
+
setDate(year, month, day, reRender) {
|
|
502
|
+
this.options.year = Number.parseInt(year);
|
|
503
|
+
this.options.month = Number.parseInt(month);
|
|
504
|
+
this.options.day = Number.parseInt(day);
|
|
505
|
+
this.options.weekday = new Date(year, month, day).getDay();
|
|
506
|
+
|
|
507
|
+
if (reRender) this.render();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Navigates the calendar to today's date and re-renders.
|
|
512
|
+
*/
|
|
513
|
+
today() {
|
|
514
|
+
const now = new Date();
|
|
515
|
+
|
|
516
|
+
this.setDate(now.getFullYear(), now.getMonth(), now.getDate());
|
|
517
|
+
|
|
518
|
+
this.adjustDateToView();
|
|
519
|
+
|
|
520
|
+
this.render();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Navigates to the previous time period based on current view (day/week/month).
|
|
525
|
+
*/
|
|
526
|
+
previous() {
|
|
527
|
+
let { year, month, day } = this.options;
|
|
528
|
+
|
|
529
|
+
if (this.options.view === 'day') {
|
|
530
|
+
--day;
|
|
531
|
+
|
|
532
|
+
if (day <= 0) {
|
|
533
|
+
--month;
|
|
534
|
+
if (month < 0) {
|
|
535
|
+
month = 11;
|
|
536
|
+
--year;
|
|
537
|
+
}
|
|
538
|
+
day = getDaysInMonth(year, month).numberOfDays;
|
|
539
|
+
}
|
|
540
|
+
} else if (this.options.view === 'month') {
|
|
541
|
+
--month;
|
|
542
|
+
if (month < 0) {
|
|
543
|
+
month = 11;
|
|
544
|
+
--year;
|
|
545
|
+
}
|
|
546
|
+
} else if (this.options.view === 'week') {
|
|
547
|
+
day -= 7;
|
|
548
|
+
|
|
549
|
+
if (day <= 0) {
|
|
550
|
+
--month;
|
|
551
|
+
if (month < 0) {
|
|
552
|
+
month = 11;
|
|
553
|
+
--year;
|
|
554
|
+
}
|
|
555
|
+
day = getDaysInMonth(year, month).numberOfDays + day;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
this.setDate(year, month, day);
|
|
560
|
+
this.render();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Navigates to the next time period based on current view (day/week/month).
|
|
565
|
+
*/
|
|
566
|
+
next() {
|
|
567
|
+
let { year, month, day } = this.options;
|
|
568
|
+
|
|
569
|
+
if (this.options.view === 'day') {
|
|
570
|
+
++day;
|
|
571
|
+
const monthStat = getDaysInMonth(year, month);
|
|
572
|
+
|
|
573
|
+
if (day > monthStat.numberOfDays) {
|
|
574
|
+
day = 1;
|
|
575
|
+
++month;
|
|
576
|
+
if (month > 11) {
|
|
577
|
+
month = 0;
|
|
578
|
+
++year;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} else if (this.options.view === 'month') {
|
|
582
|
+
++month;
|
|
583
|
+
if (month > 11) {
|
|
584
|
+
month = 0;
|
|
585
|
+
++year;
|
|
586
|
+
}
|
|
587
|
+
} else if (this.options.view === 'week') {
|
|
588
|
+
day += 7;
|
|
589
|
+
const monthStat = getDaysInMonth(year, month);
|
|
590
|
+
|
|
591
|
+
if (day > monthStat.numberOfDays) {
|
|
592
|
+
day -= monthStat.numberOfDays;
|
|
593
|
+
++month;
|
|
594
|
+
if (month > 11) {
|
|
595
|
+
month = 0;
|
|
596
|
+
++year;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
this.setDate(year, month, day);
|
|
602
|
+
this.render();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Navigates to a specific day and switches to day view.
|
|
607
|
+
* @param {number|Date} position - Date position as timestamp or Date object
|
|
608
|
+
*/
|
|
609
|
+
goToDay(position) {
|
|
610
|
+
const date = new Date(position);
|
|
611
|
+
|
|
612
|
+
this.setDate(date.getFullYear(), date.getMonth(), date.getDate());
|
|
613
|
+
|
|
614
|
+
this.setView('day');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Changes the calendar view mode and updates the display.
|
|
619
|
+
* @param {string} view - View mode to set ('month', 'week', 'day')
|
|
620
|
+
* @returns {Calendar} This calendar instance for method chaining
|
|
621
|
+
*/
|
|
622
|
+
setView(view) {
|
|
623
|
+
if (this.options.view !== view) {
|
|
624
|
+
this.options.view = view;
|
|
625
|
+
|
|
626
|
+
this.adjustDateToView();
|
|
627
|
+
this.wrapper.empty();
|
|
628
|
+
|
|
629
|
+
if (view === 'day') this.renderDay();
|
|
630
|
+
else if (view === 'week') this.renderWeek();
|
|
631
|
+
else this.renderMonth();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return this;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Retrieves all events occurring on a specific date.
|
|
639
|
+
* @param {string|Date} date - Date to query for events
|
|
640
|
+
* @returns {Array<CalendarEvent>} Array of events occurring on the specified date
|
|
641
|
+
*/
|
|
642
|
+
eventsAt(date) {
|
|
643
|
+
if (!(date instanceof Date)) date = new Date(date);
|
|
644
|
+
|
|
645
|
+
const events = [];
|
|
646
|
+
|
|
647
|
+
this.options.events.forEach(event => {
|
|
648
|
+
if (!event.recurring) {
|
|
649
|
+
if (event.year === date.getFullYear() && event.month === date.getMonth() + 1 && event.day === date.getDate())
|
|
650
|
+
events.push(event);
|
|
651
|
+
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (!event.daily) {
|
|
656
|
+
event.whitelist.forEach(dateItem => {
|
|
657
|
+
dateItem = new Date(dateItem);
|
|
658
|
+
|
|
659
|
+
if (
|
|
660
|
+
dateItem.getFullYear() === date.getFullYear() &&
|
|
661
|
+
dateItem.getMonth() === date.getMonth() &&
|
|
662
|
+
dateItem.getDate() === date.getDate()
|
|
663
|
+
)
|
|
664
|
+
events.push(event);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
for (let x = 0, count = event.blacklist.length; x < count; ++x) {
|
|
671
|
+
const dateItem = new Date(event.blacklist[x]);
|
|
672
|
+
|
|
673
|
+
// if(parseInt(dateItem[2]) === date.getFullYear() && parseInt(dateItem[1]) === date.getMonth() + 1 && parseInt(dateItem[0]) === date.getDate() + 1) return;
|
|
674
|
+
if (
|
|
675
|
+
dateItem.getFullYear() === date.getFullYear() &&
|
|
676
|
+
dateItem.getMonth() === date.getMonth() &&
|
|
677
|
+
dateItem.getDate() === date.getDate()
|
|
678
|
+
)
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (
|
|
683
|
+
event.weekdays[DAYS[date.getDay()].toLowerCase()] &&
|
|
684
|
+
(date.getFullYear() > event.year ||
|
|
685
|
+
(date.getFullYear() === event.year && date.getMonth() + 1 > event.month) ||
|
|
686
|
+
(date.getFullYear() === event.year && date.getMonth() + 1 === event.month && date.getDate() >= event.day))
|
|
687
|
+
)
|
|
688
|
+
events.push(event);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
return events;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Adds a new event to the calendar and re-renders.
|
|
696
|
+
* @param {object} eventItem - Event data object to add
|
|
697
|
+
* @returns {Calendar} This calendar instance for method chaining
|
|
698
|
+
*/
|
|
699
|
+
addEvent(eventItem) {
|
|
700
|
+
if (eventItem) {
|
|
701
|
+
this.options.events.push(new CalendarEvent(eventItem, this));
|
|
702
|
+
|
|
703
|
+
this.emit('newEvent', eventItem);
|
|
704
|
+
|
|
705
|
+
this.render();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export default Calendar;
|
|
713
|
+
|
|
714
|
+
// Zero-arg scenarios for LLD verification
|
|
715
|
+
export const navigateBackFromJanuary = () => {
|
|
716
|
+
const c = new Calendar({ view: 'month', year: 2024, month: 0, autoRender: false });
|
|
717
|
+
c.render();
|
|
718
|
+
c.previous();
|
|
719
|
+
return { month: c.options.month, year: c.options.year };
|
|
720
|
+
};
|