@dataverse-kit/calendar-kit 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.
- package/README.md +103 -0
- package/dist/CalendarView-CShiWFsR.d.ts +104 -0
- package/dist/CalendarView-Dd8BHOLE.d.cts +104 -0
- package/dist/core.cjs +192 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +64 -0
- package/dist/core.d.ts +64 -0
- package/dist/core.mjs +151 -0
- package/dist/core.mjs.map +1 -0
- package/dist/dataverse.cjs +300 -0
- package/dist/dataverse.cjs.map +1 -0
- package/dist/dataverse.d.cts +121 -0
- package/dist/dataverse.d.ts +121 -0
- package/dist/dataverse.mjs +263 -0
- package/dist/dataverse.mjs.map +1 -0
- package/dist/fake.cjs +199 -0
- package/dist/fake.cjs.map +1 -0
- package/dist/fake.d.cts +29 -0
- package/dist/fake.d.ts +29 -0
- package/dist/fake.mjs +171 -0
- package/dist/fake.mjs.map +1 -0
- package/dist/index.cjs +568 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +515 -0
- package/dist/index.mjs.map +1 -0
- package/dist/pcf-dataset.cjs +328 -0
- package/dist/pcf-dataset.cjs.map +1 -0
- package/dist/pcf-dataset.d.cts +98 -0
- package/dist/pcf-dataset.d.ts +98 -0
- package/dist/pcf-dataset.mjs +296 -0
- package/dist/pcf-dataset.mjs.map +1 -0
- package/dist/types-u3Gpxk53.d.cts +108 -0
- package/dist/types-u3Gpxk53.d.ts +108 -0
- package/dist/v9.cjs +555 -0
- package/dist/v9.cjs.map +1 -0
- package/dist/v9.d.cts +18 -0
- package/dist/v9.d.ts +18 -0
- package/dist/v9.mjs +517 -0
- package/dist/v9.mjs.map +1 -0
- package/package.json +104 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @dataverse-kit/calendar-kit
|
|
2
|
+
|
|
3
|
+
A calendar for the Microsoft stack, built on [FullCalendar v7](https://fullcalendar.io) (MIT
|
|
4
|
+
views only). You give it plain event objects; it knows nothing about Dataverse. The Dataverse
|
|
5
|
+
rules live in separate entries, so the same calendar serves a PCF control, a Power Apps code app
|
|
6
|
+
and a Power Pages site.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @dataverse-kit/calendar-kit
|
|
10
|
+
# peers: react@18, react-dom@18; @fluentui/react-components@9 only for ./v9
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Entries
|
|
14
|
+
|
|
15
|
+
| Import | What | Pulls in |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `@dataverse-kit/calendar-kit` | `CalendarView` — no Fluent (use this on Power Pages) | React, FullCalendar |
|
|
18
|
+
| `…/v9` | `FluentCalendar` — themed from the surrounding `FluentProvider` | + Fluent v9 |
|
|
19
|
+
| `…/core` | event contract, value kinds, zone conversion, colours | nothing |
|
|
20
|
+
| `…/dataverse` | DateTime behaviours, column mapping, Web API / form-parameter writers | nothing |
|
|
21
|
+
| `…/adapters/pcf-dataset` | PCF dataset → events, range filter, not-found detection | nothing |
|
|
22
|
+
| `…/fake` | seeded sample events and an in-memory data source | nothing |
|
|
23
|
+
|
|
24
|
+
## Use it
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { FluentCalendar } from '@dataverse-kit/calendar-kit/v9';
|
|
28
|
+
|
|
29
|
+
<FluentCalendar
|
|
30
|
+
events={[
|
|
31
|
+
{ id: '1', title: 'Consultation', start: '2026-09-30T15:00:00Z', end: '2026-09-30T15:30:00Z' },
|
|
32
|
+
{ id: '2', title: 'Clinic closed', start: '2026-10-02', end: '2026-10-03' },
|
|
33
|
+
]}
|
|
34
|
+
view="week"
|
|
35
|
+
editable
|
|
36
|
+
onEventChange={async (change) => {
|
|
37
|
+
await save(change); // reject → the event snaps back and a message is shown
|
|
38
|
+
}}
|
|
39
|
+
onSelectRange={(slot) => openQuickCreate(slot)}
|
|
40
|
+
onEventClick={(event) => openRecord(event.id)}
|
|
41
|
+
onRangeChange={(range) => load(range)}
|
|
42
|
+
/>;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Values: the shape of the text is its meaning
|
|
46
|
+
|
|
47
|
+
| Text | Kind | Dataverse behaviour |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `2026-09-29` | a day (all-day) | DateOnly |
|
|
50
|
+
| `2026-09-29T23:30:00` | a wall-clock time, same face everywhere | TimeZoneIndependent |
|
|
51
|
+
| `2026-09-30T06:30:00Z` | an instant, shown in the viewer's zone | UserLocal |
|
|
52
|
+
|
|
53
|
+
An event keeps its kind through a drag. All-day `end` is **exclusive** (the day after).
|
|
54
|
+
|
|
55
|
+
### Time zone
|
|
56
|
+
|
|
57
|
+
Pass `zone` — a function returning minutes **east** of UTC for an instant. In a PCF control that
|
|
58
|
+
is the Dynamics user's zone:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const zone = (d: Date) => context.userSettings.getTimeZoneOffsetMinutes(d);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Without it the browser's zone is used, which is wrong whenever the two differ.
|
|
65
|
+
|
|
66
|
+
### In a PCF dataset control
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { resolveColumnMap, resolveBehaviors, readDatasetEvents, buildRangeFilter } from '@dataverse-kit/calendar-kit/adapters/pcf-dataset';
|
|
70
|
+
import { toWebApiPatch, toQuickCreateParameters } from '@dataverse-kit/calendar-kit/dataverse';
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Map columns with manifest property-sets named `startColumn`, `endColumn`, `titleColumn`,
|
|
74
|
+
`colorColumn`, `groupColumn`, `allDayColumn`.
|
|
75
|
+
|
|
76
|
+
## Measured facts this package encodes
|
|
77
|
+
|
|
78
|
+
Measured on a live Dataverse org, not taken from documentation; each is pinned by a test.
|
|
79
|
+
|
|
80
|
+
- A dataset's `getValue()` on a date column returns an **ISO string**, not a `Date` — for all three
|
|
81
|
+
behaviours. DateOnly arrives as UTC midnight, so reading it through `new Date()` gives the
|
|
82
|
+
previous day west of UTC.
|
|
83
|
+
- TimeZoneIndependent and UserLocal columns report the same `dataType`; only metadata
|
|
84
|
+
(`Behavior` 1/2/3) tells them apart.
|
|
85
|
+
- `webAPI.updateRecord` and `navigation.openForm` parameters are **different contracts** — see
|
|
86
|
+
`src/dataverse/dates.ts`. A bare `YYYY-MM-DD` quick-create parameter stored the previous day.
|
|
87
|
+
- A dataset range filter is ANDed with the view's own filter.
|
|
88
|
+
|
|
89
|
+
## Known limits
|
|
90
|
+
|
|
91
|
+
- The root and `./v9` entries import FullCalendar (ESM) and its CSS, so they are for **bundlers**
|
|
92
|
+
(Vite, webpack, pcf-scripts). Their `.cjs` builds cannot be `require()`d from plain Node. The
|
|
93
|
+
pure entries (`./core`, `./dataverse`, `./adapters/*`, `./fake`) work anywhere.
|
|
94
|
+
- Importing both the root and `./v9` bundles `CalendarView` twice. Pick one.
|
|
95
|
+
- On a spring-forward day a selection inside the missing hour has zero length (that hour does
|
|
96
|
+
not exist in the zone).
|
|
97
|
+
|
|
98
|
+
## Licensing
|
|
99
|
+
|
|
100
|
+
MIT. Uses only FullCalendar's MIT views (day grid, time grid, list, multi-month, interaction).
|
|
101
|
+
FullCalendar Premium (resource and timeline views) is **not** included or required.
|
|
102
|
+
|
|
103
|
+
**Stack:** React 18 · FullCalendar 7.1 · Fluent UI v9 (optional) · TypeScript · tsup
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { a as CalendarEvent, Z as ZoneOffset, B as BusinessHours, S as SlotSelection, b as CalendarEventChange, d as VisibleRange } from './types-u3Gpxk53.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The colours a calendar can be themed with, as plain CSS values (hex, rgb(), or `var(--x)`).
|
|
6
|
+
* Each maps onto one of FullCalendar v7's classic-theme custom properties, so theming is just
|
|
7
|
+
* setting variables on the calendar's own container — no stylesheet override, nothing global.
|
|
8
|
+
*
|
|
9
|
+
* ★ Scoped to the container on purpose: FullCalendar's palette declares its defaults on `:root`,
|
|
10
|
+
* and a PCF control shares `:root` with the whole form. Two calendars on one form each set
|
|
11
|
+
* their own, and neither touches the host.
|
|
12
|
+
*/
|
|
13
|
+
interface CalendarTheme {
|
|
14
|
+
primary?: string;
|
|
15
|
+
primaryForeground?: string;
|
|
16
|
+
background?: string;
|
|
17
|
+
foreground?: string;
|
|
18
|
+
mutedForeground?: string;
|
|
19
|
+
faint?: string;
|
|
20
|
+
muted?: string;
|
|
21
|
+
strong?: string;
|
|
22
|
+
border?: string;
|
|
23
|
+
strongBorder?: string;
|
|
24
|
+
today?: string;
|
|
25
|
+
highlight?: string;
|
|
26
|
+
now?: string;
|
|
27
|
+
button?: string;
|
|
28
|
+
buttonForeground?: string;
|
|
29
|
+
buttonBorder?: string;
|
|
30
|
+
buttonStrong?: string;
|
|
31
|
+
buttonStrongBorder?: string;
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
fontSize?: string;
|
|
34
|
+
/** Rendered as a banner above the calendar when a save fails. */
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
/** The container style that applies `theme`. Unset keys keep FullCalendar's defaults. */
|
|
38
|
+
declare function themeStyle(theme: CalendarTheme | undefined): React.CSSProperties;
|
|
39
|
+
|
|
40
|
+
/** The views a host can ask for, by plain name. */
|
|
41
|
+
type CalendarViewName = 'month' | 'week' | 'workWeek' | 'day' | 'list';
|
|
42
|
+
interface CalendarViewProps {
|
|
43
|
+
/** Plain event objects. See `CalendarEvent` for how a value's shape sets its kind. */
|
|
44
|
+
events: readonly CalendarEvent[];
|
|
45
|
+
/** The zone to show instants in. Default: the browser's. A PCF host passes the USER's. */
|
|
46
|
+
zone?: ZoneOffset;
|
|
47
|
+
/** Starting view. Default `week`. */
|
|
48
|
+
view?: CalendarViewName;
|
|
49
|
+
/** View buttons offered, in order. Default: month, week, day, list. */
|
|
50
|
+
views?: readonly CalendarViewName[];
|
|
51
|
+
/** `YYYY-MM-DD` to open on. Default today (in `zone`). */
|
|
52
|
+
initialDate?: string;
|
|
53
|
+
/** Allow drag and resize. Default false — read-only is the safe default. */
|
|
54
|
+
editable?: boolean;
|
|
55
|
+
/** Allow selecting an empty range (→ `onSelectRange`). Default: true when a handler is given. */
|
|
56
|
+
selectable?: boolean;
|
|
57
|
+
businessHours?: readonly BusinessHours[];
|
|
58
|
+
/** 0 = Sunday. */
|
|
59
|
+
firstDay?: number;
|
|
60
|
+
/** `HH:mm:ss` bounds of the time grid. */
|
|
61
|
+
slotMinTime?: string;
|
|
62
|
+
slotMaxTime?: string;
|
|
63
|
+
/** Where the time grid first scrolls to. Default `08:00:00`. */
|
|
64
|
+
scrollTime?: string;
|
|
65
|
+
/** Any CSS height, `'auto'`, or a number of pixels. Default `'auto'`. */
|
|
66
|
+
height?: number | string;
|
|
67
|
+
theme?: CalendarTheme;
|
|
68
|
+
/** Shown while data for a new range loads. */
|
|
69
|
+
loading?: boolean;
|
|
70
|
+
className?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Classes for FullCalendar's own parts. ★ v7 HASHES its class names (`fc-classic-1El`), so a
|
|
73
|
+
* descendant selector like `.fc-button` matches NOTHING — measured: 0 of 7 buttons. These go
|
|
74
|
+
* through FullCalendar's `buttonClass` / `headerToolbarClass` options instead.
|
|
75
|
+
*/
|
|
76
|
+
classNames?: {
|
|
77
|
+
button?: string;
|
|
78
|
+
toolbar?: string;
|
|
79
|
+
};
|
|
80
|
+
/** Accessible name for the calendar region. */
|
|
81
|
+
'aria-label'?: string;
|
|
82
|
+
onEventClick?: (event: CalendarEvent) => void;
|
|
83
|
+
onSelectRange?: (selection: SlotSelection) => void;
|
|
84
|
+
/**
|
|
85
|
+
* A move or resize. Return a promise to make it OPTIMISTIC: the event stays where it was
|
|
86
|
+
* dropped while the promise runs, and goes back — with a message — if it rejects.
|
|
87
|
+
*/
|
|
88
|
+
onEventChange?: (change: CalendarEventChange) => void | Promise<void>;
|
|
89
|
+
onRangeChange?: (range: VisibleRange) => void;
|
|
90
|
+
/** Called after a failed change has been reverted. The banner is shown either way. */
|
|
91
|
+
onChangeError?: (error: unknown, change: CalendarEventChange) => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* FullCalendar v7 (MIT views only), fed plain events. Knows nothing about Dataverse and nothing
|
|
95
|
+
* about Fluent — this entry is what a Power Pages site uses. `FluentCalendar` (`./v9`) wraps it
|
|
96
|
+
* with Fluent tokens.
|
|
97
|
+
*
|
|
98
|
+
* ★ Nothing here portals and nothing uses tabster, so it cannot join the Fluent/tabster
|
|
99
|
+
* singleton race that has taken down PCF controls on model-driven forms. The error banner is
|
|
100
|
+
* in-tree for the same reason: a Toast or Dialog would portal to `document.body`.
|
|
101
|
+
*/
|
|
102
|
+
declare function CalendarView(props: CalendarViewProps): React.ReactElement;
|
|
103
|
+
|
|
104
|
+
export { type CalendarTheme as C, CalendarView as a, type CalendarViewName as b, type CalendarViewProps as c, themeStyle as t };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { a as CalendarEvent, Z as ZoneOffset, B as BusinessHours, S as SlotSelection, b as CalendarEventChange, d as VisibleRange } from './types-u3Gpxk53.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The colours a calendar can be themed with, as plain CSS values (hex, rgb(), or `var(--x)`).
|
|
6
|
+
* Each maps onto one of FullCalendar v7's classic-theme custom properties, so theming is just
|
|
7
|
+
* setting variables on the calendar's own container — no stylesheet override, nothing global.
|
|
8
|
+
*
|
|
9
|
+
* ★ Scoped to the container on purpose: FullCalendar's palette declares its defaults on `:root`,
|
|
10
|
+
* and a PCF control shares `:root` with the whole form. Two calendars on one form each set
|
|
11
|
+
* their own, and neither touches the host.
|
|
12
|
+
*/
|
|
13
|
+
interface CalendarTheme {
|
|
14
|
+
primary?: string;
|
|
15
|
+
primaryForeground?: string;
|
|
16
|
+
background?: string;
|
|
17
|
+
foreground?: string;
|
|
18
|
+
mutedForeground?: string;
|
|
19
|
+
faint?: string;
|
|
20
|
+
muted?: string;
|
|
21
|
+
strong?: string;
|
|
22
|
+
border?: string;
|
|
23
|
+
strongBorder?: string;
|
|
24
|
+
today?: string;
|
|
25
|
+
highlight?: string;
|
|
26
|
+
now?: string;
|
|
27
|
+
button?: string;
|
|
28
|
+
buttonForeground?: string;
|
|
29
|
+
buttonBorder?: string;
|
|
30
|
+
buttonStrong?: string;
|
|
31
|
+
buttonStrongBorder?: string;
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
fontSize?: string;
|
|
34
|
+
/** Rendered as a banner above the calendar when a save fails. */
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
/** The container style that applies `theme`. Unset keys keep FullCalendar's defaults. */
|
|
38
|
+
declare function themeStyle(theme: CalendarTheme | undefined): React.CSSProperties;
|
|
39
|
+
|
|
40
|
+
/** The views a host can ask for, by plain name. */
|
|
41
|
+
type CalendarViewName = 'month' | 'week' | 'workWeek' | 'day' | 'list';
|
|
42
|
+
interface CalendarViewProps {
|
|
43
|
+
/** Plain event objects. See `CalendarEvent` for how a value's shape sets its kind. */
|
|
44
|
+
events: readonly CalendarEvent[];
|
|
45
|
+
/** The zone to show instants in. Default: the browser's. A PCF host passes the USER's. */
|
|
46
|
+
zone?: ZoneOffset;
|
|
47
|
+
/** Starting view. Default `week`. */
|
|
48
|
+
view?: CalendarViewName;
|
|
49
|
+
/** View buttons offered, in order. Default: month, week, day, list. */
|
|
50
|
+
views?: readonly CalendarViewName[];
|
|
51
|
+
/** `YYYY-MM-DD` to open on. Default today (in `zone`). */
|
|
52
|
+
initialDate?: string;
|
|
53
|
+
/** Allow drag and resize. Default false — read-only is the safe default. */
|
|
54
|
+
editable?: boolean;
|
|
55
|
+
/** Allow selecting an empty range (→ `onSelectRange`). Default: true when a handler is given. */
|
|
56
|
+
selectable?: boolean;
|
|
57
|
+
businessHours?: readonly BusinessHours[];
|
|
58
|
+
/** 0 = Sunday. */
|
|
59
|
+
firstDay?: number;
|
|
60
|
+
/** `HH:mm:ss` bounds of the time grid. */
|
|
61
|
+
slotMinTime?: string;
|
|
62
|
+
slotMaxTime?: string;
|
|
63
|
+
/** Where the time grid first scrolls to. Default `08:00:00`. */
|
|
64
|
+
scrollTime?: string;
|
|
65
|
+
/** Any CSS height, `'auto'`, or a number of pixels. Default `'auto'`. */
|
|
66
|
+
height?: number | string;
|
|
67
|
+
theme?: CalendarTheme;
|
|
68
|
+
/** Shown while data for a new range loads. */
|
|
69
|
+
loading?: boolean;
|
|
70
|
+
className?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Classes for FullCalendar's own parts. ★ v7 HASHES its class names (`fc-classic-1El`), so a
|
|
73
|
+
* descendant selector like `.fc-button` matches NOTHING — measured: 0 of 7 buttons. These go
|
|
74
|
+
* through FullCalendar's `buttonClass` / `headerToolbarClass` options instead.
|
|
75
|
+
*/
|
|
76
|
+
classNames?: {
|
|
77
|
+
button?: string;
|
|
78
|
+
toolbar?: string;
|
|
79
|
+
};
|
|
80
|
+
/** Accessible name for the calendar region. */
|
|
81
|
+
'aria-label'?: string;
|
|
82
|
+
onEventClick?: (event: CalendarEvent) => void;
|
|
83
|
+
onSelectRange?: (selection: SlotSelection) => void;
|
|
84
|
+
/**
|
|
85
|
+
* A move or resize. Return a promise to make it OPTIMISTIC: the event stays where it was
|
|
86
|
+
* dropped while the promise runs, and goes back — with a message — if it rejects.
|
|
87
|
+
*/
|
|
88
|
+
onEventChange?: (change: CalendarEventChange) => void | Promise<void>;
|
|
89
|
+
onRangeChange?: (range: VisibleRange) => void;
|
|
90
|
+
/** Called after a failed change has been reverted. The banner is shown either way. */
|
|
91
|
+
onChangeError?: (error: unknown, change: CalendarEventChange) => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* FullCalendar v7 (MIT views only), fed plain events. Knows nothing about Dataverse and nothing
|
|
95
|
+
* about Fluent — this entry is what a Power Pages site uses. `FluentCalendar` (`./v9`) wraps it
|
|
96
|
+
* with Fluent tokens.
|
|
97
|
+
*
|
|
98
|
+
* ★ Nothing here portals and nothing uses tabster, so it cannot join the Fluent/tabster
|
|
99
|
+
* singleton race that has taken down PCF controls on model-driven forms. The error banner is
|
|
100
|
+
* in-tree for the same reason: a Toast or Dialog would portal to `document.body`.
|
|
101
|
+
*/
|
|
102
|
+
declare function CalendarView(props: CalendarViewProps): React.ReactElement;
|
|
103
|
+
|
|
104
|
+
export { type CalendarTheme as C, CalendarView as a, type CalendarViewName as b, type CalendarViewProps as c, themeStyle as t };
|
package/dist/core.cjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/core/index.ts
|
|
21
|
+
var core_exports = {};
|
|
22
|
+
__export(core_exports, {
|
|
23
|
+
GROUP_PALETTE: () => GROUP_PALETTE,
|
|
24
|
+
addDays: () => addDays,
|
|
25
|
+
browserZone: () => browserZone,
|
|
26
|
+
contrastRatio: () => contrastRatio,
|
|
27
|
+
fixedZone: () => fixedZone,
|
|
28
|
+
fromWall: () => fromWall,
|
|
29
|
+
groupColor: () => groupColor,
|
|
30
|
+
kindOf: () => kindOf,
|
|
31
|
+
normaliseFloating: () => normaliseFloating,
|
|
32
|
+
parseHex: () => parseHex,
|
|
33
|
+
readableTextOn: () => readableTextOn,
|
|
34
|
+
toDate: () => toDate,
|
|
35
|
+
toInstant: () => toInstant,
|
|
36
|
+
toWall: () => toWall,
|
|
37
|
+
utcFieldsToWall: () => utcFieldsToWall
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(core_exports);
|
|
40
|
+
|
|
41
|
+
// src/core/datetime.ts
|
|
42
|
+
var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
43
|
+
var FLOATING_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?$/;
|
|
44
|
+
var INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})$/i;
|
|
45
|
+
var pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
46
|
+
var browserZone = (instant) => -instant.getTimezoneOffset();
|
|
47
|
+
var fixedZone = (minutesEast) => () => minutesEast;
|
|
48
|
+
function kindOf(value) {
|
|
49
|
+
if (value instanceof Date) {
|
|
50
|
+
if (Number.isNaN(value.getTime())) throw new RangeError("Invalid Date");
|
|
51
|
+
return "instant";
|
|
52
|
+
}
|
|
53
|
+
const local = DATE_RE.test(value) ? "date" : FLOATING_RE.test(value) ? "floating" : null;
|
|
54
|
+
if (local) {
|
|
55
|
+
const m = FLOATING_RE.exec(value) ?? DATE_RE.exec(value);
|
|
56
|
+
const [, y, mo, d, h = "0", mi = "0", sec = "0"] = m;
|
|
57
|
+
const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));
|
|
58
|
+
if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {
|
|
59
|
+
throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);
|
|
60
|
+
}
|
|
61
|
+
return local;
|
|
62
|
+
}
|
|
63
|
+
if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return "instant";
|
|
64
|
+
throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);
|
|
65
|
+
}
|
|
66
|
+
function toInstant(value) {
|
|
67
|
+
if (value instanceof Date) return value.toISOString();
|
|
68
|
+
if (kindOf(value) !== "instant") throw new RangeError(`Not an instant: ${value}`);
|
|
69
|
+
return new Date(value).toISOString();
|
|
70
|
+
}
|
|
71
|
+
function wallMillis(wall) {
|
|
72
|
+
const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);
|
|
73
|
+
if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);
|
|
74
|
+
const [, y, mo, d, h = "0", mi = "0", s = "0"] = m;
|
|
75
|
+
return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);
|
|
76
|
+
}
|
|
77
|
+
function formatWall(ms) {
|
|
78
|
+
const d = new Date(ms);
|
|
79
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
80
|
+
}
|
|
81
|
+
function toWall(instant, zone) {
|
|
82
|
+
const at = instant instanceof Date ? instant : new Date(toInstant(instant));
|
|
83
|
+
return formatWall(at.getTime() + zone(at) * 6e4);
|
|
84
|
+
}
|
|
85
|
+
function fromWall(wall, zone) {
|
|
86
|
+
const w = wallMillis(wall);
|
|
87
|
+
const before = zone(new Date(w - 864e5));
|
|
88
|
+
const after = zone(new Date(w + 864e5));
|
|
89
|
+
const fits = (offset) => {
|
|
90
|
+
const t2 = w - offset * 6e4;
|
|
91
|
+
return zone(new Date(t2)) === offset ? t2 : null;
|
|
92
|
+
};
|
|
93
|
+
const candidates = [fits(before), fits(after)].filter((t2) => t2 !== null);
|
|
94
|
+
const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 6e4;
|
|
95
|
+
return new Date(t).toISOString();
|
|
96
|
+
}
|
|
97
|
+
function toDate(value, zone) {
|
|
98
|
+
const k = kindOf(value);
|
|
99
|
+
if (k === "date") return value;
|
|
100
|
+
if (k === "floating") return value.slice(0, 10);
|
|
101
|
+
return toWall(value, zone).slice(0, 10);
|
|
102
|
+
}
|
|
103
|
+
function addDays(date, days) {
|
|
104
|
+
if (kindOf(date) !== "date") throw new RangeError(`addDays needs a date, got ${date}`);
|
|
105
|
+
return formatWall(wallMillis(date) + days * 864e5).slice(0, 10);
|
|
106
|
+
}
|
|
107
|
+
function utcFieldsToWall(d) {
|
|
108
|
+
return formatWall(d.getTime());
|
|
109
|
+
}
|
|
110
|
+
function normaliseFloating(value) {
|
|
111
|
+
return formatWall(wallMillis(value));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/core/colors.ts
|
|
115
|
+
var GROUP_PALETTE = [
|
|
116
|
+
"#0f6cbd",
|
|
117
|
+
// blue
|
|
118
|
+
"#107c10",
|
|
119
|
+
// green
|
|
120
|
+
"#c50f1f",
|
|
121
|
+
// red
|
|
122
|
+
"#8764b8",
|
|
123
|
+
// lavender
|
|
124
|
+
"#ae4b00",
|
|
125
|
+
// dark orange
|
|
126
|
+
"#03787c",
|
|
127
|
+
// teal
|
|
128
|
+
"#4f52b2",
|
|
129
|
+
// indigo
|
|
130
|
+
"#8a5b00",
|
|
131
|
+
// dark gold
|
|
132
|
+
"#c0077d",
|
|
133
|
+
// magenta
|
|
134
|
+
"#5c2e91"
|
|
135
|
+
// purple
|
|
136
|
+
];
|
|
137
|
+
function hash(key) {
|
|
138
|
+
let h = 2166136261;
|
|
139
|
+
for (let i = 0; i < key.length; i++) {
|
|
140
|
+
h ^= key.charCodeAt(i);
|
|
141
|
+
h = Math.imul(h, 16777619);
|
|
142
|
+
}
|
|
143
|
+
return h >>> 0;
|
|
144
|
+
}
|
|
145
|
+
function groupColor(key, palette = GROUP_PALETTE) {
|
|
146
|
+
return palette[hash(key) % palette.length];
|
|
147
|
+
}
|
|
148
|
+
function parseHex(color) {
|
|
149
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());
|
|
150
|
+
if (!m) return null;
|
|
151
|
+
const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
152
|
+
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
153
|
+
}
|
|
154
|
+
function luminance([r, g, b]) {
|
|
155
|
+
const lin = (c) => {
|
|
156
|
+
const s = c / 255;
|
|
157
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
158
|
+
};
|
|
159
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
160
|
+
}
|
|
161
|
+
function contrastRatio(a, b) {
|
|
162
|
+
const pa = parseHex(a);
|
|
163
|
+
const pb = parseHex(b);
|
|
164
|
+
if (!pa || !pb) return null;
|
|
165
|
+
const [hi, lo] = [luminance(pa), luminance(pb)].sort((x, y) => y - x);
|
|
166
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
167
|
+
}
|
|
168
|
+
function readableTextOn(background) {
|
|
169
|
+
const onWhite = contrastRatio(background, "#ffffff");
|
|
170
|
+
const onBlack = contrastRatio(background, "#000000");
|
|
171
|
+
if (onWhite === null || onBlack === null) return void 0;
|
|
172
|
+
return onWhite >= onBlack ? "#ffffff" : "#000000";
|
|
173
|
+
}
|
|
174
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
175
|
+
0 && (module.exports = {
|
|
176
|
+
GROUP_PALETTE,
|
|
177
|
+
addDays,
|
|
178
|
+
browserZone,
|
|
179
|
+
contrastRatio,
|
|
180
|
+
fixedZone,
|
|
181
|
+
fromWall,
|
|
182
|
+
groupColor,
|
|
183
|
+
kindOf,
|
|
184
|
+
normaliseFloating,
|
|
185
|
+
parseHex,
|
|
186
|
+
readableTextOn,
|
|
187
|
+
toDate,
|
|
188
|
+
toInstant,
|
|
189
|
+
toWall,
|
|
190
|
+
utcFieldsToWall
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=core.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/index.ts","../src/core/datetime.ts","../src/core/colors.ts"],"sourcesContent":["/**\n * `@dataverse-kit/calendar-kit/core` — the framework-agnostic layer: the event contract, value\n * kinds and zone conversion, and colours. No React, no FullCalendar, no Fluent, no Dataverse.\n */\nexport type {\n BusinessHours,\n CalendarDataSource,\n CalendarEvent,\n CalendarEventChange,\n CalendarValue,\n SlotSelection,\n ValueKind,\n VisibleRange,\n ZoneOffset,\n} from './types';\nexport {\n addDays,\n browserZone,\n fixedZone,\n fromWall,\n kindOf,\n normaliseFloating,\n toDate,\n toInstant,\n toWall,\n utcFieldsToWall,\n} from './datetime';\nexport { GROUP_PALETTE, contrastRatio, groupColor, parseHex, readableTextOn } from './colors';\n","import type { CalendarValue, ValueKind, ZoneOffset } from './types';\n\n/**\n * Value-kind detection and zone conversion.\n *\n * ★★ Nothing in this file may read the PROCESS time zone. Every `Date` is built and read through\n * its UTC fields, so the same input gives the same output on a laptop in Auckland and a CI\n * runner in UTC. `npm test` runs the suite under three TZ values to prove it — a single-zone\n * run cannot see a leak, because the developer's zone is the one the code was written in.\n */\n\nconst DATE_RE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst FLOATING_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d{1,9})?)?$/;\nconst INSTANT_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})$/i;\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n\n/** The browser's own zone, east-positive. The default when a host supplies none. */\nexport const browserZone: ZoneOffset = (instant) => -instant.getTimezoneOffset();\n\n/** A fixed zone (no daylight saving). Mostly for tests and demos. */\nexport const fixedZone =\n (minutesEast: number): ZoneOffset =>\n () =>\n minutesEast;\n\nexport function kindOf(value: CalendarValue | Date): ValueKind {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) throw new RangeError('Invalid Date');\n return 'instant';\n }\n const local = DATE_RE.test(value) ? 'date' : FLOATING_RE.test(value) ? 'floating' : null;\n if (local) {\n // ★ The regex accepts `2026-02-31`; Date.UTC would silently roll it to March 3rd. Reject\n // any date whose fields do not survive a round trip.\n const m = (FLOATING_RE.exec(value) ?? DATE_RE.exec(value))!;\n const [, y, mo, d, h = '0', mi = '0', sec = '0'] = m;\n const t = new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec));\n if (t.getUTCFullYear() !== +y || t.getUTCMonth() !== +mo - 1 || t.getUTCDate() !== +d || +h > 23 || +mi > 59 || +sec > 59) {\n throw new RangeError(`Not a real calendar date or time: ${JSON.stringify(value)}`);\n }\n return local;\n }\n if (INSTANT_RE.test(value) && !Number.isNaN(new Date(value).getTime())) return 'instant';\n throw new RangeError(`Not an ISO-8601 date, wall-clock time or instant: ${JSON.stringify(value)}`);\n}\n\n/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */\nexport function toInstant(value: CalendarValue | Date): CalendarValue {\n if (value instanceof Date) return value.toISOString();\n if (kindOf(value) !== 'instant') throw new RangeError(`Not an instant: ${value}`);\n return new Date(value).toISOString();\n}\n\n/** `Date.UTC` from wall-clock fields — i.e. the wall clock treated AS IF it were UTC. */\nfunction wallMillis(wall: string): number {\n const m = FLOATING_RE.exec(wall) ?? DATE_RE.exec(wall);\n if (!m) throw new RangeError(`Not a date or wall-clock time: ${wall}`);\n const [, y, mo, d, h = '0', mi = '0', s = '0'] = m;\n return Date.UTC(+y, +mo - 1, +d, +h, +mi, +s);\n}\n\nfunction formatWall(ms: number): string {\n const d = new Date(ms);\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +\n `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`\n );\n}\n\n/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */\nexport function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const at = instant instanceof Date ? instant : new Date(toInstant(instant));\n return formatWall(at.getTime() + zone(at) * 60_000);\n}\n\n/**\n * The instant at which `zone`'s clocks read `wall`.\n *\n * The offset depends on the instant being solved for, so this takes the offset in force a day\n * BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:\n * - an ordinary time: both give the same instant;\n * - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;\n * - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the\n * pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS\n * `Date` does. What Dataverse does with one is UNMEASURED.\n *\n * ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and\n * the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the\n * first guess happened to land in. Tests now pin both hemispheres with synthetic zones.\n */\nexport function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue {\n const w = wallMillis(wall);\n const before = zone(new Date(w - 86_400_000));\n const after = zone(new Date(w + 86_400_000));\n const fits = (offset: number): number | null => {\n const t = w - offset * 60_000;\n return zone(new Date(t)) === offset ? t : null;\n };\n const candidates = [fits(before), fits(after)].filter((t): t is number => t !== null);\n const t = candidates.length > 0 ? Math.min(...candidates) : w - before * 60_000;\n return new Date(t).toISOString();\n}\n\n/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */\nexport function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue {\n const k = kindOf(value);\n if (k === 'date') return value as string;\n if (k === 'floating') return (value as string).slice(0, 10);\n return toWall(value, zone).slice(0, 10);\n}\n\n/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */\nexport function addDays(date: CalendarValue, days: number): CalendarValue {\n if (kindOf(date) !== 'date') throw new RangeError(`addDays needs a date, got ${date}`);\n return formatWall(wallMillis(date) + days * 86_400_000).slice(0, 10);\n}\n\n/**\n * The UTC fields of a `Date` as floating text. For reading a `Date` that some library has\n * produced in a \"UTC-as-wall-clock\" convention (FullCalendar with `timeZone: 'UTC'`).\n */\nexport function utcFieldsToWall(d: Date): CalendarValue {\n return formatWall(d.getTime());\n}\n\n/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */\nexport function normaliseFloating(value: CalendarValue): CalendarValue {\n return formatWall(wallMillis(value));\n}\n","/**\n * Event colours.\n *\n * ★ Without FullCalendar Premium there are no resource lanes, so \"appointments by location\"\n * is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour\n * on every load and every machine), which is what lets a person learn the legend.\n * ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a\n * test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their\n * text colour from `readableTextOn` instead of assuming white; a well-known client control\n * hard-coded white on yellow, which is ~1.4:1.\n */\nexport const GROUP_PALETTE: readonly string[] = [\n '#0f6cbd', // blue\n '#107c10', // green\n '#c50f1f', // red\n '#8764b8', // lavender\n '#ae4b00', // dark orange\n '#03787c', // teal\n '#4f52b2', // indigo\n '#8a5b00', // dark gold\n '#c0077d', // magenta\n '#5c2e91', // purple\n];\n\n/** FNV-1a — tiny, stable across engines, good enough spread for a 10-colour palette. */\nfunction hash(key: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\nexport function groupColor(key: string, palette: readonly string[] = GROUP_PALETTE): string {\n return palette[hash(key) % palette.length];\n}\n\n/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */\nexport function parseHex(color: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());\n if (!m) return null;\n const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];\n}\n\nfunction luminance([r, g, b]: [number, number, number]): number {\n const lin = (c: number): number => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\n\n/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */\nexport function contrastRatio(a: string, b: string): number | null {\n const pa = parseHex(a);\n const pb = parseHex(b);\n if (!pa || !pb) return null;\n const [hi, lo] = [luminance(pa), luminance(pb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/**\n * Black or white text, whichever reads better on `background`. Unparseable backgrounds\n * (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.\n */\nexport function readableTextOn(background: string): '#000000' | '#ffffff' | undefined {\n const onWhite = contrastRatio(background, '#ffffff');\n const onBlack = contrastRatio(background, '#000000');\n if (onWhite === null || onBlack === null) return undefined;\n return onWhite >= onBlack ? '#ffffff' : '#000000';\n}\n"],"mappings":"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","names":["t"]}
|
package/dist/core.d.cts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { c as CalendarValue, Z as ZoneOffset, V as ValueKind } from './types-u3Gpxk53.cjs';
|
|
2
|
+
export { B as BusinessHours, C as CalendarDataSource, a as CalendarEvent, b as CalendarEventChange, S as SlotSelection, d as VisibleRange } from './types-u3Gpxk53.cjs';
|
|
3
|
+
|
|
4
|
+
/** The browser's own zone, east-positive. The default when a host supplies none. */
|
|
5
|
+
declare const browserZone: ZoneOffset;
|
|
6
|
+
/** A fixed zone (no daylight saving). Mostly for tests and demos. */
|
|
7
|
+
declare const fixedZone: (minutesEast: number) => ZoneOffset;
|
|
8
|
+
declare function kindOf(value: CalendarValue | Date): ValueKind;
|
|
9
|
+
/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */
|
|
10
|
+
declare function toInstant(value: CalendarValue | Date): CalendarValue;
|
|
11
|
+
/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */
|
|
12
|
+
declare function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue;
|
|
13
|
+
/**
|
|
14
|
+
* The instant at which `zone`'s clocks read `wall`.
|
|
15
|
+
*
|
|
16
|
+
* The offset depends on the instant being solved for, so this takes the offset in force a day
|
|
17
|
+
* BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:
|
|
18
|
+
* - an ordinary time: both give the same instant;
|
|
19
|
+
* - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;
|
|
20
|
+
* - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the
|
|
21
|
+
* pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS
|
|
22
|
+
* `Date` does. What Dataverse does with one is UNMEASURED.
|
|
23
|
+
*
|
|
24
|
+
* ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and
|
|
25
|
+
* the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the
|
|
26
|
+
* first guess happened to land in. Tests now pin both hemispheres with synthetic zones.
|
|
27
|
+
*/
|
|
28
|
+
declare function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue;
|
|
29
|
+
/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */
|
|
30
|
+
declare function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue;
|
|
31
|
+
/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */
|
|
32
|
+
declare function addDays(date: CalendarValue, days: number): CalendarValue;
|
|
33
|
+
/**
|
|
34
|
+
* The UTC fields of a `Date` as floating text. For reading a `Date` that some library has
|
|
35
|
+
* produced in a "UTC-as-wall-clock" convention (FullCalendar with `timeZone: 'UTC'`).
|
|
36
|
+
*/
|
|
37
|
+
declare function utcFieldsToWall(d: Date): CalendarValue;
|
|
38
|
+
/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */
|
|
39
|
+
declare function normaliseFloating(value: CalendarValue): CalendarValue;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Event colours.
|
|
43
|
+
*
|
|
44
|
+
* ★ Without FullCalendar Premium there are no resource lanes, so "appointments by location"
|
|
45
|
+
* is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour
|
|
46
|
+
* on every load and every machine), which is what lets a person learn the legend.
|
|
47
|
+
* ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a
|
|
48
|
+
* test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their
|
|
49
|
+
* text colour from `readableTextOn` instead of assuming white; a well-known client control
|
|
50
|
+
* hard-coded white on yellow, which is ~1.4:1.
|
|
51
|
+
*/
|
|
52
|
+
declare const GROUP_PALETTE: readonly string[];
|
|
53
|
+
declare function groupColor(key: string, palette?: readonly string[]): string;
|
|
54
|
+
/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */
|
|
55
|
+
declare function parseHex(color: string): [number, number, number] | null;
|
|
56
|
+
/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */
|
|
57
|
+
declare function contrastRatio(a: string, b: string): number | null;
|
|
58
|
+
/**
|
|
59
|
+
* Black or white text, whichever reads better on `background`. Unparseable backgrounds
|
|
60
|
+
* (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.
|
|
61
|
+
*/
|
|
62
|
+
declare function readableTextOn(background: string): '#000000' | '#ffffff' | undefined;
|
|
63
|
+
|
|
64
|
+
export { CalendarValue, GROUP_PALETTE, ValueKind, ZoneOffset, addDays, browserZone, contrastRatio, fixedZone, fromWall, groupColor, kindOf, normaliseFloating, parseHex, readableTextOn, toDate, toInstant, toWall, utcFieldsToWall };
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { c as CalendarValue, Z as ZoneOffset, V as ValueKind } from './types-u3Gpxk53.js';
|
|
2
|
+
export { B as BusinessHours, C as CalendarDataSource, a as CalendarEvent, b as CalendarEventChange, S as SlotSelection, d as VisibleRange } from './types-u3Gpxk53.js';
|
|
3
|
+
|
|
4
|
+
/** The browser's own zone, east-positive. The default when a host supplies none. */
|
|
5
|
+
declare const browserZone: ZoneOffset;
|
|
6
|
+
/** A fixed zone (no daylight saving). Mostly for tests and demos. */
|
|
7
|
+
declare const fixedZone: (minutesEast: number) => ZoneOffset;
|
|
8
|
+
declare function kindOf(value: CalendarValue | Date): ValueKind;
|
|
9
|
+
/** An instant as canonical UTC ISO text (`…T…Z`, milliseconds kept). */
|
|
10
|
+
declare function toInstant(value: CalendarValue | Date): CalendarValue;
|
|
11
|
+
/** The wall-clock reading of an instant in `zone`, as floating text (`YYYY-MM-DDTHH:mm:ss`). */
|
|
12
|
+
declare function toWall(instant: CalendarValue | Date, zone: ZoneOffset): CalendarValue;
|
|
13
|
+
/**
|
|
14
|
+
* The instant at which `zone`'s clocks read `wall`.
|
|
15
|
+
*
|
|
16
|
+
* The offset depends on the instant being solved for, so this takes the offset in force a day
|
|
17
|
+
* BEFORE and a day AFTER the wall reading (no zone changes offset twice in 48 h) and tries both:
|
|
18
|
+
* - an ordinary time: both give the same instant;
|
|
19
|
+
* - a wall time that exists TWICE (fall-back): both round-trip, and the EARLIER is returned;
|
|
20
|
+
* - a wall time that DOES NOT EXIST (spring-forward gap): neither round-trips, and the
|
|
21
|
+
* pre-transition offset is used, which pushes it forward by the gap (02:30 → 03:30), as JS
|
|
22
|
+
* `Date` does. What Dataverse does with one is UNMEASURED.
|
|
23
|
+
*
|
|
24
|
+
* ★ A reviewer caught the first version returning the LATER occurrence east of UTC (Berlin) and
|
|
25
|
+
* the earlier one west of it (Pacific): the two-pass guess settled on whichever offset the
|
|
26
|
+
* first guess happened to land in. Tests now pin both hemispheres with synthetic zones.
|
|
27
|
+
*/
|
|
28
|
+
declare function fromWall(wall: CalendarValue, zone: ZoneOffset): CalendarValue;
|
|
29
|
+
/** The calendar day (`YYYY-MM-DD`) of any value, in `zone` for instants. */
|
|
30
|
+
declare function toDate(value: CalendarValue | Date, zone: ZoneOffset): CalendarValue;
|
|
31
|
+
/** Calendar-day arithmetic on a `YYYY-MM-DD` value (no zone involved). */
|
|
32
|
+
declare function addDays(date: CalendarValue, days: number): CalendarValue;
|
|
33
|
+
/**
|
|
34
|
+
* The UTC fields of a `Date` as floating text. For reading a `Date` that some library has
|
|
35
|
+
* produced in a "UTC-as-wall-clock" convention (FullCalendar with `timeZone: 'UTC'`).
|
|
36
|
+
*/
|
|
37
|
+
declare function utcFieldsToWall(d: Date): CalendarValue;
|
|
38
|
+
/** Normalise floating text to seconds precision (`…T09:00` → `…T09:00:00`). */
|
|
39
|
+
declare function normaliseFloating(value: CalendarValue): CalendarValue;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Event colours.
|
|
43
|
+
*
|
|
44
|
+
* ★ Without FullCalendar Premium there are no resource lanes, so "appointments by location"
|
|
45
|
+
* is expressed as ONE COLOUR PER GROUP. `groupColor` is deterministic (same key → same colour
|
|
46
|
+
* on every load and every machine), which is what lets a person learn the legend.
|
|
47
|
+
* ★ Every palette entry carries white text at >= 4.5:1 (WCAG AA, normal text) — asserted by a
|
|
48
|
+
* test, not by eye. Host-supplied colours (Dataverse option colours are often pale) get their
|
|
49
|
+
* text colour from `readableTextOn` instead of assuming white; a well-known client control
|
|
50
|
+
* hard-coded white on yellow, which is ~1.4:1.
|
|
51
|
+
*/
|
|
52
|
+
declare const GROUP_PALETTE: readonly string[];
|
|
53
|
+
declare function groupColor(key: string, palette?: readonly string[]): string;
|
|
54
|
+
/** `#rgb` / `#rrggbb` → [r, g, b] 0-255, or null for anything else (named colours, var()). */
|
|
55
|
+
declare function parseHex(color: string): [number, number, number] | null;
|
|
56
|
+
/** WCAG contrast ratio between two hex colours, or null if either cannot be parsed. */
|
|
57
|
+
declare function contrastRatio(a: string, b: string): number | null;
|
|
58
|
+
/**
|
|
59
|
+
* Black or white text, whichever reads better on `background`. Unparseable backgrounds
|
|
60
|
+
* (a CSS variable, a named colour) get `undefined`, i.e. the theme's own event text colour.
|
|
61
|
+
*/
|
|
62
|
+
declare function readableTextOn(background: string): '#000000' | '#ffffff' | undefined;
|
|
63
|
+
|
|
64
|
+
export { CalendarValue, GROUP_PALETTE, ValueKind, ZoneOffset, addDays, browserZone, contrastRatio, fixedZone, fromWall, groupColor, kindOf, normaliseFloating, parseHex, readableTextOn, toDate, toInstant, toWall, utcFieldsToWall };
|