@neo-reckoning/core 0.1.0-alpha.1 → 0.1.0-alpha.2
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 +149 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @neo-reckoning/core
|
|
2
|
+
|
|
3
|
+
Headless calendar state management library. Zero dependencies. Framework-agnostic.
|
|
4
|
+
|
|
5
|
+
A modern TypeScript rewrite of the concepts from [reckoning](https://github.com/sdougbrown/reckoning), extended with sub-day time support, hourly recurrence, timezone handling, and agent-ready scheduling primitives.
|
|
6
|
+
|
|
7
|
+
## What this does
|
|
8
|
+
|
|
9
|
+
Neo-reckoning is a **computation library**, not a UI library. It tells you *what* and *when* — your app decides *how it looks*.
|
|
10
|
+
|
|
11
|
+
- **DateRange evaluation** — does a date/time fall within a range? Supports explicit dates, day-of-week/month recurrence, hourly recurrence (`everyHour` or `startTime`/`repeatEvery`), and timezone conversion.
|
|
12
|
+
- **Calendar grid generation** — produces Month/Week/Day data structures for rendering, with configurable fidelity (year/month/week/day) so you only compute what you need.
|
|
13
|
+
- **Timeline positioning** — computes event positions for hourly views with overlap detection and column assignment.
|
|
14
|
+
- **Span detection** — groups contiguous days into spans with lane assignment for consistent multi-day bar rendering.
|
|
15
|
+
- **Conflict detection** — finds overlapping timed ranges on a given day or across a window.
|
|
16
|
+
- **Free slot detection** — finds gaps in a schedule where new events could fit.
|
|
17
|
+
- **Schedule scoring** — computes quality metrics (conflicts, free time, focus blocks, context switches) for evaluating rearrangements.
|
|
18
|
+
- **Event normalization** — converts DateRanges into a flat `CalendarEvent[]` that can be merged with imported calendar events.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
npm install @neo-reckoning/core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { RangeEvaluator, CalendarGrid } from '@neo-reckoning/core';
|
|
30
|
+
import type { DateRange } from '@neo-reckoning/core';
|
|
31
|
+
|
|
32
|
+
// Define some ranges
|
|
33
|
+
const ranges: DateRange[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'standup',
|
|
36
|
+
label: 'Daily Standup',
|
|
37
|
+
everyWeekday: [1, 2, 3, 4, 5], // Mon-Fri
|
|
38
|
+
startTime: '09:00',
|
|
39
|
+
endTime: '09:15',
|
|
40
|
+
timezone: 'UTC',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'meds',
|
|
44
|
+
label: 'Medication',
|
|
45
|
+
everyHour: [8, 14, 20], // 8am, 2pm, 8pm
|
|
46
|
+
duration: 5,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'vacation',
|
|
50
|
+
label: 'Spring Break',
|
|
51
|
+
fromDate: '2026-03-23',
|
|
52
|
+
toDate: '2026-03-27',
|
|
53
|
+
fixedBetween: true,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// Evaluate ranges
|
|
58
|
+
const evaluator = new RangeEvaluator('America/New_York');
|
|
59
|
+
|
|
60
|
+
// Check if a date is in a range
|
|
61
|
+
evaluator.isDateInRange('2026-03-25', ranges[2]); // true (vacation)
|
|
62
|
+
|
|
63
|
+
// Expand occurrences in a window
|
|
64
|
+
const occurrences = evaluator.expand(ranges[0], new Date(2026, 2, 23), new Date(2026, 2, 27));
|
|
65
|
+
// → [{ date: '2026-03-23', startTime: '04:00', ... }, ...] (UTC→ET converted)
|
|
66
|
+
|
|
67
|
+
// Find conflicts
|
|
68
|
+
const conflicts = evaluator.findConflicts(ranges, '2026-03-23');
|
|
69
|
+
|
|
70
|
+
// Find free slots
|
|
71
|
+
const freeSlots = evaluator.findFreeSlots(ranges, '2026-03-23', {
|
|
72
|
+
dayStart: '09:00',
|
|
73
|
+
dayEnd: '17:00',
|
|
74
|
+
minDuration: 30,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Generate a calendar grid
|
|
78
|
+
const grid = new CalendarGrid({
|
|
79
|
+
focusDate: '2026-03-15',
|
|
80
|
+
numberOfMonths: 1,
|
|
81
|
+
ranges,
|
|
82
|
+
fidelity: 'month', // skip time slot computation
|
|
83
|
+
weekStartsOn: 1, // Monday
|
|
84
|
+
userTimezone: 'America/New_York',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
grid.months[0].weeks[0].days[0].ranges;
|
|
88
|
+
// → [{ rangeId: '...', label: '...', isStart: true, isEnd: false, ... }]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Core concepts
|
|
92
|
+
|
|
93
|
+
### DateRange
|
|
94
|
+
|
|
95
|
+
The fundamental data model. Defines a set of dates and/or times using explicit values or recurrence patterns.
|
|
96
|
+
|
|
97
|
+
**Day-level** (from original Reckoning):
|
|
98
|
+
- `dates` — explicit date list
|
|
99
|
+
- `everyWeekday` — days of week (0=Sun, 6=Sat)
|
|
100
|
+
- `everyDate` — days of month (1-31)
|
|
101
|
+
- `everyMonth` — months (1-12)
|
|
102
|
+
- `fromDate` / `toDate` / `fixedBetween` — date bounds
|
|
103
|
+
|
|
104
|
+
**Sub-day** (two mutually exclusive approaches):
|
|
105
|
+
- `everyHour` — explicit hours list, e.g. `[6, 14, 22]`
|
|
106
|
+
- `startTime` / `endTime` / `repeatEvery` / `duration` — interval-based
|
|
107
|
+
|
|
108
|
+
Day and time fields combine as AND: `everyWeekday: [1,3,5]` + `everyHour: [9,17]` means "9am and 5pm on Mon/Wed/Fri."
|
|
109
|
+
|
|
110
|
+
### Timezone handling
|
|
111
|
+
|
|
112
|
+
- **UTC** (`timezone: 'UTC'`): Times are converted to the user's timezone via `Intl.DateTimeFormat`. Default for API-sourced ranges.
|
|
113
|
+
- **Floating** (`timezone: null`): Times are as-is, no conversion. "9am" means 9am wherever you are.
|
|
114
|
+
- **Specific timezone**: Converted to user's timezone.
|
|
115
|
+
|
|
116
|
+
### View fidelity
|
|
117
|
+
|
|
118
|
+
`CalendarGrid` accepts a `fidelity` option to control how much it computes per day:
|
|
119
|
+
|
|
120
|
+
| Fidelity | What's computed | Use case |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `'year'` | `hasActivity` boolean only | Year overview, heatmaps |
|
|
123
|
+
| `'month'` | `ranges[]` (span info) | Month grid |
|
|
124
|
+
| `'week'` | `ranges[]` + `timeSlots[]` | Week view |
|
|
125
|
+
| `'day'` | `ranges[]` + `timeSlots[]` | Day detail |
|
|
126
|
+
|
|
127
|
+
### No styling
|
|
128
|
+
|
|
129
|
+
This library carries identifiers (`rangeId`, `sourceId`), not colors or CSS. Your app maps range IDs to colors via its own palette/theme system.
|
|
130
|
+
|
|
131
|
+
## API reference
|
|
132
|
+
|
|
133
|
+
### Classes
|
|
134
|
+
|
|
135
|
+
- **`RangeEvaluator`** — `isDateInRange()`, `isInRange()`, `expand()`, `expandDay()`, `computeSpans()`, `findConflicts()`, `findConflictsInWindow()`, `findFreeSlots()`, `findNextFreeSlot()`
|
|
136
|
+
- **`CalendarGrid`** — Month/week/day grid generation with navigation (`next()`, `prev()`, `goTo()`)
|
|
137
|
+
- **`TimelineGrid`** — Hourly timeline with event positioning and overlap detection
|
|
138
|
+
- **`YearGrid`** — Lightweight year-level view with per-day range counts
|
|
139
|
+
|
|
140
|
+
### Functions
|
|
141
|
+
|
|
142
|
+
- **`scoreSchedule()`** — Schedule quality metrics
|
|
143
|
+
- **`resolveDisplayType()`** — Auto-resolve display hints based on range type and view fidelity
|
|
144
|
+
- **`fromDateRange()`** / **`expandToEvents()`** — Convert ranges to normalized `CalendarEvent[]`
|
|
145
|
+
- **`computeEventPositions()`** — Standalone overlap detection for timeline views
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo-reckoning/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "https://github.com/sdougbrown/neo-reckoning.git",
|
|
23
|
+
"url": "git+https://github.com/sdougbrown/neo-reckoning.git",
|
|
24
24
|
"directory": "packages/core"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|