@gobrand/calendar-core 0.0.18 → 0.0.20
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/README.md +17 -418
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,441 +3,40 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@gobrand/calendar-core)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Framework-agnostic calendar utilities built with the
|
|
6
|
+
**Framework-agnostic calendar utilities built with the Temporal API.** Simple, composable functions for building month, week, and day views.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
👉 **[Documentation](https://eng.gobrand.app/calendar)**
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
pnpm add @gobrand/calendar-core
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Why @gobrand/calendar-core?
|
|
19
|
-
|
|
20
|
-
Building calendars is complex: timezone handling, DST transitions, date arithmetic, and data mapping. **@gobrand/calendar-core** provides pure, framework-agnostic functions for calendar logic:
|
|
21
|
-
|
|
22
|
-
- **🌍 Timezone-aware** - Native timezone support with Temporal API primitives
|
|
23
|
-
- **🎯 Data-agnostic** - Works with any data type through accessor pattern
|
|
24
|
-
- **⚡️ Type-safe** - Full TypeScript support with proper Temporal types
|
|
25
|
-
- **📦 Minimal** - Simple, composable functions with no unnecessary abstractions
|
|
26
|
-
- **🔧 Zero config** - Sensible defaults, customize only what you need
|
|
27
|
-
- **🪄 Framework-agnostic** - Use with React, Vue, Angular, Svelte, or vanilla JavaScript
|
|
28
|
-
|
|
29
|
-
**Key features:**
|
|
30
|
-
- ✅ Built exclusively on Temporal API (no Date objects, no moment.js, no date-fns)
|
|
31
|
-
- ✅ Automatic DST handling and timezone conversions
|
|
32
|
-
- ✅ Calendar-aware arithmetic (leap years, month-end dates)
|
|
33
|
-
- ✅ Flexible accessor pattern for any data structure
|
|
34
|
-
- ✅ Polyfill included for browser compatibility
|
|
35
|
-
|
|
36
|
-
**Perfect for:**
|
|
37
|
-
- Building custom calendar UIs in any framework
|
|
38
|
-
- Server-side calendar generation
|
|
39
|
-
- Event calendars and schedulers
|
|
40
|
-
- Booking systems and appointment managers
|
|
41
|
-
- Task management with due dates
|
|
42
|
-
- Analytics dashboards with date ranges
|
|
43
|
-
|
|
44
|
-
## Quick Start
|
|
10
|
+
- **Built on Temporal API** - No Date objects, no moment.js, no date-fns
|
|
11
|
+
- **Timezone-aware** - Native DST handling with IANA timezones
|
|
12
|
+
- **Type-safe** - Full TypeScript with proper Temporal types
|
|
13
|
+
- **Framework-agnostic** - Use with React, Vue, Svelte, or vanilla JS
|
|
14
|
+
- **Data-agnostic** - Works with any data type through accessor pattern
|
|
45
15
|
|
|
46
16
|
```typescript
|
|
47
|
-
import { buildMonth, createCalendarAccessor, getWeekdays
|
|
48
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
49
|
-
|
|
50
|
-
type Event = {
|
|
51
|
-
id: string;
|
|
52
|
-
date: Temporal.PlainDate;
|
|
53
|
-
title: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const events: Event[] = [
|
|
57
|
-
{ id: '1', date: Temporal.PlainDate.from('2025-01-20'), title: 'Team Meeting' },
|
|
58
|
-
{ id: '2', date: Temporal.PlainDate.from('2025-01-22'), title: 'Code Review' },
|
|
59
|
-
];
|
|
17
|
+
import { buildMonth, createCalendarAccessor, getWeekdays } from '@gobrand/calendar-core';
|
|
60
18
|
|
|
61
19
|
const accessor = createCalendarAccessor<Event>({
|
|
62
|
-
getDate: (
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const month = buildMonth(2025, 1, {
|
|
66
|
-
weekStartsOn: 1,
|
|
67
|
-
data: events,
|
|
68
|
-
accessor,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
console.log(getMonthName(month.month)); // "January"
|
|
72
|
-
console.log(getWeekdays(1)); // ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
|
73
|
-
|
|
74
|
-
month.weeks.forEach(week => {
|
|
75
|
-
week.forEach(day => {
|
|
76
|
-
console.log(
|
|
77
|
-
day.date.toString(),
|
|
78
|
-
day.isCurrentMonth,
|
|
79
|
-
day.isToday,
|
|
80
|
-
`${day.items.length} events`
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## API
|
|
87
|
-
|
|
88
|
-
### Accessor Pattern
|
|
89
|
-
|
|
90
|
-
#### `createCalendarAccessor(accessor)`
|
|
91
|
-
|
|
92
|
-
Create a type-safe accessor for mapping your data to calendar dates. This is a type-identity function for TypeScript inference.
|
|
93
|
-
|
|
94
|
-
**Parameters:**
|
|
95
|
-
- `accessor` (CalendarAccessor<TItem>): Accessor configuration
|
|
96
|
-
- `getDate` (required): Extract PlainDate from item
|
|
97
|
-
- `getStart` (optional): Extract ZonedDateTime start time
|
|
98
|
-
- `getEnd` (optional): Extract ZonedDateTime end time
|
|
99
|
-
|
|
100
|
-
**Returns:** Same accessor object with proper types
|
|
101
|
-
|
|
102
|
-
**Example:**
|
|
103
|
-
```tsx
|
|
104
|
-
// Simple date-based items (tasks, posts)
|
|
105
|
-
type Task = {
|
|
106
|
-
id: string;
|
|
107
|
-
name: string;
|
|
108
|
-
dueDate: Temporal.PlainDate;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const taskAccessor = createCalendarAccessor<Task>({
|
|
112
|
-
getDate: (task) => task.dueDate,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Time-based items with start time (events, appointments)
|
|
116
|
-
type Event = {
|
|
117
|
-
id: string;
|
|
118
|
-
title: string;
|
|
119
|
-
start: Temporal.ZonedDateTime;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const eventAccessor = createCalendarAccessor<Event>({
|
|
123
|
-
getDate: (event) => event.start.toPlainDate(),
|
|
124
|
-
getStart: (event) => event.start,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Items with start and end times (meetings, bookings)
|
|
128
|
-
type Meeting = {
|
|
129
|
-
id: string;
|
|
130
|
-
title: string;
|
|
131
|
-
start: Temporal.ZonedDateTime;
|
|
132
|
-
end: Temporal.ZonedDateTime;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const meetingAccessor = createCalendarAccessor<Meeting>({
|
|
136
|
-
getDate: (meeting) => meeting.start.toPlainDate(),
|
|
137
|
-
getStart: (meeting) => meeting.start,
|
|
138
|
-
getEnd: (meeting) => meeting.end,
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Building Calendars
|
|
143
|
-
|
|
144
|
-
Low-level functions for building calendar grids without state management.
|
|
145
|
-
|
|
146
|
-
#### `buildMonth(year, month, options?)`
|
|
147
|
-
|
|
148
|
-
Build a month grid for any year and month.
|
|
149
|
-
|
|
150
|
-
**Parameters:**
|
|
151
|
-
- `year` (number): Year (e.g., 2025)
|
|
152
|
-
- `month` (number): Month (1-12)
|
|
153
|
-
- `options` (object, optional):
|
|
154
|
-
- `weekStartsOn` (0-6): First day of week
|
|
155
|
-
- `today` (PlainDate): Override today's date
|
|
156
|
-
- `data` (TItem[]): Items to include
|
|
157
|
-
- `accessor` (CalendarAccessor<TItem>): Data accessor
|
|
158
|
-
|
|
159
|
-
**Returns:** `CalendarMonth<TItem>`
|
|
160
|
-
|
|
161
|
-
**Example:**
|
|
162
|
-
```typescript
|
|
163
|
-
import { buildMonth, createCalendarAccessor } from '@gobrand/calendar-core';
|
|
164
|
-
|
|
165
|
-
const month = buildMonth(2025, 1, {
|
|
166
|
-
weekStartsOn: 1,
|
|
167
|
-
data: events,
|
|
168
|
-
accessor: createCalendarAccessor({
|
|
169
|
-
getDate: (event) => event.date
|
|
170
|
-
})
|
|
171
|
-
});
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
#### `buildWeek(date, options?)`
|
|
175
|
-
|
|
176
|
-
Build a week view for a specific date.
|
|
177
|
-
|
|
178
|
-
**Parameters:**
|
|
179
|
-
- `date` (PlainDate): Any date in the target week
|
|
180
|
-
- `options` (object, optional):
|
|
181
|
-
- `weekStartsOn` (0-6): First day of week
|
|
182
|
-
- `startHour` (number): Start hour for time slots
|
|
183
|
-
- `endHour` (number): End hour for time slots
|
|
184
|
-
- `slotDuration` (number): Minutes per slot
|
|
185
|
-
- `today` (PlainDate): Override today's date
|
|
186
|
-
- `data` (TItem[]): Items to include
|
|
187
|
-
- `accessor` (CalendarAccessor<TItem>): Data accessor
|
|
188
|
-
|
|
189
|
-
**Returns:** `CalendarWeekView<TItem>`
|
|
190
|
-
|
|
191
|
-
#### `buildDay(date, options?)`
|
|
192
|
-
|
|
193
|
-
Build a day view with time slots.
|
|
194
|
-
|
|
195
|
-
**Parameters:**
|
|
196
|
-
- `date` (PlainDate): The target date
|
|
197
|
-
- `options` (object, optional):
|
|
198
|
-
- `startHour` (number): Start hour for time slots
|
|
199
|
-
- `endHour` (number): End hour for time slots
|
|
200
|
-
- `slotDuration` (number): Minutes per slot
|
|
201
|
-
- `today` (PlainDate): Override today's date
|
|
202
|
-
- `data` (TItem[]): Items to include
|
|
203
|
-
- `accessor` (CalendarAccessor<TItem>): Data accessor
|
|
204
|
-
|
|
205
|
-
**Returns:** `CalendarDayView<TItem>`
|
|
206
|
-
|
|
207
|
-
### Formatting Utilities
|
|
208
|
-
|
|
209
|
-
#### `getWeekdays(weekStartsOn?, locale?, format?)`
|
|
210
|
-
|
|
211
|
-
Get localized weekday names.
|
|
212
|
-
|
|
213
|
-
**Parameters:**
|
|
214
|
-
- `weekStartsOn` (0-6, optional): First day of week (default: 0 = Sunday)
|
|
215
|
-
- `locale` (string, optional): BCP 47 locale (default: system locale)
|
|
216
|
-
- `format` ('long' | 'short' | 'narrow', optional): Name format (default: 'short')
|
|
217
|
-
|
|
218
|
-
**Returns:** `string[]` - Array of 7 weekday names
|
|
219
|
-
|
|
220
|
-
**Example:**
|
|
221
|
-
```typescript
|
|
222
|
-
import { getWeekdays } from '@gobrand/calendar-core';
|
|
223
|
-
|
|
224
|
-
getWeekdays(1); // ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
|
225
|
-
getWeekdays(0); // ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|
226
|
-
getWeekdays(1, 'es-ES'); // ["lun", "mar", "mié", "jue", "vie", "sáb", "dom"]
|
|
227
|
-
getWeekdays(1, 'en-US', 'long'); // ["Monday", "Tuesday", ...]
|
|
228
|
-
getWeekdays(1, 'en-US', 'narrow'); // ["M", "T", "W", "T", "F", "S", "S"]
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
#### `getMonthName(month, locale?)`
|
|
232
|
-
|
|
233
|
-
Get localized month name.
|
|
234
|
-
|
|
235
|
-
**Parameters:**
|
|
236
|
-
- `month` (PlainYearMonth): The month to format
|
|
237
|
-
- `locale` (string, optional): BCP 47 locale
|
|
238
|
-
|
|
239
|
-
**Returns:** `string` - Formatted month name
|
|
240
|
-
|
|
241
|
-
**Example:**
|
|
242
|
-
```typescript
|
|
243
|
-
import { getMonthName } from '@gobrand/calendar-core';
|
|
244
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
245
|
-
|
|
246
|
-
const month = Temporal.PlainYearMonth.from('2025-01');
|
|
247
|
-
getMonthName(month); // "January"
|
|
248
|
-
getMonthName(month, 'es-ES'); // "enero"
|
|
249
|
-
getMonthName(month, 'ja-JP'); // "1月"
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
#### `formatTime(time, locale?)`
|
|
253
|
-
|
|
254
|
-
Format a PlainTime as a localized time string.
|
|
255
|
-
|
|
256
|
-
**Parameters:**
|
|
257
|
-
- `time` (PlainTime): Time to format
|
|
258
|
-
- `locale` (string, optional): BCP 47 locale
|
|
259
|
-
|
|
260
|
-
**Returns:** `string` - Formatted time
|
|
261
|
-
|
|
262
|
-
**Example:**
|
|
263
|
-
```typescript
|
|
264
|
-
import { formatTime } from '@gobrand/calendar-core';
|
|
265
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
266
|
-
|
|
267
|
-
const time = Temporal.PlainTime.from('14:30');
|
|
268
|
-
formatTime(time); // "2:30 PM" (en-US)
|
|
269
|
-
formatTime(time, 'en-GB'); // "14:30"
|
|
270
|
-
formatTime(time, 'es-ES'); // "14:30"
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Timezone Utilities
|
|
274
|
-
|
|
275
|
-
#### `getMonthRange(timeZone?, weekStartsOn?)`
|
|
276
|
-
|
|
277
|
-
Get the date range for the current month, week-aligned in a specific timezone.
|
|
278
|
-
|
|
279
|
-
**Parameters:**
|
|
280
|
-
- `timeZone` (string, optional): IANA timezone (default: system timezone)
|
|
281
|
-
- `weekStartsOn` (0-6, optional): First day of week (default: 1)
|
|
282
|
-
|
|
283
|
-
**Returns:** `{ start: Temporal.PlainDate; end: Temporal.PlainDate }` - Start/end dates for the week-aligned month
|
|
284
|
-
|
|
285
|
-
**Example:**
|
|
286
|
-
```typescript
|
|
287
|
-
import { getMonthRange } from '@gobrand/calendar-core';
|
|
288
|
-
|
|
289
|
-
const range = getMonthRange('America/New_York', 1);
|
|
290
|
-
// Returns week-aligned range for current month in New York time
|
|
291
|
-
console.log(range.start, range.end);
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
#### `getWeekRange(timeZone?, weekStartsOn?)`
|
|
295
|
-
|
|
296
|
-
Get the date range for the current week in a specific timezone.
|
|
297
|
-
|
|
298
|
-
**Parameters:**
|
|
299
|
-
- `timeZone` (string, optional): IANA timezone (default: system timezone)
|
|
300
|
-
- `weekStartsOn` (0-6, optional): First day of week (default: 1)
|
|
301
|
-
|
|
302
|
-
**Returns:** `{ start: Temporal.PlainDate; end: Temporal.PlainDate }` - Start/end dates for the week
|
|
303
|
-
|
|
304
|
-
#### `getDayRange(timeZone?)`
|
|
305
|
-
|
|
306
|
-
Get the date range for today in a specific timezone.
|
|
307
|
-
|
|
308
|
-
**Parameters:**
|
|
309
|
-
- `timeZone` (string, optional): IANA timezone (default: system timezone)
|
|
310
|
-
|
|
311
|
-
**Returns:** `{ start: Temporal.PlainDate; end: Temporal.PlainDate }` - Start/end dates for today (same date)
|
|
312
|
-
|
|
313
|
-
#### `getCurrentTimeZone()`
|
|
314
|
-
|
|
315
|
-
Get the system's current IANA timezone identifier.
|
|
316
|
-
|
|
317
|
-
**Returns:** `string` - IANA timezone (e.g., "America/New_York")
|
|
318
|
-
|
|
319
|
-
#### `convertToTimezone(zdt, timeZone)`
|
|
320
|
-
|
|
321
|
-
Convert a ZonedDateTime to a different timezone.
|
|
322
|
-
|
|
323
|
-
**Parameters:**
|
|
324
|
-
- `zdt` (ZonedDateTime): Source ZonedDateTime
|
|
325
|
-
- `timeZone` (string): Target IANA timezone
|
|
326
|
-
|
|
327
|
-
**Returns:** `ZonedDateTime` - Same instant in new timezone
|
|
328
|
-
|
|
329
|
-
#### `createZonedDateTime(date, time, timeZone)`
|
|
330
|
-
|
|
331
|
-
Create a ZonedDateTime from a PlainDate and PlainTime.
|
|
332
|
-
|
|
333
|
-
**Parameters:**
|
|
334
|
-
- `date` (PlainDate): The date
|
|
335
|
-
- `time` (PlainTime): The time
|
|
336
|
-
- `timeZone` (string): IANA timezone
|
|
337
|
-
|
|
338
|
-
**Returns:** `ZonedDateTime`
|
|
339
|
-
|
|
340
|
-
## Real World Examples
|
|
341
|
-
|
|
342
|
-
### Example 1: HTML Calendar Generator
|
|
343
|
-
|
|
344
|
-
Using core functions without React for maximum flexibility.
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
import {
|
|
348
|
-
buildMonth,
|
|
349
|
-
createCalendarAccessor,
|
|
350
|
-
getWeekdays,
|
|
351
|
-
getMonthName
|
|
352
|
-
} from '@gobrand/calendar-core';
|
|
353
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
354
|
-
|
|
355
|
-
type BlogPost = {
|
|
356
|
-
id: string;
|
|
357
|
-
title: string;
|
|
358
|
-
publishedAt: Temporal.PlainDate;
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const posts: BlogPost[] = [
|
|
362
|
-
{ id: '1', title: 'Getting Started with Temporal', publishedAt: Temporal.PlainDate.from('2025-01-15') },
|
|
363
|
-
{ id: '2', title: 'Building Calendars', publishedAt: Temporal.PlainDate.from('2025-01-20') },
|
|
364
|
-
];
|
|
365
|
-
|
|
366
|
-
const accessor = createCalendarAccessor<BlogPost>({
|
|
367
|
-
getDate: (post) => post.publishedAt,
|
|
20
|
+
getDate: (e) => e.date,
|
|
368
21
|
});
|
|
369
22
|
|
|
370
|
-
|
|
371
|
-
const month = buildMonth(2025, 1, {
|
|
372
|
-
weekStartsOn: 1,
|
|
373
|
-
data: posts,
|
|
374
|
-
accessor,
|
|
375
|
-
});
|
|
23
|
+
const month = buildMonth(2025, 1, { weekStartsOn: 1, data: events, accessor });
|
|
376
24
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const monthName = getMonthName(month.month);
|
|
380
|
-
|
|
381
|
-
let html = `<h2>${monthName} ${month.month.year}</h2>`;
|
|
382
|
-
html += '<table><thead><tr>';
|
|
383
|
-
weekdays.forEach(day => {
|
|
384
|
-
html += `<th>${day}</th>`;
|
|
385
|
-
});
|
|
386
|
-
html += '</tr></thead><tbody>';
|
|
387
|
-
|
|
388
|
-
month.weeks.forEach(week => {
|
|
389
|
-
html += '<tr>';
|
|
390
|
-
week.forEach(day => {
|
|
391
|
-
const className = day.isCurrentMonth ? '' : 'other-month';
|
|
392
|
-
html += `<td class="${className}">`;
|
|
393
|
-
html += `<div>${day.date.day}</div>`;
|
|
394
|
-
day.items.forEach(post => {
|
|
395
|
-
html += `<div class="post">${post.title}</div>`;
|
|
396
|
-
});
|
|
397
|
-
html += '</td>';
|
|
398
|
-
});
|
|
399
|
-
html += '</tr>';
|
|
25
|
+
month.weeks.flat().forEach(day => {
|
|
26
|
+
console.log(day.date.toString(), day.items.length);
|
|
400
27
|
});
|
|
401
|
-
|
|
402
|
-
html += '</tbody></table>';
|
|
403
|
-
document.getElementById('calendar')!.innerHTML = html;
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
## Browser Support
|
|
407
|
-
|
|
408
|
-
The Temporal API is a Stage 3 TC39 proposal. The polyfill `@js-temporal/polyfill` is included as a dependency, ensuring compatibility across all modern browsers and Node.js environments.
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
412
28
|
```
|
|
413
29
|
|
|
414
|
-
##
|
|
30
|
+
## Install
|
|
415
31
|
|
|
416
32
|
```bash
|
|
417
|
-
|
|
418
|
-
pnpm install
|
|
419
|
-
|
|
420
|
-
# Build all packages
|
|
421
|
-
pnpm build
|
|
422
|
-
|
|
423
|
-
# Run tests
|
|
424
|
-
pnpm test --run
|
|
425
|
-
|
|
426
|
-
# Type check
|
|
427
|
-
pnpm typecheck
|
|
428
|
-
|
|
429
|
-
# Release new version
|
|
430
|
-
pnpm release <patch|minor|major>
|
|
33
|
+
pnpm add @gobrand/calendar-core
|
|
431
34
|
```
|
|
432
35
|
|
|
433
|
-
##
|
|
36
|
+
## Docs
|
|
434
37
|
|
|
435
|
-
|
|
38
|
+
**[eng.gobrand.app/calendar](https://eng.gobrand.app/calendar)** — Full API reference, examples, and guides.
|
|
436
39
|
|
|
437
40
|
## License
|
|
438
41
|
|
|
439
|
-
MIT
|
|
440
|
-
|
|
441
|
-
## Built by Go Brand
|
|
442
|
-
|
|
443
|
-
temporal-calendar is built and maintained by [Go Brand](https://gobrand.app) - a modern social media management platform.
|
|
42
|
+
MIT © [Ruben Costa](https://x.com/PonziChad) / [Go Brand](https://gobrand.app)
|
package/dist/index.cjs
CHANGED
|
@@ -177,6 +177,7 @@ function buildMonth(year, month, options) {
|
|
|
177
177
|
}
|
|
178
178
|
const dateKey = currentDate.toString();
|
|
179
179
|
weeks[weeks.length - 1].push({
|
|
180
|
+
id: `${yearMonth.toString()}-${dateKey}`,
|
|
180
181
|
date: currentDate,
|
|
181
182
|
isCurrentMonth: currentDate.month === month,
|
|
182
183
|
isToday: import_polyfill2.Temporal.PlainDate.compare(currentDate, today) === 0,
|
|
@@ -218,6 +219,7 @@ function buildDay(date, options) {
|
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
const timeSlots = [];
|
|
222
|
+
const dateStr = date.toString();
|
|
221
223
|
let currentHour = startHour;
|
|
222
224
|
let currentMinute = 0;
|
|
223
225
|
while (currentHour < endHour) {
|
|
@@ -244,6 +246,7 @@ function buildDay(date, options) {
|
|
|
244
246
|
}
|
|
245
247
|
}
|
|
246
248
|
timeSlots.push({
|
|
249
|
+
id: `${dateStr}-${String(currentHour).padStart(2, "0")}:${String(currentMinute).padStart(2, "0")}`,
|
|
247
250
|
hour: currentHour,
|
|
248
251
|
minute: currentMinute,
|
|
249
252
|
time: slotStart,
|
|
@@ -256,6 +259,7 @@ function buildDay(date, options) {
|
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
return {
|
|
262
|
+
id: dateStr,
|
|
259
263
|
date,
|
|
260
264
|
isToday: import_polyfill3.Temporal.PlainDate.compare(date, today) === 0,
|
|
261
265
|
timeSlots,
|
|
@@ -302,6 +306,7 @@ function buildWeek(date, options) {
|
|
|
302
306
|
timeSlots = dayView.timeSlots;
|
|
303
307
|
}
|
|
304
308
|
days.push({
|
|
309
|
+
id: `${weekStart.toString()}-${dateKey}`,
|
|
305
310
|
date: currentDate,
|
|
306
311
|
isToday: import_polyfill4.Temporal.PlainDate.compare(currentDate, today) === 0,
|
|
307
312
|
items: itemsByDate.get(dateKey) ?? [],
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,8 @@ type CalendarAccessor<TItem> = {
|
|
|
7
7
|
getEnd?: (item: TItem) => Temporal.ZonedDateTime;
|
|
8
8
|
};
|
|
9
9
|
type CalendarDay<TItem = unknown> = {
|
|
10
|
+
/** Unique identifier for this day within the view context. Use as React/Vue key. */
|
|
11
|
+
id: string;
|
|
10
12
|
date: Temporal.PlainDate;
|
|
11
13
|
isCurrentMonth: boolean;
|
|
12
14
|
isToday: boolean;
|
|
@@ -18,6 +20,8 @@ type CalendarMonth<TItem = unknown> = {
|
|
|
18
20
|
month: Temporal.PlainYearMonth;
|
|
19
21
|
};
|
|
20
22
|
type WeekDay<TItem = unknown> = {
|
|
23
|
+
/** Unique identifier for this day within the view context. Use as React/Vue key. */
|
|
24
|
+
id: string;
|
|
21
25
|
date: Temporal.PlainDate;
|
|
22
26
|
isToday: boolean;
|
|
23
27
|
items: TItem[];
|
|
@@ -29,12 +33,16 @@ type CalendarWeekView<TItem = unknown> = {
|
|
|
29
33
|
weekEnd: Temporal.PlainDate;
|
|
30
34
|
};
|
|
31
35
|
type TimeSlot<TItem = unknown> = {
|
|
36
|
+
/** Unique identifier for this time slot. Use as React/Vue key. */
|
|
37
|
+
id: string;
|
|
32
38
|
hour: number;
|
|
33
39
|
minute: number;
|
|
34
40
|
time: Temporal.PlainTime;
|
|
35
41
|
items: TItem[];
|
|
36
42
|
};
|
|
37
43
|
type CalendarDayView<TItem = unknown> = {
|
|
44
|
+
/** Unique identifier for this day view. Use as React/Vue key. */
|
|
45
|
+
id: string;
|
|
38
46
|
date: Temporal.PlainDate;
|
|
39
47
|
isToday: boolean;
|
|
40
48
|
timeSlots: TimeSlot<TItem>[];
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ type CalendarAccessor<TItem> = {
|
|
|
7
7
|
getEnd?: (item: TItem) => Temporal.ZonedDateTime;
|
|
8
8
|
};
|
|
9
9
|
type CalendarDay<TItem = unknown> = {
|
|
10
|
+
/** Unique identifier for this day within the view context. Use as React/Vue key. */
|
|
11
|
+
id: string;
|
|
10
12
|
date: Temporal.PlainDate;
|
|
11
13
|
isCurrentMonth: boolean;
|
|
12
14
|
isToday: boolean;
|
|
@@ -18,6 +20,8 @@ type CalendarMonth<TItem = unknown> = {
|
|
|
18
20
|
month: Temporal.PlainYearMonth;
|
|
19
21
|
};
|
|
20
22
|
type WeekDay<TItem = unknown> = {
|
|
23
|
+
/** Unique identifier for this day within the view context. Use as React/Vue key. */
|
|
24
|
+
id: string;
|
|
21
25
|
date: Temporal.PlainDate;
|
|
22
26
|
isToday: boolean;
|
|
23
27
|
items: TItem[];
|
|
@@ -29,12 +33,16 @@ type CalendarWeekView<TItem = unknown> = {
|
|
|
29
33
|
weekEnd: Temporal.PlainDate;
|
|
30
34
|
};
|
|
31
35
|
type TimeSlot<TItem = unknown> = {
|
|
36
|
+
/** Unique identifier for this time slot. Use as React/Vue key. */
|
|
37
|
+
id: string;
|
|
32
38
|
hour: number;
|
|
33
39
|
minute: number;
|
|
34
40
|
time: Temporal.PlainTime;
|
|
35
41
|
items: TItem[];
|
|
36
42
|
};
|
|
37
43
|
type CalendarDayView<TItem = unknown> = {
|
|
44
|
+
/** Unique identifier for this day view. Use as React/Vue key. */
|
|
45
|
+
id: string;
|
|
38
46
|
date: Temporal.PlainDate;
|
|
39
47
|
isToday: boolean;
|
|
40
48
|
timeSlots: TimeSlot<TItem>[];
|
package/dist/index.js
CHANGED
|
@@ -123,6 +123,7 @@ function buildMonth(year, month, options) {
|
|
|
123
123
|
}
|
|
124
124
|
const dateKey = currentDate.toString();
|
|
125
125
|
weeks[weeks.length - 1].push({
|
|
126
|
+
id: `${yearMonth.toString()}-${dateKey}`,
|
|
126
127
|
date: currentDate,
|
|
127
128
|
isCurrentMonth: currentDate.month === month,
|
|
128
129
|
isToday: Temporal2.PlainDate.compare(currentDate, today) === 0,
|
|
@@ -164,6 +165,7 @@ function buildDay(date, options) {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
const timeSlots = [];
|
|
168
|
+
const dateStr = date.toString();
|
|
167
169
|
let currentHour = startHour;
|
|
168
170
|
let currentMinute = 0;
|
|
169
171
|
while (currentHour < endHour) {
|
|
@@ -190,6 +192,7 @@ function buildDay(date, options) {
|
|
|
190
192
|
}
|
|
191
193
|
}
|
|
192
194
|
timeSlots.push({
|
|
195
|
+
id: `${dateStr}-${String(currentHour).padStart(2, "0")}:${String(currentMinute).padStart(2, "0")}`,
|
|
193
196
|
hour: currentHour,
|
|
194
197
|
minute: currentMinute,
|
|
195
198
|
time: slotStart,
|
|
@@ -202,6 +205,7 @@ function buildDay(date, options) {
|
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
return {
|
|
208
|
+
id: dateStr,
|
|
205
209
|
date,
|
|
206
210
|
isToday: Temporal3.PlainDate.compare(date, today) === 0,
|
|
207
211
|
timeSlots,
|
|
@@ -248,6 +252,7 @@ function buildWeek(date, options) {
|
|
|
248
252
|
timeSlots = dayView.timeSlots;
|
|
249
253
|
}
|
|
250
254
|
days.push({
|
|
255
|
+
id: `${weekStart.toString()}-${dateKey}`,
|
|
251
256
|
date: currentDate,
|
|
252
257
|
isToday: Temporal4.PlainDate.compare(currentDate, today) === 0,
|
|
253
258
|
items: itemsByDate.get(dateKey) ?? [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobrand/calendar-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "Lightweight utility library for building calendars using the Temporal API",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/go-brand/calendar/issues"
|
|
33
33
|
},
|
|
34
|
-
"homepage": "https://
|
|
34
|
+
"homepage": "https://eng.gobrand.app/calendar",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@js-temporal/polyfill": "^0.5.1",
|
|
37
37
|
"@tanstack/store": "^0.8.0"
|