@particle-academy/react-fancy 5.55.0 → 5.57.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/CHANGELOG.md +63 -0
- package/dist/calendar.cjs +2 -2
- package/dist/calendar.d.cts +30 -1
- package/dist/calendar.d.ts +30 -1
- package/dist/calendar.js +1 -1
- package/dist/{chunk-XO4UWQCU.cjs → chunk-4DL2U23D.cjs} +233 -77
- package/dist/chunk-4DL2U23D.cjs.map +1 -0
- package/dist/{chunk-UET6D7B3.cjs → chunk-CQ7QXZLR.cjs} +45 -26
- package/dist/chunk-CQ7QXZLR.cjs.map +1 -0
- package/dist/{chunk-SFJ5WOX5.js → chunk-MLZGGAXQ.js} +233 -77
- package/dist/chunk-MLZGGAXQ.js.map +1 -0
- package/dist/{chunk-HVND2IIB.js → chunk-X2A3NAJW.js} +45 -26
- package/dist/chunk-X2A3NAJW.js.map +1 -0
- package/dist/index.cjs +4 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/time-grid.cjs +2 -2
- package/dist/time-grid.d.cts +8 -0
- package/dist/time-grid.d.ts +8 -0
- package/dist/time-grid.js +1 -1
- package/docs/Calendar.md +37 -0
- package/package.json +1 -1
- package/dist/chunk-HVND2IIB.js.map +0 -1
- package/dist/chunk-SFJ5WOX5.js.map +0 -1
- package/dist/chunk-UET6D7B3.cjs.map +0 -1
- package/dist/chunk-XO4UWQCU.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.57.0] - 2026-10-22
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`TimeGrid` column windowing (typed grid).** `windowColumns` renders only
|
|
19
|
+
the columns in view plus overscan. It is on by default above 500 columns,
|
|
20
|
+
and `windowColumns` forces it on or off. **Nothing to do for an existing
|
|
21
|
+
grid:** at 500 columns or fewer nothing changes unless you ask, and a
|
|
22
|
+
boolean grid (no `cells`) is never windowed.
|
|
23
|
+
- Stable handles and selection still cover every cell. Ids and the cells
|
|
24
|
+
passed to `onSelectionChange` come from `cells`, not the DOM, so a
|
|
25
|
+
selection across unrendered columns reports all of them.
|
|
26
|
+
- Keyboard focus moved past the window, or a `selection` set from outside
|
|
27
|
+
(a host, an agent through a bridge) that is out of view, scrolls it into
|
|
28
|
+
view. Windowed cells carry `aria-colindex`.
|
|
29
|
+
- The column tracks stay the full grid while scrolling. A jump is rendered
|
|
30
|
+
before the next paint, so the viewport never flashes blank. On the server
|
|
31
|
+
a windowed grid renders its frame with no cells.
|
|
32
|
+
- Measured in Chrome (Playwright, CDP 4x CPU throttle, 240 columns, median
|
|
33
|
+
of 7 page loads, 2026-10-22). Mount, 5.56.0 against 5.57.0 windowed:
|
|
34
|
+
|
|
35
|
+
| container | 7 days | 14 days | 31 days |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| 800px | 182 → 119 ms | 329 → 209 ms | 624 → 370 ms |
|
|
38
|
+
| 1280px | 139 → 131 ms | 286 → 263 ms | 632 → 524 ms |
|
|
39
|
+
|
|
40
|
+
The gain depends on how much of the grid is off screen: at 1280px the
|
|
41
|
+
window still holds 208 of 240 columns. 14 days in 1280px still misses a
|
|
42
|
+
200 ms budget.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Typed `TimeGrid` cells render in memoised 16-column chunks, so a selection
|
|
47
|
+
change re-renders only the chunks it touches. Unwindowed 31 days mounts in
|
|
48
|
+
545 ms against 632 ms in 5.56.0 (1280px, same setup as above).
|
|
49
|
+
|
|
50
|
+
## [5.56.0] - 2026-10-21
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`Calendar` per-day hooks and a controlled visible month.** All additive:
|
|
55
|
+
**nothing to do** for an existing Calendar, which renders and behaves as
|
|
56
|
+
before, plus new data attributes.
|
|
57
|
+
- Every day cell carries a stable id, `data-react-fancy-calendar-day`,
|
|
58
|
+
`YYYY-MM-DD` in local time by default (`dateId` replaces it). Leading and
|
|
59
|
+
trailing days carry `data-outside-month`; the heading carries
|
|
60
|
+
`data-react-fancy-calendar-month`.
|
|
61
|
+
- `renderDay(ctx)` draws a day's content, `dayClassName(ctx)` adds classes
|
|
62
|
+
to one day, `dayAriaLabel(ctx)` sets its accessible name. `ctx` is
|
|
63
|
+
`{ date, id, selected, disabled, inRange, outsideMonth, today }`, exported
|
|
64
|
+
as `CalendarDayContext`. A day shaded with `dayClassName` should get a
|
|
65
|
+
`dayAriaLabel` too: colour alone does not reach a screen reader.
|
|
66
|
+
- `visibleMonth` / `onVisibleMonthChange`: the shown month is controlled when
|
|
67
|
+
`visibleMonth` is given (the chevrons propose, the host decides);
|
|
68
|
+
uncontrolled it starts at today as before, and still reports each move.
|
|
69
|
+
The callback gets the 1st of the month.
|
|
70
|
+
- Opening a day on click is the existing `onChange` (single mode).
|
|
71
|
+
- Verified in Chrome in light, `.dark` and `[data-theme="dark"]`.
|
|
72
|
+
|
|
73
|
+
The requester's wording was `month` / `onMonthChange`. The names here follow
|
|
74
|
+
the spec (`visibleMonth` / `onVisibleMonthChange`), because `value` is already
|
|
75
|
+
a date and a bare `month` reads as the selected one.
|
|
76
|
+
|
|
14
77
|
## [5.55.0] - 2026-10-21
|
|
15
78
|
|
|
16
79
|
### Added
|
package/dist/calendar.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkCQ7QXZLR_cjs = require('./chunk-CQ7QXZLR.cjs');
|
|
4
4
|
require('./chunk-ZU5Q77OT.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "Calendar", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkCQ7QXZLR_cjs.Calendar; }
|
|
11
11
|
});
|
|
12
12
|
//# sourceMappingURL=calendar.cjs.map
|
|
13
13
|
//# sourceMappingURL=calendar.cjs.map
|
package/dist/calendar.d.cts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
type CalendarMode = "single" | "range" | "multiple";
|
|
4
5
|
interface DateRange {
|
|
5
6
|
start: Date | null;
|
|
6
7
|
end: Date | null;
|
|
7
8
|
}
|
|
9
|
+
/** Everything a per-day hook needs to know about one day cell. */
|
|
10
|
+
interface CalendarDayContext {
|
|
11
|
+
date: Date;
|
|
12
|
+
/** Stable id: `dateId(date)`, default `YYYY-MM-DD` in local time. Also on the cell as `data-react-fancy-calendar-day`. */
|
|
13
|
+
id: string;
|
|
14
|
+
selected: boolean;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
/** Strictly between a range's ends (range mode). */
|
|
17
|
+
inRange: boolean;
|
|
18
|
+
/** A leading/trailing day from the neighbouring month. */
|
|
19
|
+
outsideMonth: boolean;
|
|
20
|
+
today: boolean;
|
|
21
|
+
}
|
|
8
22
|
interface CalendarProps {
|
|
9
23
|
mode?: CalendarMode;
|
|
10
24
|
value?: Date | Date[] | DateRange | null;
|
|
@@ -13,8 +27,23 @@ interface CalendarProps {
|
|
|
13
27
|
maxDate?: Date;
|
|
14
28
|
disabledDates?: Date[];
|
|
15
29
|
className?: string;
|
|
30
|
+
/** Stable id per day. Default `YYYY-MM-DD` in local time. */
|
|
31
|
+
dateId?: (date: Date) => string;
|
|
32
|
+
/** The day cell's content. Default: the day number. The cell (button, click, state classes) stays the Calendar's. */
|
|
33
|
+
renderDay?: (ctx: CalendarDayContext) => ReactNode;
|
|
34
|
+
/** Extra classes for one day cell, merged after the built-in ones. */
|
|
35
|
+
dayClassName?: (ctx: CalendarDayContext) => string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The day cell's accessible name. Pair it with `dayClassName`: a shaded day
|
|
38
|
+
* must say what the shade means. It REPLACES the name, so include the date.
|
|
39
|
+
*/
|
|
40
|
+
dayAriaLabel?: (ctx: CalendarDayContext) => string | undefined;
|
|
41
|
+
/** Controlled visible month (any day in it). Uncontrolled when omitted; starts at today. */
|
|
42
|
+
visibleMonth?: Date;
|
|
43
|
+
/** Called with the 1st of the month navigated to, controlled or not. */
|
|
44
|
+
onVisibleMonthChange?: (month: Date) => void;
|
|
16
45
|
}
|
|
17
46
|
|
|
18
47
|
declare const Calendar: react.ForwardRefExoticComponent<CalendarProps & react.RefAttributes<HTMLDivElement>>;
|
|
19
48
|
|
|
20
|
-
export { Calendar, type CalendarMode, type CalendarProps, type DateRange };
|
|
49
|
+
export { Calendar, type CalendarDayContext, type CalendarMode, type CalendarProps, type DateRange };
|
package/dist/calendar.d.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
type CalendarMode = "single" | "range" | "multiple";
|
|
4
5
|
interface DateRange {
|
|
5
6
|
start: Date | null;
|
|
6
7
|
end: Date | null;
|
|
7
8
|
}
|
|
9
|
+
/** Everything a per-day hook needs to know about one day cell. */
|
|
10
|
+
interface CalendarDayContext {
|
|
11
|
+
date: Date;
|
|
12
|
+
/** Stable id: `dateId(date)`, default `YYYY-MM-DD` in local time. Also on the cell as `data-react-fancy-calendar-day`. */
|
|
13
|
+
id: string;
|
|
14
|
+
selected: boolean;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
/** Strictly between a range's ends (range mode). */
|
|
17
|
+
inRange: boolean;
|
|
18
|
+
/** A leading/trailing day from the neighbouring month. */
|
|
19
|
+
outsideMonth: boolean;
|
|
20
|
+
today: boolean;
|
|
21
|
+
}
|
|
8
22
|
interface CalendarProps {
|
|
9
23
|
mode?: CalendarMode;
|
|
10
24
|
value?: Date | Date[] | DateRange | null;
|
|
@@ -13,8 +27,23 @@ interface CalendarProps {
|
|
|
13
27
|
maxDate?: Date;
|
|
14
28
|
disabledDates?: Date[];
|
|
15
29
|
className?: string;
|
|
30
|
+
/** Stable id per day. Default `YYYY-MM-DD` in local time. */
|
|
31
|
+
dateId?: (date: Date) => string;
|
|
32
|
+
/** The day cell's content. Default: the day number. The cell (button, click, state classes) stays the Calendar's. */
|
|
33
|
+
renderDay?: (ctx: CalendarDayContext) => ReactNode;
|
|
34
|
+
/** Extra classes for one day cell, merged after the built-in ones. */
|
|
35
|
+
dayClassName?: (ctx: CalendarDayContext) => string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The day cell's accessible name. Pair it with `dayClassName`: a shaded day
|
|
38
|
+
* must say what the shade means. It REPLACES the name, so include the date.
|
|
39
|
+
*/
|
|
40
|
+
dayAriaLabel?: (ctx: CalendarDayContext) => string | undefined;
|
|
41
|
+
/** Controlled visible month (any day in it). Uncontrolled when omitted; starts at today. */
|
|
42
|
+
visibleMonth?: Date;
|
|
43
|
+
/** Called with the 1st of the month navigated to, controlled or not. */
|
|
44
|
+
onVisibleMonthChange?: (month: Date) => void;
|
|
16
45
|
}
|
|
17
46
|
|
|
18
47
|
declare const Calendar: react.ForwardRefExoticComponent<CalendarProps & react.RefAttributes<HTMLDivElement>>;
|
|
19
48
|
|
|
20
|
-
export { Calendar, type CalendarMode, type CalendarProps, type DateRange };
|
|
49
|
+
export { Calendar, type CalendarDayContext, type CalendarMode, type CalendarProps, type DateRange };
|
package/dist/calendar.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var chunkI27K6NLX_cjs = require('./chunk-I27K6NLX.cjs');
|
|
4
4
|
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
5
5
|
var react = require('react');
|
|
6
|
+
var reactDom = require('react-dom');
|
|
6
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
8
|
|
|
8
9
|
// src/components/TimeGrid/TimeGrid.selection.ts
|
|
@@ -109,7 +110,7 @@ var TONE_FILL = {
|
|
|
109
110
|
zinc: "bg-zinc-500"
|
|
110
111
|
};
|
|
111
112
|
var NEUTRAL_FILL = "bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] dark:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))]";
|
|
112
|
-
var CELL_BASE = "relative m-px rounded-sm";
|
|
113
|
+
var CELL_BASE = "relative m-px h-[var(--tg-cell-h)] w-[var(--tg-cell-w)] rounded-sm";
|
|
113
114
|
var CELL_ENABLED = "cursor-pointer hover:brightness-90 dark:hover:brightness-125";
|
|
114
115
|
var CELL_DISABLED = "cursor-not-allowed opacity-40";
|
|
115
116
|
var CELL_SELECTED = "after:pointer-events-none after:absolute after:inset-0 after:rounded-sm after:bg-[color:var(--fancy-focus,var(--color-blue-500))]/50 dark:after:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))]/50";
|
|
@@ -127,6 +128,11 @@ function isDev() {
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
var EMPTY = {};
|
|
131
|
+
var WINDOW_DEFAULT_ABOVE = 500;
|
|
132
|
+
var WINDOW_MIN_OVERSCAN = 16;
|
|
133
|
+
var CHUNK = 16;
|
|
134
|
+
var WINDOW_CHUNK = CHUNK;
|
|
135
|
+
var ROW_START = { gridColumnStart: 1 };
|
|
130
136
|
var warnedStates = /* @__PURE__ */ new Set();
|
|
131
137
|
var warnedNoStates = false;
|
|
132
138
|
var cellDomId = (baseId, r, c) => `${baseId}-${r}-${c}`;
|
|
@@ -143,23 +149,71 @@ var TypedRow = react.memo(function TypedRow2({
|
|
|
143
149
|
selStart,
|
|
144
150
|
selEnd,
|
|
145
151
|
activeCol,
|
|
146
|
-
onRowHeader
|
|
152
|
+
onRowHeader,
|
|
153
|
+
colStart,
|
|
154
|
+
colEnd,
|
|
155
|
+
windowed
|
|
147
156
|
}) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
const out = [];
|
|
158
|
+
out.push(
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
role: "rowheader",
|
|
163
|
+
"data-react-fancy-timegrid-row": r,
|
|
164
|
+
style: windowed ? ROW_START : void 0,
|
|
165
|
+
onClick: onRowHeader ? () => onRowHeader(r) : void 0,
|
|
166
|
+
className: onRowHeader ? `${HEADER_ROW} ${HEADER_HOVER}` : HEADER_ROW,
|
|
167
|
+
children: ctx.rows[r]
|
|
168
|
+
},
|
|
169
|
+
"h"
|
|
170
|
+
)
|
|
161
171
|
);
|
|
162
|
-
|
|
172
|
+
if (colEnd >= colStart) {
|
|
173
|
+
const k0 = Math.floor(colStart / CHUNK);
|
|
174
|
+
const k1 = Math.floor(colEnd / CHUNK);
|
|
175
|
+
for (let k = k0; k <= k1; k++) {
|
|
176
|
+
const from = Math.max(k * CHUNK, colStart);
|
|
177
|
+
const to = Math.min(k * CHUNK + CHUNK - 1, colEnd);
|
|
178
|
+
const overlaps = selEnd >= from && selStart <= to;
|
|
179
|
+
out.push(
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
181
|
+
CellChunk,
|
|
182
|
+
{
|
|
183
|
+
ctx,
|
|
184
|
+
r,
|
|
185
|
+
rowCells,
|
|
186
|
+
from,
|
|
187
|
+
to,
|
|
188
|
+
selStart: overlaps ? Math.max(selStart, from) : -1,
|
|
189
|
+
selEnd: overlaps ? Math.min(selEnd, to) : -2,
|
|
190
|
+
activeCol: activeCol >= from && activeCol <= to ? activeCol : -1,
|
|
191
|
+
place: windowed && from === colStart,
|
|
192
|
+
windowed
|
|
193
|
+
},
|
|
194
|
+
k
|
|
195
|
+
)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "row", className: "contents", children: out });
|
|
200
|
+
});
|
|
201
|
+
var CellChunk = react.memo(function CellChunk2({
|
|
202
|
+
ctx,
|
|
203
|
+
r,
|
|
204
|
+
rowCells,
|
|
205
|
+
from,
|
|
206
|
+
to,
|
|
207
|
+
selStart,
|
|
208
|
+
selEnd,
|
|
209
|
+
activeCol,
|
|
210
|
+
place,
|
|
211
|
+
windowed
|
|
212
|
+
}) {
|
|
213
|
+
const { baseId, rows, cols, states, marks, stateClass } = ctx;
|
|
214
|
+
const { cellId, ariaCell } = ctx.fns.current;
|
|
215
|
+
const out = [];
|
|
216
|
+
for (let c = from; c <= to; c++) {
|
|
163
217
|
const cell = rowCells?.[c];
|
|
164
218
|
const state = cell?.state ?? "";
|
|
165
219
|
const selected = c >= selStart && c <= selEnd;
|
|
@@ -175,37 +229,40 @@ var TypedRow = react.memo(function TypedRow2({
|
|
|
175
229
|
let cls = stateClass(state) + " " + (cell?.disabled ? CELL_DISABLED : CELL_ENABLED);
|
|
176
230
|
if (selected) cls += " " + CELL_SELECTED;
|
|
177
231
|
if (c === activeCol) cls += " " + CELL_ACTIVE;
|
|
178
|
-
out
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{
|
|
198
|
-
"
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
232
|
+
out.push(
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
234
|
+
"div",
|
|
235
|
+
{
|
|
236
|
+
id: cellDomId(baseId, r, c),
|
|
237
|
+
role: "gridcell",
|
|
238
|
+
"aria-colindex": windowed ? c + 2 : void 0,
|
|
239
|
+
style: place && c === from ? { gridColumnStart: c + 2 } : void 0,
|
|
240
|
+
"data-react-fancy-timegrid-cell": cellId ? cellId(r, c) : `${r}:${c}`,
|
|
241
|
+
"data-state": state,
|
|
242
|
+
"data-marks": markIds?.length ? markIds.join(" ") : void 0,
|
|
243
|
+
"aria-selected": selected,
|
|
244
|
+
"aria-disabled": cell?.disabled ? true : void 0,
|
|
245
|
+
"aria-label": label,
|
|
246
|
+
"aria-description": reasons,
|
|
247
|
+
title: reasons,
|
|
248
|
+
className: cls,
|
|
249
|
+
children: markIds?.length ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute right-0 top-0 flex gap-px", children: markIds.map((m) => {
|
|
250
|
+
const def = marks[m];
|
|
251
|
+
return def?.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "data-react-fancy-timegrid-mark": m, className: "flex h-2.5 w-2.5 items-center justify-center [&>svg]:h-full [&>svg]:w-full", children: def.icon }, m) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
252
|
+
"span",
|
|
253
|
+
{
|
|
254
|
+
"data-react-fancy-timegrid-mark": m,
|
|
255
|
+
className: `${MARK_DOT} ${def?.tone ? TONE_FILL[def.tone] : NEUTRAL_FILL}`
|
|
256
|
+
},
|
|
257
|
+
m
|
|
258
|
+
);
|
|
259
|
+
}) }) : null
|
|
260
|
+
},
|
|
261
|
+
c
|
|
262
|
+
)
|
|
206
263
|
);
|
|
207
264
|
}
|
|
208
|
-
return
|
|
265
|
+
return out;
|
|
209
266
|
});
|
|
210
267
|
var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
211
268
|
rows,
|
|
@@ -227,6 +284,7 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
227
284
|
disabledRange = "clamp",
|
|
228
285
|
onBlockedSelect,
|
|
229
286
|
onCellHover,
|
|
287
|
+
windowColumns,
|
|
230
288
|
"aria-label": ariaLabel
|
|
231
289
|
}, ref) {
|
|
232
290
|
if (!statesProp) {
|
|
@@ -254,6 +312,64 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
254
312
|
const anchorRef = react.useRef(null);
|
|
255
313
|
const gestureRef = react.useRef(null);
|
|
256
314
|
const hoverRef = react.useRef("");
|
|
315
|
+
const pitchX = cellWidth + 2;
|
|
316
|
+
const windowed = windowColumns ?? nCols > WINDOW_DEFAULT_ABOVE;
|
|
317
|
+
const metrics = react.useRef({ left: 0, width: 0, headerW: 0 });
|
|
318
|
+
const [win, setWin] = react.useState(null);
|
|
319
|
+
const visibleRange = react.useCallback(() => {
|
|
320
|
+
const { left, width, headerW } = metrics.current;
|
|
321
|
+
const first = Math.max(0, Math.floor((left - headerW) / pitchX));
|
|
322
|
+
const last = Math.min(nCols - 1, Math.max(first, Math.ceil((left + width - headerW) / pitchX)));
|
|
323
|
+
return [first, last];
|
|
324
|
+
}, [pitchX, nCols]);
|
|
325
|
+
const measure = react.useCallback(() => {
|
|
326
|
+
const el = outerRef.current;
|
|
327
|
+
if (!el) return;
|
|
328
|
+
const rh = el.querySelector('[role="rowheader"]');
|
|
329
|
+
metrics.current = {
|
|
330
|
+
left: el.scrollLeft,
|
|
331
|
+
width: el.clientWidth || (typeof window !== "undefined" ? window.innerWidth : 1024),
|
|
332
|
+
headerW: rh ? rh.offsetWidth + 2 : 0
|
|
333
|
+
};
|
|
334
|
+
const [first, last] = visibleRange();
|
|
335
|
+
const over = Math.max(WINDOW_MIN_OVERSCAN, Math.ceil((last - first) / 4));
|
|
336
|
+
const start = Math.max(0, Math.floor((first - over) / WINDOW_CHUNK) * WINDOW_CHUNK);
|
|
337
|
+
const end = Math.min(nCols - 1, Math.ceil((last + over + 1) / WINDOW_CHUNK) * WINDOW_CHUNK - 1);
|
|
338
|
+
setWin((w) => w && w.start === start && w.end === end ? w : { start, end });
|
|
339
|
+
}, [visibleRange, nCols]);
|
|
340
|
+
react.useLayoutEffect(() => {
|
|
341
|
+
if (!windowed) return;
|
|
342
|
+
measure();
|
|
343
|
+
const el = outerRef.current;
|
|
344
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
345
|
+
const ro = new ResizeObserver(() => measure());
|
|
346
|
+
ro.observe(el);
|
|
347
|
+
return () => ro.disconnect();
|
|
348
|
+
}, [windowed, measure]);
|
|
349
|
+
const onScroll = react.useCallback(() => reactDom.flushSync(measure), [measure]);
|
|
350
|
+
let colStart = 0;
|
|
351
|
+
let colEnd = nCols - 1;
|
|
352
|
+
if (windowed) {
|
|
353
|
+
colStart = win ? win.start : 0;
|
|
354
|
+
colEnd = win ? Math.min(win.end, nCols - 1) : -1;
|
|
355
|
+
}
|
|
356
|
+
const revealCol = react.useCallback(
|
|
357
|
+
(c) => {
|
|
358
|
+
const el = outerRef.current;
|
|
359
|
+
if (!el) return;
|
|
360
|
+
const { headerW } = metrics.current;
|
|
361
|
+
const width = el.clientWidth || metrics.current.width;
|
|
362
|
+
const x = headerW + c * pitchX;
|
|
363
|
+
let left = el.scrollLeft;
|
|
364
|
+
if (x < left + headerW) left = Math.max(0, x - headerW);
|
|
365
|
+
else if (x + pitchX > left + width) left = x + pitchX - width;
|
|
366
|
+
if (left !== el.scrollLeft) {
|
|
367
|
+
el.scrollLeft = left;
|
|
368
|
+
measure();
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
[pitchX, measure]
|
|
372
|
+
);
|
|
257
373
|
const live = react.useRef({ cells, selection, selectionShape, disabledRange, onSelectionChange, onSelectionCommit, onBlockedSelect, onCellHover });
|
|
258
374
|
live.current = { cells, selection, selectionShape, disabledRange, onSelectionChange, onSelectionCommit, onBlockedSelect, onCellHover };
|
|
259
375
|
const fns = react.useRef({});
|
|
@@ -323,6 +439,23 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
323
439
|
},
|
|
324
440
|
[cellsOf]
|
|
325
441
|
);
|
|
442
|
+
const clampToGrid = react.useCallback(
|
|
443
|
+
(x, y) => {
|
|
444
|
+
const first = document.getElementById(cellDomId(baseId, 0, colStart));
|
|
445
|
+
const last = document.getElementById(cellDomId(baseId, nRows - 1, colEnd));
|
|
446
|
+
if (!first || !last) return null;
|
|
447
|
+
const a = first.getBoundingClientRect();
|
|
448
|
+
const b = last.getBoundingClientRect();
|
|
449
|
+
const w = (b.right - a.left) / (colEnd - colStart + 1);
|
|
450
|
+
const h = (b.bottom - a.top) / nRows;
|
|
451
|
+
if (!(w > 0) || !(h > 0)) return null;
|
|
452
|
+
return {
|
|
453
|
+
r: Math.min(nRows - 1, Math.max(0, Math.floor((y - a.top) / h))),
|
|
454
|
+
c: Math.min(nCols - 1, Math.max(0, colStart + Math.floor((x - a.left) / w)))
|
|
455
|
+
};
|
|
456
|
+
},
|
|
457
|
+
[baseId, nRows, nCols, colStart, colEnd]
|
|
458
|
+
);
|
|
326
459
|
const coordFromEvent = react.useCallback(
|
|
327
460
|
(e) => {
|
|
328
461
|
const t = e.target;
|
|
@@ -334,23 +467,8 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
334
467
|
if (!hit) hit = clampToGrid(e.clientX, e.clientY);
|
|
335
468
|
return hit;
|
|
336
469
|
},
|
|
337
|
-
|
|
338
|
-
[baseId, nRows, nCols]
|
|
470
|
+
[baseId, clampToGrid]
|
|
339
471
|
);
|
|
340
|
-
function clampToGrid(x, y) {
|
|
341
|
-
const first = document.getElementById(cellDomId(baseId, 0, 0));
|
|
342
|
-
const last = document.getElementById(cellDomId(baseId, nRows - 1, nCols - 1));
|
|
343
|
-
if (!first || !last) return null;
|
|
344
|
-
const a = first.getBoundingClientRect();
|
|
345
|
-
const b = last.getBoundingClientRect();
|
|
346
|
-
const w = (b.right - a.left) / nCols;
|
|
347
|
-
const h = (b.bottom - a.top) / nRows;
|
|
348
|
-
if (!(w > 0) || !(h > 0)) return null;
|
|
349
|
-
return {
|
|
350
|
-
r: Math.min(nRows - 1, Math.max(0, Math.floor((y - a.top) / h))),
|
|
351
|
-
c: Math.min(nCols - 1, Math.max(0, Math.floor((x - a.left) / w)))
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
472
|
const onPointerDown = (e) => {
|
|
355
473
|
if (e.button !== 0) return;
|
|
356
474
|
const at = coordFromEvent(e);
|
|
@@ -481,13 +599,33 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
481
599
|
const b = el.getBoundingClientRect();
|
|
482
600
|
setTipPos({ left: b.left - o.left + outer.scrollLeft, top: b.bottom - o.top + outer.scrollTop + 4 });
|
|
483
601
|
}, [activeReasons, active.r, active.c, baseId]);
|
|
602
|
+
const reveal = react.useRef({ revealCol, windowed: windowed && !!win, visibleRange });
|
|
603
|
+
reveal.current = { revealCol, windowed: windowed && !!win, visibleRange };
|
|
484
604
|
react.useEffect(() => {
|
|
485
605
|
if (!focused) return;
|
|
606
|
+
if (reveal.current.windowed) {
|
|
607
|
+
reveal.current.revealCol(active.c);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
486
610
|
const el = document.getElementById(cellDomId(baseId, active.r, active.c));
|
|
487
611
|
el?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
488
612
|
}, [focused, active.r, active.c, baseId]);
|
|
613
|
+
react.useEffect(() => {
|
|
614
|
+
const { windowed: w, visibleRange: vr, revealCol: rc } = reveal.current;
|
|
615
|
+
if (!w || !selection || gestureRef.current) return;
|
|
616
|
+
const [f, l] = vr();
|
|
617
|
+
let s0;
|
|
618
|
+
let s1;
|
|
619
|
+
if (selection.shape === "rectangle") {
|
|
620
|
+
s0 = selection.c1;
|
|
621
|
+
s1 = selection.c2;
|
|
622
|
+
} else if (selection.from.r === selection.to.r) {
|
|
623
|
+
s0 = selection.from.c;
|
|
624
|
+
s1 = selection.to.c;
|
|
625
|
+
} else return;
|
|
626
|
+
if (s1 < f || s0 > l) rc(s0);
|
|
627
|
+
}, [selection]);
|
|
489
628
|
const colStep = Math.max(1, Math.floor(nCols / 6));
|
|
490
|
-
const pitchX = cellWidth + 2;
|
|
491
629
|
const tipId = `${baseId}-tip`;
|
|
492
630
|
const bodyRows = new Array(nRows);
|
|
493
631
|
for (let r = 0; r < nRows; r++) {
|
|
@@ -501,17 +639,41 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
501
639
|
selStart: seg ? seg[0] : -1,
|
|
502
640
|
selEnd: seg ? seg[1] : -2,
|
|
503
641
|
activeCol: focused && active.r === r ? active.c : -1,
|
|
504
|
-
onRowHeader
|
|
642
|
+
onRowHeader,
|
|
643
|
+
colStart,
|
|
644
|
+
colEnd,
|
|
645
|
+
windowed
|
|
505
646
|
},
|
|
506
647
|
r
|
|
507
648
|
);
|
|
508
649
|
}
|
|
650
|
+
const gridTemplateColumns = `max-content repeat(${nCols}, ${pitchX}px)`;
|
|
651
|
+
const headerCols = [];
|
|
652
|
+
for (let c = colStart; c <= colEnd; c++) {
|
|
653
|
+
headerCols.push(
|
|
654
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
655
|
+
"div",
|
|
656
|
+
{
|
|
657
|
+
role: "columnheader",
|
|
658
|
+
"aria-colindex": windowed ? c + 2 : void 0,
|
|
659
|
+
style: windowed && c === colStart ? { gridColumnStart: c + 2 } : void 0,
|
|
660
|
+
"data-react-fancy-timegrid-col": c,
|
|
661
|
+
onClick: toggleStripsOnHeaderClick ? () => pickStrip({ r: 0, c }, { r: nRows - 1, c }) : void 0,
|
|
662
|
+
className: toggleStripsOnHeaderClick ? `${HEADER_COL} ${HEADER_HOVER}` : HEADER_COL,
|
|
663
|
+
children: sparseColLabels ? c % colStep === 0 ? cols[c] : "" : cols[c]
|
|
664
|
+
},
|
|
665
|
+
c
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
509
669
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
510
670
|
"div",
|
|
511
671
|
{
|
|
512
672
|
ref: setOuterRef,
|
|
513
673
|
"data-react-fancy-timegrid": "",
|
|
514
674
|
"data-typed": "",
|
|
675
|
+
"data-windowed": windowed ? "" : void 0,
|
|
676
|
+
onScroll: windowed ? onScroll : void 0,
|
|
515
677
|
className: chunkZU5Q77OT_cjs.cn("relative select-none overflow-x-auto", className),
|
|
516
678
|
children: [
|
|
517
679
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -536,21 +698,15 @@ var TypedTimeGrid = react.forwardRef(function TypedTimeGrid2({
|
|
|
536
698
|
onFocus: () => setFocused(true),
|
|
537
699
|
onBlur: () => setFocused(false),
|
|
538
700
|
className: "inline-grid touch-none items-center outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))]/40 dark:focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))]/20",
|
|
539
|
-
style: {
|
|
701
|
+
style: {
|
|
702
|
+
gridTemplateColumns,
|
|
703
|
+
"--tg-cell-w": `${cellWidth}px`,
|
|
704
|
+
"--tg-cell-h": `${cellHeight}px`
|
|
705
|
+
},
|
|
540
706
|
children: [
|
|
541
707
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { role: "row", className: "contents", children: [
|
|
542
708
|
/* @__PURE__ */ jsxRuntime.jsx("div", { role: "columnheader" }),
|
|
543
|
-
|
|
544
|
-
"div",
|
|
545
|
-
{
|
|
546
|
-
role: "columnheader",
|
|
547
|
-
"data-react-fancy-timegrid-col": c,
|
|
548
|
-
onClick: toggleStripsOnHeaderClick ? () => pickStrip({ r: 0, c }, { r: nRows - 1, c }) : void 0,
|
|
549
|
-
className: toggleStripsOnHeaderClick ? `${HEADER_COL} ${HEADER_HOVER}` : HEADER_COL,
|
|
550
|
-
children: sparseColLabels ? c % colStep === 0 ? label : "" : label
|
|
551
|
-
},
|
|
552
|
-
c
|
|
553
|
-
))
|
|
709
|
+
headerCols
|
|
554
710
|
] }),
|
|
555
711
|
bodyRows
|
|
556
712
|
]
|
|
@@ -701,5 +857,5 @@ TimeGrid.displayName = "TimeGrid";
|
|
|
701
857
|
BooleanTimeGrid.displayName = "BooleanTimeGrid";
|
|
702
858
|
|
|
703
859
|
exports.TimeGrid = TimeGrid;
|
|
704
|
-
//# sourceMappingURL=chunk-
|
|
705
|
-
//# sourceMappingURL=chunk-
|
|
860
|
+
//# sourceMappingURL=chunk-4DL2U23D.cjs.map
|
|
861
|
+
//# sourceMappingURL=chunk-4DL2U23D.cjs.map
|