@tasknotes/calendar 0.0.0-stage → 0.1.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +163 -2
  4. package/dist/calendar.css +1345 -0
  5. package/dist/cjs/core.cjs +716 -0
  6. package/dist/cjs/index.cjs +4206 -0
  7. package/dist/cjs/react.cjs +4034 -0
  8. package/dist/esm/chunk-DVMZ5OXN.js +694 -0
  9. package/dist/esm/chunk-L4WKZ6WT.js +0 -0
  10. package/dist/esm/chunk-UWCSL5AN.js +3401 -0
  11. package/dist/esm/core.js +85 -0
  12. package/dist/esm/index.js +223 -0
  13. package/dist/esm/react.js +46 -0
  14. package/dist/types/core/date.d.cts +30 -0
  15. package/dist/types/core/date.d.ts +30 -0
  16. package/dist/types/core/index.d.cts +7 -0
  17. package/dist/types/core/index.d.ts +7 -0
  18. package/dist/types/core/layout.d.cts +82 -0
  19. package/dist/types/core/layout.d.ts +82 -0
  20. package/dist/types/core/order.d.cts +7 -0
  21. package/dist/types/core/order.d.ts +7 -0
  22. package/dist/types/core/plan.d.cts +42 -0
  23. package/dist/types/core/plan.d.ts +42 -0
  24. package/dist/types/core/store.d.cts +60 -0
  25. package/dist/types/core/store.d.ts +60 -0
  26. package/dist/types/core/types.d.cts +69 -0
  27. package/dist/types/core/types.d.ts +69 -0
  28. package/dist/types/core/zone.d.cts +43 -0
  29. package/dist/types/core/zone.d.ts +43 -0
  30. package/dist/types/dom/calendar.d.cts +206 -0
  31. package/dist/types/dom/calendar.d.ts +206 -0
  32. package/dist/types/dom/context.d.cts +107 -0
  33. package/dist/types/dom/context.d.ts +107 -0
  34. package/dist/types/dom/dom.d.cts +8 -0
  35. package/dist/types/dom/dom.d.ts +8 -0
  36. package/dist/types/dom/format.d.cts +30 -0
  37. package/dist/types/dom/format.d.ts +30 -0
  38. package/dist/types/dom/options.d.cts +401 -0
  39. package/dist/types/dom/options.d.ts +401 -0
  40. package/dist/types/dom/views/daygrid.d.cts +42 -0
  41. package/dist/types/dom/views/daygrid.d.ts +42 -0
  42. package/dist/types/dom/views/list.d.cts +27 -0
  43. package/dist/types/dom/views/list.d.ts +27 -0
  44. package/dist/types/dom/views/timegrid.d.cts +54 -0
  45. package/dist/types/dom/views/timegrid.d.ts +54 -0
  46. package/dist/types/dom/views/timeline.d.cts +83 -0
  47. package/dist/types/dom/views/timeline.d.ts +83 -0
  48. package/dist/types/dom/views/year.d.cts +26 -0
  49. package/dist/types/dom/views/year.d.ts +26 -0
  50. package/dist/types/index.d.cts +6 -0
  51. package/dist/types/index.d.ts +6 -0
  52. package/dist/types/react/index.d.cts +20 -0
  53. package/dist/types/react/index.d.ts +20 -0
  54. package/dist/types/tasknotes/bases.d.cts +50 -0
  55. package/dist/types/tasknotes/bases.d.ts +50 -0
  56. package/package.json +108 -5
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial public release.
6
+
7
+ - DOM-free date, timezone, store, layout and planning primitives.
8
+ - Incremental DOM calendar: week/day/N-day, month (including dots), list, year and timeline views.
9
+ - Mouse, touch and keyboard item editing, render hooks, popout support and an optional React wrapper.
10
+ - TaskNotes Bases settings adapter preserving existing saved view names and layout settings.
11
+ - Scoped stylesheet with host theme variables; ESM, CommonJS and typed consumer entry points.
12
+ - Package installation checks, deterministic Chromium visual baselines and structural accessibility coverage.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Callum Alpass
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,164 @@
1
- # Temporary Holding Version
1
+ # @tasknotes/calendar
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A task-native calendar engine for TaskNotes: week/day/N-day time grid, month, list, year and timeline views, with incremental rendering, DST-safe drag arithmetic and popout-window support. No runtime dependencies.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @tasknotes/calendar
9
+ # Only for the optional React wrapper:
10
+ npm install react
11
+ ```
12
+
13
+ ES modules and CommonJS (`require("@tasknotes/calendar")`) are supported.
14
+ Import the stylesheet once in your bundler. React 18+ is an optional peer; the
15
+ DOM and core entry points have no runtime dependencies. Use a modern browser
16
+ with CSS Grid, `color-mix()`, Pointer Events and `ResizeObserver`. Node 22+
17
+ is supported for tooling and DOM-free core use.
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { Calendar } from "@tasknotes/calendar";
23
+ import "@tasknotes/calendar/calendar.css";
24
+
25
+ const calendar = new Calendar(el, {
26
+ view: "week",
27
+ timeZone: "local",
28
+ editable: true,
29
+ onItemDrop: ({ item, startStr, endStr, revert }) => save(item.id, startStr, endStr).catch(revert),
30
+ });
31
+ calendar.setItems([{ id: "t1", title: "Plan sprint", start: "2026-10-21T10:00", end: "2026-10-21T11:30" }]);
32
+ // On host/component teardown:
33
+ // calendar.destroy();
34
+ ```
35
+
36
+ Give `el` a height (for example `height: 600px`) for the default
37
+ `height: "fill"`, or choose `height: "auto"` to grow with content.
38
+ The host persists changes; the calendar supplies optimistic updates and a
39
+ `revert()` function. Destroy the instance to release listeners and observers.
40
+
41
+ - **Items** take `start`/`end` as `YYYY-MM-DD` (all-day) or `YYYY-MM-DDTHH:mm` (floating, read in the calendar's zone), ISO strings with an offset, or epoch ms. `end` is exclusive. Optional fields: `allDay`, `color`, `kind`, `lane` (`"plan"`/`"actual"`), `display: "background"`, `completed`, `dependsOn`, `rev` (cheap change signature), `data`.
42
+ - **Updates** are incremental: `setItems` diffs against the previous set, and `upsertItems`/`removeItems` re-render only the days touched. Render hooks (`itemDidMount`, …) fire only for elements that actually change, but every callback's `item` is the object most recently passed in for that id, so `item.data` is never stale.
43
+ - **Render hooks** (`itemContent`, `itemClassNames`, `itemDidMount`, `itemWillUnmount`, `listItemContent`) get `variant` (`"timed"`, `"bar"`, `"dot"`, `"indicator"`, `"list"`, `"timeline"`) and `timeText`, the time label in the calendar's locale and hour format (`"9am – 10:30am"` for timed, list and timeline items, the start for bars and dots, empty for all-day items).
44
+ - **Drags** move the item itself with the pointer in every view (time grid, all-day row, month, timeline), for mouse and touch; the dragged item is never folded into "+N more".
45
+ - **Changes** (`onItemDrop`, `onItemResize`, `onSelect`, `onExternalDrop`) report TaskNotes storage strings (`startStr`/`endStr`), `revert()`, and `source: "pointer" | "keyboard"`. Returning a rejected promise reverts.
46
+ - **Clicks**: `onDateClick` reports `minute` (the slot's wall-clock minute, snapped to `slotDuration`; null for day cells and the all-day row), `allDay` and `startStr` (`"YYYY-MM-DD"` or `"YYYY-MM-DDTHH:mm"`), so a tap on a slot can prefill a time.
47
+ - **Input**: mouse drag after 4px, touch long press (`longPressDelay`), keyboard (Enter/Space, Shift+F10, Alt+Arrow to move, Alt+Shift+Arrow to resize), external drags via `beginExternalDrag` or native HTML5 drops.
48
+ - **Popouts**: all listeners, timers and observers use the host element's `ownerDocument`; bare `window`/`document` are lint errors in `src`.
49
+
50
+ ### Views and options
51
+
52
+ | `view` | Layout |
53
+ | --- | --- |
54
+ | `"week"` / `"day"` / `"days"` | Time grid; `dayCount` controls the N-day view |
55
+ | `"month"` | Month cells, full items or compact dots |
56
+ | `"list"` | Agenda for `listDayCount` days (default 7) |
57
+ | `"year"` | Twelve mini-months with activity indicators |
58
+ | `"timeline"` | Resource/task rows with dependency bars |
59
+
60
+ Pass partial options to the constructor or `setOptions()`.
61
+ `changeView()`, `gotoDate()`, `prev()`, `next()` and `goToday()` control navigation.
62
+ Common options include `date`, `timeZone` (`"local"` or an IANA zone),
63
+ `locale`, `firstDay` (0–6), `hour12`, `weekends`, `hiddenDays`,
64
+ `weekNumbers`, `toolbar`, `editable`, `selectable`, `navLinks`,
65
+ `dayMaxEvents`, `slotMinTime`, `slotMaxTime`, `slotDuration`,
66
+ `snapDuration`, `scrollTime` and `nowIndicator`. The time grid defaults
67
+ to 00:00–24:00 in 30-minute slots, initially scrolled to 08:00.
68
+ `text` supplies translated labels; `itemContent` and related hooks supply
69
+ custom DOM. The exported `CalendarOptions<D>` type documents the full API.
70
+
71
+ ### Month and list
72
+
73
+ - `monthDisplay: "dots"` draws every item in a month cell as a small dot (`--tnc-dot-size`, `--tnc-dot-gap`) that wraps across the cell, with "+N" when they do not fit; `dayMaxEvents` caps the dots (`false` lets cells grow). The day cell is the tap target: dots ignore the pointer, are `aria-hidden`, get no content hook, and the cell is labelled "<date>, N items" (`text.itemCount`). Items in dots have `variant: "indicator"`; colour them with `color` or `itemDidMount` setting `--tnc-c`.
74
+ - **"+N more" height**: day rows (and the time grid's all-day row) make room for the link at its rendered height, so a host may give it a 44px touch target (`--tnc-more-h`, or any min-height) without overflowing the row.
75
+ - `listStart: "date" | "week"`: the list view starts at the date (default; FullCalendar `duration: { days: n }` lists, as the TaskNotes plugin and app used) or at the start of its week (FullCalendar's stock `listWeek`); week-aligned lists step by whole weeks.
76
+
77
+ ### Timeline
78
+
79
+ `view: "timeline"` draws one row per `timeline.rows` entry (`rowOf(item)` picks the row) with bars, milestones (`kind: "milestone"`) and dependency arrows (`timeline.dependencies`).
80
+
81
+ - **Scale**: `timeline.scale = { unit, dayWidth | unitWidth, snapMinutes }`. `dayWidth` sets pixels per day directly; `unit` then only chooses the header ticks. `TIMELINE_UNIT_DAYS` is the days-per-unit table used to turn `unitWidth` into a day width.
82
+ - **Dependencies**: `dependsOn` takes ids (finish-to-start) or `{ id, type: "FS" | "SS" | "FF" | "SF" }`. Arrows leave and enter the edges the type names, and `dependencyConflicts` (also `dependencySatisfied`, `itemDependencies`, `dependencyType` in core) checks those edges.
83
+ - **Linking**: with `timeline.onLink`, bars get start and finish link handles (a milestone has one). Dragging from a handle to another bar's handle, or to its body (nearer edge; a milestone's body is its start), calls `onLink({ from, to, fromEdge, toEdge, type })`. `canLink` returns `false` or a reason to refuse a target live, and `onLinkDrag` reports `start`/`over`/`end` phases for feedback. On touch, handles show on the bar in `selected` (44px hit areas) and on every bar while linking.
84
+ - **Selection**: the `selected` option (id or ids) marks bars `is-selected` without a rebuild.
85
+ - **Hit testing**: `calendar.hitTest(x, y)` returns `{ kind: "timeline", day, minute, rowId, area, overTrack }` where `area` is `"track"`, `"header"`, `"label"` or `"outside"` (inside the chart but past the rows or range), and null outside the chart. External drags (`beginExternalDrag`) drop on rows, group headings and the date header; `onExternalDrop` carries the `hit`, and `onExternalDragEnd({ source, dropped, hit })` fires for every external drag, dropped or not.
86
+ - **Scrolling and zoom**: `calendar.scrollToDate(date, { align: "start" | "center" | "end", smooth })`. In the timeline, `setOptions` keeps what is on screen when `date`, range or scale change (a scale change keeps the middle of the visible track); `setOptions(next, { anchor: { clientX } })` keeps the instant under the pointer instead, e.g. for Ctrl+wheel zoom.
87
+ - **Row labels**: `rowLabelDidMount({ row, el })` / `rowLabelWillUnmount` hand you each label cell to fill (React portals work); rows compare by value, so unchanged rows keep their cells, and a rows-only change re-renders in place. The click that ends a drag is swallowed in the capture phase, before it reaches content in the calendar.
88
+ - **Small bars**: bars under 28px hide their title; under 20px they drop their resize grips and get a wider hit area so they still move and tap.
89
+ - **Keyboard**: `resizeHandleLabel({ item, edge })` makes the grips focusable buttons; Left/Right on a focused grip moves that edge by the snap. `todayLabel` labels the today line.
90
+
91
+ `@tasknotes/calendar/core` exposes the DOM-free parts (zones, store, layout, ordering, planning helpers) and `@tasknotes/calendar/react` a thin `TaskCalendar` wrapper.
92
+
93
+ ### TaskNotes Bases settings
94
+
95
+ `basesLayoutOptions(settings)` maps the settings stored by the TaskNotes plugin's Bases calendar view (`viewFromBases`/`viewToBases` translate the FullCalendar view names held in `.base` files) to calendar options, applying the plugin's normalisation so existing settings keep their meaning. Saved `listWeek` views keep starting at the date (`listStart: "date"`): the plugin configured FullCalendar's list as `duration: { days: listDayCount }`, which aligns to the day, not the week. See `docs/integration/plugin-bases-calendar-spec.md`.
96
+
97
+ ## Theming
98
+
99
+ Set `--tnc-theme-*` variables on the host (they inherit into the calendar)
100
+ or on `.tnc`. The stylesheet is scoped to `.tnc` and does not inject CSS.
101
+
102
+ ```css
103
+ .my-calendar {
104
+ --tnc-theme-bg: #181818;
105
+ --tnc-theme-bg-2: #222;
106
+ --tnc-theme-bg-3: #303030;
107
+ --tnc-theme-text: #f5f5f5;
108
+ --tnc-theme-muted: #bbb;
109
+ --tnc-theme-faint: #aaa;
110
+ --tnc-theme-line: #444;
111
+ --tnc-theme-line-strong: #666;
112
+ --tnc-theme-accent: #b6a3ff;
113
+ --tnc-theme-accent-text: #181818;
114
+ }
115
+ ```
116
+
117
+ Additional semantic colours: `--tnc-theme-now`, `--tnc-theme-warn`,
118
+ `--tnc-theme-ok`, `--tnc-theme-amber`. Item colours come from `color`,
119
+ `backgroundColor`, `borderColor` and `textColor`.
120
+ Override sizing tokens such as `--tnc-font-size`, `--tnc-small`,
121
+ `--tnc-radius`, `--tnc-bar-h`, `--tnc-more-h`, `--tnc-dot-size` and
122
+ `--tnc-dot-gap` directly on `.tnc` (not just its ancestor).
123
+
124
+ ## Accessibility
125
+
126
+ Items are labelled, focusable buttons: Enter/Space activates, Shift+F10 opens
127
+ the host's context menu, Alt+Arrow moves, and Alt+Shift+Arrow resizes when
128
+ editing is allowed (the list and year views are read-only). Month dots and year days expose date/item-count labels;
129
+ Enter/Space invokes `onDateClick`. Dots are summaries, not individual item
130
+ controls. Time grid and timeline expose labelled tables; month exposes a grid.
131
+ Full arrow-key grid navigation, keyboard dependency linking and keyboard range
132
+ selection are not implemented. Hosts must provide alternatives for those actions.
133
+ Structural ARIA checks are not a full WCAG audit; verify contrast and custom hook
134
+ content in your own theme.
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ npm ci
140
+ npm run playground # http://localhost:4187 — demo data, options form, event log
141
+ npm test # unit tests (vitest)
142
+ npm run test:browser # Playwright: Chromium + touch (Pixel 7)
143
+ npm run lint && npm run typecheck
144
+ npm run build # dist/esm, dist/cjs, dist/types, dist/calendar.css
145
+ npm run test:package # pack/install; ESM, CJS, CSS, React and strict consumer types
146
+ ```
147
+
148
+ Visual baselines and interaction tests use Chromium in the official
149
+ `mcr.microsoft.com/playwright:v1.64.1-noble` image, also used by CI. To run
150
+ the same environment locally (Docker required):
151
+
152
+ ```bash
153
+ docker run --rm --ipc=host -v "$PWD:/work" -w /work \\
154
+ -v /work/node_modules -e CI=true \\
155
+ mcr.microsoft.com/playwright:v1.64.1-noble \\
156
+ bash -lc 'npm ci && npm run test:browser'
157
+ ```
158
+
159
+ Append `-- --update-snapshots` to the test command to regenerate baselines
160
+ **inside that image**, then review the PNGs before accepting them. Tests fix
161
+ the date, timezone, locale, viewport, colour scheme and motion preference.
162
+ Keep the image version and the exact `@playwright/test` pin in sync.
163
+
164
+ The playground reads URL parameters (`view`, `tz`, `locale`, `date`, `theme`, `stress`, `height`, `capacity`, `display=dots`, `listStart=week`) and exposes `window.tnc` for scripted checks.