@particle-academy/react-fancy 5.54.2 → 5.56.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 +78 -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-UET6D7B3.cjs → chunk-CQ7QXZLR.cjs} +45 -26
- package/dist/chunk-CQ7QXZLR.cjs.map +1 -0
- package/dist/chunk-SFJ5WOX5.js +703 -0
- package/dist/chunk-SFJ5WOX5.js.map +1 -0
- package/dist/{chunk-HVND2IIB.js → chunk-X2A3NAJW.js} +45 -26
- package/dist/chunk-X2A3NAJW.js.map +1 -0
- package/dist/chunk-XO4UWQCU.cjs +705 -0
- package/dist/chunk-XO4UWQCU.cjs.map +1 -0
- package/dist/index.cjs +4 -4
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/time-grid.cjs +3 -2
- package/dist/time-grid.d.cts +120 -17
- package/dist/time-grid.d.ts +120 -17
- package/dist/time-grid.js +2 -1
- package/docs/Calendar.md +37 -0
- package/package.json +1 -1
- package/dist/chunk-GF23UBOT.js +0 -129
- package/dist/chunk-GF23UBOT.js.map +0 -1
- package/dist/chunk-HVND2IIB.js.map +0 -1
- package/dist/chunk-UET6D7B3.cjs.map +0 -1
- package/dist/chunk-WFHK2DKG.cjs +0 -131
- package/dist/chunk-WFHK2DKG.cjs.map +0 -1
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { composeRefs } from './chunk-SV5BW3IA.js';
|
|
2
|
+
import { cn } from './chunk-MMW2JGKZ.js';
|
|
3
|
+
import { memo, forwardRef, useId, useRef, useMemo, useState, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/components/TimeGrid/TimeGrid.selection.ts
|
|
7
|
+
function selectionBetween(shape, a, b, nCols) {
|
|
8
|
+
if (shape === "linear") {
|
|
9
|
+
const ia = a.r * nCols + a.c;
|
|
10
|
+
const ib = b.r * nCols + b.c;
|
|
11
|
+
return ia <= ib ? { shape: "linear", from: { r: a.r, c: a.c }, to: { r: b.r, c: b.c } } : { shape: "linear", from: { r: b.r, c: b.c }, to: { r: a.r, c: a.c } };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
shape: "rectangle",
|
|
15
|
+
r1: Math.min(a.r, b.r),
|
|
16
|
+
c1: Math.min(a.c, b.c),
|
|
17
|
+
r2: Math.max(a.r, b.r),
|
|
18
|
+
c2: Math.max(a.c, b.c)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function rowSegment(sel, r, nCols) {
|
|
22
|
+
if (!sel) return null;
|
|
23
|
+
if (sel.shape === "rectangle") {
|
|
24
|
+
return r >= sel.r1 && r <= sel.r2 ? [sel.c1, sel.c2] : null;
|
|
25
|
+
}
|
|
26
|
+
const { from, to } = sel;
|
|
27
|
+
if (r < from.r || r > to.r) return null;
|
|
28
|
+
const start = r === from.r ? from.c : 0;
|
|
29
|
+
const end = r === to.r ? to.c : nCols - 1;
|
|
30
|
+
return start <= end ? [start, end] : null;
|
|
31
|
+
}
|
|
32
|
+
function selectionCoords(sel, nRows, nCols) {
|
|
33
|
+
const out = [];
|
|
34
|
+
if (!sel) return out;
|
|
35
|
+
const rFrom = sel.shape === "rectangle" ? sel.r1 : sel.from.r;
|
|
36
|
+
const rTo = sel.shape === "rectangle" ? sel.r2 : sel.to.r;
|
|
37
|
+
for (let r = Math.max(0, rFrom); r <= Math.min(nRows - 1, rTo); r++) {
|
|
38
|
+
const seg = rowSegment(sel, r, nCols);
|
|
39
|
+
if (!seg) continue;
|
|
40
|
+
for (let c = Math.max(0, seg[0]); c <= Math.min(nCols - 1, seg[1]); c++) out.push({ r, c });
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function sameSelection(a, b) {
|
|
45
|
+
if (!a || !b) return !a && !b;
|
|
46
|
+
if (a.shape === "rectangle" && b.shape === "rectangle") {
|
|
47
|
+
return a.r1 === b.r1 && a.c1 === b.c1 && a.r2 === b.r2 && a.c2 === b.c2;
|
|
48
|
+
}
|
|
49
|
+
if (a.shape === "linear" && b.shape === "linear") {
|
|
50
|
+
return a.from.r === b.from.r && a.from.c === b.from.c && a.to.r === b.to.r && a.to.c === b.to.c;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function resolveRange(shape, anchor, target, nRows, nCols, isDisabled, policy) {
|
|
55
|
+
const requested = selectionBetween(shape, anchor, target, nCols);
|
|
56
|
+
if (policy === "span") return { kind: "select", selection: requested, blocked: [] };
|
|
57
|
+
const blocked = selectionCoords(requested, nRows, nCols).filter(({ r, c }) => isDisabled(r, c));
|
|
58
|
+
if (blocked.length === 0) return { kind: "select", selection: requested, blocked };
|
|
59
|
+
if (policy === "refuse" || isDisabled(anchor.r, anchor.c)) return { kind: "refuse", blocked };
|
|
60
|
+
if (shape === "linear") {
|
|
61
|
+
const ia = anchor.r * nCols + anchor.c;
|
|
62
|
+
const it = target.r * nCols + target.c;
|
|
63
|
+
const step = it >= ia ? 1 : -1;
|
|
64
|
+
let last = ia;
|
|
65
|
+
for (let i = ia + step; step > 0 ? i <= it : i >= it; i += step) {
|
|
66
|
+
if (isDisabled(Math.floor(i / nCols), i % nCols)) break;
|
|
67
|
+
last = i;
|
|
68
|
+
}
|
|
69
|
+
const end = { r: Math.floor(last / nCols), c: last % nCols };
|
|
70
|
+
return { kind: "select", selection: selectionBetween("linear", anchor, end, nCols), blocked };
|
|
71
|
+
}
|
|
72
|
+
const rStep = target.r >= anchor.r ? 1 : -1;
|
|
73
|
+
const cStep = target.c >= anchor.c ? 1 : -1;
|
|
74
|
+
const colBlocked = (c, rEnd2) => {
|
|
75
|
+
for (let r = anchor.r; rStep > 0 ? r <= rEnd2 : r >= rEnd2; r += rStep) {
|
|
76
|
+
if (isDisabled(r, c)) return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
let rEnd = target.r;
|
|
81
|
+
if (colBlocked(anchor.c, rEnd)) {
|
|
82
|
+
rEnd = anchor.r;
|
|
83
|
+
for (let r = anchor.r + rStep; rStep > 0 ? r <= target.r : r >= target.r; r += rStep) {
|
|
84
|
+
if (isDisabled(r, anchor.c)) break;
|
|
85
|
+
rEnd = r;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let cEnd = anchor.c;
|
|
89
|
+
for (let c = anchor.c + cStep; cStep > 0 ? c <= target.c : c >= target.c; c += cStep) {
|
|
90
|
+
if (colBlocked(c, rEnd)) break;
|
|
91
|
+
cEnd = c;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
kind: "select",
|
|
95
|
+
selection: selectionBetween("rectangle", anchor, { r: rEnd, c: cEnd }, nCols),
|
|
96
|
+
blocked
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
var TONE_FILL = {
|
|
100
|
+
violet: "bg-violet-500",
|
|
101
|
+
emerald: "bg-emerald-500",
|
|
102
|
+
sky: "bg-sky-500",
|
|
103
|
+
rose: "bg-rose-500",
|
|
104
|
+
amber: "bg-amber-500",
|
|
105
|
+
indigo: "bg-indigo-500",
|
|
106
|
+
blue: "bg-blue-500",
|
|
107
|
+
zinc: "bg-zinc-500"
|
|
108
|
+
};
|
|
109
|
+
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))]";
|
|
110
|
+
var CELL_BASE = "relative m-px rounded-sm";
|
|
111
|
+
var CELL_ENABLED = "cursor-pointer hover:brightness-90 dark:hover:brightness-125";
|
|
112
|
+
var CELL_DISABLED = "cursor-not-allowed opacity-40";
|
|
113
|
+
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";
|
|
114
|
+
var CELL_ACTIVE = "z-10 outline-2 outline-offset-1 outline-[color:var(--fancy-text,var(--color-zinc-900))] dark:outline-[color:var(--fancy-text-dark,var(--color-zinc-100))]";
|
|
115
|
+
var MARK_DOT = "h-1.5 w-1.5 rounded-full ring-1 ring-[color:var(--fancy-surface,var(--color-white))] dark:ring-[color:var(--fancy-surface-dark,var(--color-zinc-900))]";
|
|
116
|
+
var HEADER_COL = "overflow-visible whitespace-nowrap text-[10px] leading-4 text-[color:var(--fancy-text-faint,var(--color-zinc-400))] dark:text-[color:var(--fancy-text-faint-dark,var(--color-zinc-500))]";
|
|
117
|
+
var HEADER_ROW = "whitespace-nowrap pr-1 text-right text-[10px] font-medium text-[color:var(--fancy-text-muted,var(--color-zinc-500))] dark:text-[color:var(--fancy-text-muted-dark,var(--color-zinc-400))]";
|
|
118
|
+
var HEADER_HOVER = "cursor-pointer hover:text-[color:var(--fancy-text,var(--color-zinc-900))] dark:hover:text-[color:var(--fancy-text-dark,var(--color-zinc-100))]";
|
|
119
|
+
var TOOLTIP = "pointer-events-none absolute z-20 max-w-xs rounded-md px-2 py-1 text-xs shadow-lg bg-[color:var(--fancy-text,var(--color-zinc-900))] text-[color:var(--fancy-surface,var(--color-white))] dark:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))] dark:text-[color:var(--fancy-text-dark,var(--color-zinc-100))]";
|
|
120
|
+
function isDev() {
|
|
121
|
+
try {
|
|
122
|
+
return process.env.NODE_ENV !== "production";
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
var EMPTY = {};
|
|
128
|
+
var warnedStates = /* @__PURE__ */ new Set();
|
|
129
|
+
var warnedNoStates = false;
|
|
130
|
+
var cellDomId = (baseId, r, c) => `${baseId}-${r}-${c}`;
|
|
131
|
+
function parseCoord(el, baseId) {
|
|
132
|
+
const id = el?.id;
|
|
133
|
+
if (!id || !id.startsWith(baseId + "-")) return null;
|
|
134
|
+
const m = /-(\d+)-(\d+)$/.exec(id);
|
|
135
|
+
return m ? { r: Number(m[1]), c: Number(m[2]) } : null;
|
|
136
|
+
}
|
|
137
|
+
var TypedRow = memo(function TypedRow2({
|
|
138
|
+
ctx,
|
|
139
|
+
r,
|
|
140
|
+
rowCells,
|
|
141
|
+
selStart,
|
|
142
|
+
selEnd,
|
|
143
|
+
activeCol,
|
|
144
|
+
onRowHeader
|
|
145
|
+
}) {
|
|
146
|
+
const { baseId, rows, cols, states, marks, stateClass, cellWidth, cellHeight } = ctx;
|
|
147
|
+
const { cellId, ariaCell } = ctx.fns.current;
|
|
148
|
+
const out = new Array(cols.length + 1);
|
|
149
|
+
out[0] = /* @__PURE__ */ jsx(
|
|
150
|
+
"div",
|
|
151
|
+
{
|
|
152
|
+
role: "rowheader",
|
|
153
|
+
"data-react-fancy-timegrid-row": r,
|
|
154
|
+
onClick: onRowHeader ? () => onRowHeader(r) : void 0,
|
|
155
|
+
className: onRowHeader ? `${HEADER_ROW} ${HEADER_HOVER}` : HEADER_ROW,
|
|
156
|
+
children: rows[r]
|
|
157
|
+
},
|
|
158
|
+
"h"
|
|
159
|
+
);
|
|
160
|
+
for (let c = 0; c < cols.length; c++) {
|
|
161
|
+
const cell = rowCells?.[c];
|
|
162
|
+
const state = cell?.state ?? "";
|
|
163
|
+
const selected = c >= selStart && c <= selEnd;
|
|
164
|
+
const reasons = cell?.reasons?.length ? cell.reasons.join(". ") : void 0;
|
|
165
|
+
const markIds = cell?.marks;
|
|
166
|
+
let label;
|
|
167
|
+
if (ariaCell) {
|
|
168
|
+
label = ariaCell(r, c, selected);
|
|
169
|
+
} else {
|
|
170
|
+
label = `${rows[r]} ${cols[c]} ${states[state]?.label ?? state}`;
|
|
171
|
+
if (markIds?.length) label += ", " + markIds.map((m) => marks[m]?.label ?? m).join(", ");
|
|
172
|
+
}
|
|
173
|
+
let cls = stateClass(state) + " " + (cell?.disabled ? CELL_DISABLED : CELL_ENABLED);
|
|
174
|
+
if (selected) cls += " " + CELL_SELECTED;
|
|
175
|
+
if (c === activeCol) cls += " " + CELL_ACTIVE;
|
|
176
|
+
out[c + 1] = /* @__PURE__ */ jsx(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
id: cellDomId(baseId, r, c),
|
|
180
|
+
role: "gridcell",
|
|
181
|
+
"data-react-fancy-timegrid-cell": cellId ? cellId(r, c) : `${r}:${c}`,
|
|
182
|
+
"data-state": state,
|
|
183
|
+
"data-marks": markIds?.length ? markIds.join(" ") : void 0,
|
|
184
|
+
"aria-selected": selected,
|
|
185
|
+
"aria-disabled": cell?.disabled ? true : void 0,
|
|
186
|
+
"aria-label": label,
|
|
187
|
+
"aria-description": reasons,
|
|
188
|
+
title: reasons,
|
|
189
|
+
className: cls,
|
|
190
|
+
style: { width: cellWidth, height: cellHeight },
|
|
191
|
+
children: markIds?.length ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute right-0 top-0 flex gap-px", children: markIds.map((m) => {
|
|
192
|
+
const def = marks[m];
|
|
193
|
+
return def?.icon ? /* @__PURE__ */ 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__ */ jsx(
|
|
194
|
+
"span",
|
|
195
|
+
{
|
|
196
|
+
"data-react-fancy-timegrid-mark": m,
|
|
197
|
+
className: `${MARK_DOT} ${def?.tone ? TONE_FILL[def.tone] : NEUTRAL_FILL}`
|
|
198
|
+
},
|
|
199
|
+
m
|
|
200
|
+
);
|
|
201
|
+
}) }) : null
|
|
202
|
+
},
|
|
203
|
+
c
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return /* @__PURE__ */ jsx("div", { role: "row", className: "contents", children: out });
|
|
207
|
+
});
|
|
208
|
+
var TypedTimeGrid = forwardRef(function TypedTimeGrid2({
|
|
209
|
+
rows,
|
|
210
|
+
cols,
|
|
211
|
+
cells: cellsProp,
|
|
212
|
+
states: statesProp,
|
|
213
|
+
marks: marksProp,
|
|
214
|
+
cellWidth = 20,
|
|
215
|
+
cellHeight = 16,
|
|
216
|
+
sparseColLabels = true,
|
|
217
|
+
toggleStripsOnHeaderClick = true,
|
|
218
|
+
ariaCell,
|
|
219
|
+
cellId,
|
|
220
|
+
className,
|
|
221
|
+
selectionShape = "rectangle",
|
|
222
|
+
selection: selectionProp,
|
|
223
|
+
onSelectionChange,
|
|
224
|
+
onSelectionCommit,
|
|
225
|
+
disabledRange = "clamp",
|
|
226
|
+
onBlockedSelect,
|
|
227
|
+
onCellHover,
|
|
228
|
+
"aria-label": ariaLabel
|
|
229
|
+
}, ref) {
|
|
230
|
+
if (!statesProp) {
|
|
231
|
+
const msg = 'TimeGrid: `cells` was given without `states`. Every cell names a state id, and with no vocabulary there is no tone to draw it in. Pass `states={{ open: { tone: "emerald", label: "Open" }, \u2026 }}`.';
|
|
232
|
+
if (isDev()) throw new Error(msg);
|
|
233
|
+
if (!warnedNoStates) {
|
|
234
|
+
warnedNoStates = true;
|
|
235
|
+
console.error(msg);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const states = statesProp ?? EMPTY;
|
|
239
|
+
const marks = marksProp ?? EMPTY;
|
|
240
|
+
const cells = cellsProp;
|
|
241
|
+
const nRows = rows.length;
|
|
242
|
+
const nCols = cols.length;
|
|
243
|
+
const baseId = useId();
|
|
244
|
+
const outerRef = useRef(null);
|
|
245
|
+
const gridRef = useRef(null);
|
|
246
|
+
const setOuterRef = useMemo(() => composeRefs(ref, outerRef), [ref]);
|
|
247
|
+
const controlled = selectionProp !== void 0;
|
|
248
|
+
const [inner, setInner] = useState(null);
|
|
249
|
+
const selection = controlled ? selectionProp : inner;
|
|
250
|
+
const [active, setActive] = useState({ r: 0, c: 0 });
|
|
251
|
+
const [focused, setFocused] = useState(false);
|
|
252
|
+
const anchorRef = useRef(null);
|
|
253
|
+
const gestureRef = useRef(null);
|
|
254
|
+
const hoverRef = useRef("");
|
|
255
|
+
const live = useRef({ cells, selection, selectionShape, disabledRange, onSelectionChange, onSelectionCommit, onBlockedSelect, onCellHover });
|
|
256
|
+
live.current = { cells, selection, selectionShape, disabledRange, onSelectionChange, onSelectionCommit, onBlockedSelect, onCellHover };
|
|
257
|
+
const fns = useRef({});
|
|
258
|
+
fns.current = { cellId, ariaCell };
|
|
259
|
+
const stateClass = useMemo(() => {
|
|
260
|
+
const cache = /* @__PURE__ */ new Map();
|
|
261
|
+
return (state) => {
|
|
262
|
+
let cls = cache.get(state);
|
|
263
|
+
if (cls !== void 0) return cls;
|
|
264
|
+
const def = states[state];
|
|
265
|
+
if (!def && statesProp && !warnedStates.has(state)) {
|
|
266
|
+
warnedStates.add(state);
|
|
267
|
+
console.warn(`TimeGrid: unknown state id "${state}" \u2014 not a key of \`states\`. Rendering it neutral.`);
|
|
268
|
+
}
|
|
269
|
+
cls = `${CELL_BASE} ${def?.tone ? TONE_FILL[def.tone] : NEUTRAL_FILL}${def?.className ? " " + def.className : ""}`;
|
|
270
|
+
cache.set(state, cls);
|
|
271
|
+
return cls;
|
|
272
|
+
};
|
|
273
|
+
}, [states, statesProp]);
|
|
274
|
+
const ctx = useMemo(
|
|
275
|
+
() => ({ baseId, rows, cols, states, marks, stateClass, cellWidth, cellHeight, fns }),
|
|
276
|
+
[baseId, rows, cols, states, marks, stateClass, cellWidth, cellHeight]
|
|
277
|
+
);
|
|
278
|
+
const isDisabled = useCallback(
|
|
279
|
+
(r, c) => !!live.current.cells[r]?.[c]?.disabled,
|
|
280
|
+
[]
|
|
281
|
+
);
|
|
282
|
+
const cellsOf = useCallback(
|
|
283
|
+
(sel) => selectionCoords(sel, nRows, nCols).map(({ r, c }) => live.current.cells[r]?.[c]),
|
|
284
|
+
[nRows, nCols]
|
|
285
|
+
);
|
|
286
|
+
const emit = useCallback(
|
|
287
|
+
(next) => {
|
|
288
|
+
if (!controlled) setInner(next);
|
|
289
|
+
live.current.onSelectionChange?.(next, cellsOf(next));
|
|
290
|
+
},
|
|
291
|
+
[controlled, cellsOf]
|
|
292
|
+
);
|
|
293
|
+
const reportBlocked = useCallback((blocked, g) => {
|
|
294
|
+
if (!blocked.length) return;
|
|
295
|
+
const key = blocked.map((b) => `${b.r}:${b.c}`).join(",");
|
|
296
|
+
if (g) {
|
|
297
|
+
if (g.blockedKey === key) return;
|
|
298
|
+
g.blockedKey = key;
|
|
299
|
+
}
|
|
300
|
+
live.current.onBlockedSelect?.(
|
|
301
|
+
blocked.map(({ r, c }) => {
|
|
302
|
+
const reasons = live.current.cells[r]?.[c]?.reasons;
|
|
303
|
+
return reasons ? { r, c, reasons } : { r, c };
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
}, []);
|
|
307
|
+
const extend = useCallback(
|
|
308
|
+
(anchor, target, g, shape = live.current.selectionShape) => {
|
|
309
|
+
const res = resolveRange(shape, anchor, target, nRows, nCols, isDisabled, live.current.disabledRange);
|
|
310
|
+
reportBlocked(res.blocked, g);
|
|
311
|
+
if (res.kind === "refuse") return void 0;
|
|
312
|
+
const current = g ? g.selection : live.current.selection;
|
|
313
|
+
if (!sameSelection(current, res.selection) || g && g.selection === null) emit(res.selection);
|
|
314
|
+
return res.selection;
|
|
315
|
+
},
|
|
316
|
+
[nRows, nCols, isDisabled, reportBlocked, emit]
|
|
317
|
+
);
|
|
318
|
+
const commit = useCallback(
|
|
319
|
+
(sel) => {
|
|
320
|
+
if (sel) live.current.onSelectionCommit?.(sel, cellsOf(sel));
|
|
321
|
+
},
|
|
322
|
+
[cellsOf]
|
|
323
|
+
);
|
|
324
|
+
const coordFromEvent = useCallback(
|
|
325
|
+
(e) => {
|
|
326
|
+
const t = e.target;
|
|
327
|
+
let hit = parseCoord(t?.closest?.('[role="gridcell"]') ?? null, baseId);
|
|
328
|
+
if (!hit && typeof document.elementFromPoint === "function") {
|
|
329
|
+
const under = document.elementFromPoint(e.clientX, e.clientY);
|
|
330
|
+
hit = parseCoord(under?.closest?.('[role="gridcell"]') ?? null, baseId);
|
|
331
|
+
}
|
|
332
|
+
if (!hit) hit = clampToGrid(e.clientX, e.clientY);
|
|
333
|
+
return hit;
|
|
334
|
+
},
|
|
335
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
336
|
+
[baseId, nRows, nCols]
|
|
337
|
+
);
|
|
338
|
+
function clampToGrid(x, y) {
|
|
339
|
+
const first = document.getElementById(cellDomId(baseId, 0, 0));
|
|
340
|
+
const last = document.getElementById(cellDomId(baseId, nRows - 1, nCols - 1));
|
|
341
|
+
if (!first || !last) return null;
|
|
342
|
+
const a = first.getBoundingClientRect();
|
|
343
|
+
const b = last.getBoundingClientRect();
|
|
344
|
+
const w = (b.right - a.left) / nCols;
|
|
345
|
+
const h = (b.bottom - a.top) / nRows;
|
|
346
|
+
if (!(w > 0) || !(h > 0)) return null;
|
|
347
|
+
return {
|
|
348
|
+
r: Math.min(nRows - 1, Math.max(0, Math.floor((y - a.top) / h))),
|
|
349
|
+
c: Math.min(nCols - 1, Math.max(0, Math.floor((x - a.left) / w)))
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const onPointerDown = (e) => {
|
|
353
|
+
if (e.button !== 0) return;
|
|
354
|
+
const at = coordFromEvent(e);
|
|
355
|
+
if (!at) return;
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
gridRef.current?.focus({ preventScroll: true });
|
|
358
|
+
try {
|
|
359
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
setActive(at);
|
|
363
|
+
const g = { anchor: at, last: at, selection: null, blockedKey: "" };
|
|
364
|
+
const sel = extend(at, at, g);
|
|
365
|
+
if (sel === void 0) {
|
|
366
|
+
gestureRef.current = null;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
g.selection = sel;
|
|
370
|
+
anchorRef.current = at;
|
|
371
|
+
gestureRef.current = g;
|
|
372
|
+
};
|
|
373
|
+
const onPointerMove = (e) => {
|
|
374
|
+
const g = gestureRef.current;
|
|
375
|
+
if (!g) return;
|
|
376
|
+
const at = coordFromEvent(e);
|
|
377
|
+
if (!at || at.r === g.last.r && at.c === g.last.c) return;
|
|
378
|
+
g.last = at;
|
|
379
|
+
setActive(at);
|
|
380
|
+
const sel = extend(g.anchor, at, g);
|
|
381
|
+
if (sel !== void 0) g.selection = sel;
|
|
382
|
+
};
|
|
383
|
+
const endGesture = () => {
|
|
384
|
+
const g = gestureRef.current;
|
|
385
|
+
gestureRef.current = null;
|
|
386
|
+
if (g) commit(g.selection);
|
|
387
|
+
};
|
|
388
|
+
const onPointerOver = (e) => {
|
|
389
|
+
if (!live.current.onCellHover) return;
|
|
390
|
+
const at = parseCoord(e.target?.closest?.('[role="gridcell"]') ?? null, baseId);
|
|
391
|
+
if (!at) return;
|
|
392
|
+
const key = `${at.r}:${at.c}`;
|
|
393
|
+
if (hoverRef.current === key) return;
|
|
394
|
+
hoverRef.current = key;
|
|
395
|
+
live.current.onCellHover(live.current.cells[at.r]?.[at.c] ?? null, at);
|
|
396
|
+
};
|
|
397
|
+
const onPointerLeave = () => {
|
|
398
|
+
if (!hoverRef.current) return;
|
|
399
|
+
hoverRef.current = "";
|
|
400
|
+
live.current.onCellHover?.(null, null);
|
|
401
|
+
};
|
|
402
|
+
const onKeyDown = (e) => {
|
|
403
|
+
const move = {
|
|
404
|
+
ArrowUp: [-1, 0],
|
|
405
|
+
ArrowDown: [1, 0],
|
|
406
|
+
ArrowLeft: [0, -1],
|
|
407
|
+
ArrowRight: [0, 1]
|
|
408
|
+
};
|
|
409
|
+
const d = move[e.key];
|
|
410
|
+
if (d) {
|
|
411
|
+
e.preventDefault();
|
|
412
|
+
const next = {
|
|
413
|
+
r: Math.min(nRows - 1, Math.max(0, active.r + d[0])),
|
|
414
|
+
c: Math.min(nCols - 1, Math.max(0, active.c + d[1]))
|
|
415
|
+
};
|
|
416
|
+
setActive(next);
|
|
417
|
+
if (e.shiftKey) {
|
|
418
|
+
const anchor = anchorRef.current ?? active;
|
|
419
|
+
anchorRef.current = anchor;
|
|
420
|
+
extend(anchor, next, null);
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (e.key === " " || e.key === "Spacebar") {
|
|
425
|
+
e.preventDefault();
|
|
426
|
+
const sel = live.current.selection;
|
|
427
|
+
const single = selectionBetween(live.current.selectionShape, active, active, nCols);
|
|
428
|
+
if (sel && sameSelection(sel, single)) {
|
|
429
|
+
anchorRef.current = null;
|
|
430
|
+
emit(null);
|
|
431
|
+
} else {
|
|
432
|
+
const res = extend(active, active, null);
|
|
433
|
+
if (res !== void 0) anchorRef.current = active;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (e.key === "Enter") {
|
|
438
|
+
e.preventDefault();
|
|
439
|
+
let sel = live.current.selection ?? null;
|
|
440
|
+
if (!sel) {
|
|
441
|
+
const res = extend(active, active, null);
|
|
442
|
+
if (res === void 0) return;
|
|
443
|
+
anchorRef.current = active;
|
|
444
|
+
sel = res;
|
|
445
|
+
}
|
|
446
|
+
commit(sel);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (e.key === "Escape") {
|
|
450
|
+
if (live.current.selection) {
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
anchorRef.current = null;
|
|
453
|
+
emit(null);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const pickStrip = useCallback(
|
|
458
|
+
(a, b) => {
|
|
459
|
+
anchorRef.current = a;
|
|
460
|
+
setActive(a);
|
|
461
|
+
const sel = extend(a, b, null, "rectangle");
|
|
462
|
+
if (sel) commit(sel);
|
|
463
|
+
},
|
|
464
|
+
[extend, commit]
|
|
465
|
+
);
|
|
466
|
+
const onRowHeader = useMemo(
|
|
467
|
+
() => toggleStripsOnHeaderClick ? (r) => pickStrip({ r, c: 0 }, { r, c: nCols - 1 }) : void 0,
|
|
468
|
+
[toggleStripsOnHeaderClick, pickStrip, nCols]
|
|
469
|
+
);
|
|
470
|
+
const activeCell = cells[active.r]?.[active.c];
|
|
471
|
+
const activeReasons = focused && activeCell?.reasons?.length ? activeCell.reasons.join(". ") : null;
|
|
472
|
+
const [tipPos, setTipPos] = useState(null);
|
|
473
|
+
useLayoutEffect(() => {
|
|
474
|
+
if (!activeReasons) return;
|
|
475
|
+
const outer = outerRef.current;
|
|
476
|
+
const el = document.getElementById(cellDomId(baseId, active.r, active.c));
|
|
477
|
+
if (!outer || !el) return;
|
|
478
|
+
const o = outer.getBoundingClientRect();
|
|
479
|
+
const b = el.getBoundingClientRect();
|
|
480
|
+
setTipPos({ left: b.left - o.left + outer.scrollLeft, top: b.bottom - o.top + outer.scrollTop + 4 });
|
|
481
|
+
}, [activeReasons, active.r, active.c, baseId]);
|
|
482
|
+
useEffect(() => {
|
|
483
|
+
if (!focused) return;
|
|
484
|
+
const el = document.getElementById(cellDomId(baseId, active.r, active.c));
|
|
485
|
+
el?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
486
|
+
}, [focused, active.r, active.c, baseId]);
|
|
487
|
+
const colStep = Math.max(1, Math.floor(nCols / 6));
|
|
488
|
+
const pitchX = cellWidth + 2;
|
|
489
|
+
const tipId = `${baseId}-tip`;
|
|
490
|
+
const bodyRows = new Array(nRows);
|
|
491
|
+
for (let r = 0; r < nRows; r++) {
|
|
492
|
+
const seg = rowSegment(selection, r, nCols);
|
|
493
|
+
bodyRows[r] = /* @__PURE__ */ jsx(
|
|
494
|
+
TypedRow,
|
|
495
|
+
{
|
|
496
|
+
ctx,
|
|
497
|
+
r,
|
|
498
|
+
rowCells: cells[r],
|
|
499
|
+
selStart: seg ? seg[0] : -1,
|
|
500
|
+
selEnd: seg ? seg[1] : -2,
|
|
501
|
+
activeCol: focused && active.r === r ? active.c : -1,
|
|
502
|
+
onRowHeader
|
|
503
|
+
},
|
|
504
|
+
r
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
return /* @__PURE__ */ jsxs(
|
|
508
|
+
"div",
|
|
509
|
+
{
|
|
510
|
+
ref: setOuterRef,
|
|
511
|
+
"data-react-fancy-timegrid": "",
|
|
512
|
+
"data-typed": "",
|
|
513
|
+
className: cn("relative select-none overflow-x-auto", className),
|
|
514
|
+
children: [
|
|
515
|
+
/* @__PURE__ */ jsxs(
|
|
516
|
+
"div",
|
|
517
|
+
{
|
|
518
|
+
ref: gridRef,
|
|
519
|
+
role: "grid",
|
|
520
|
+
tabIndex: 0,
|
|
521
|
+
"aria-label": ariaLabel,
|
|
522
|
+
"aria-multiselectable": "true",
|
|
523
|
+
"aria-rowcount": nRows + 1,
|
|
524
|
+
"aria-colcount": nCols + 1,
|
|
525
|
+
"aria-activedescendant": cellDomId(baseId, active.r, active.c),
|
|
526
|
+
"aria-describedby": activeReasons ? tipId : void 0,
|
|
527
|
+
onPointerDown,
|
|
528
|
+
onPointerMove,
|
|
529
|
+
onPointerUp: endGesture,
|
|
530
|
+
onPointerCancel: endGesture,
|
|
531
|
+
onPointerOver,
|
|
532
|
+
onPointerLeave,
|
|
533
|
+
onKeyDown,
|
|
534
|
+
onFocus: () => setFocused(true),
|
|
535
|
+
onBlur: () => setFocused(false),
|
|
536
|
+
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",
|
|
537
|
+
style: { gridTemplateColumns: `max-content repeat(${nCols}, ${pitchX}px)` },
|
|
538
|
+
children: [
|
|
539
|
+
/* @__PURE__ */ jsxs("div", { role: "row", className: "contents", children: [
|
|
540
|
+
/* @__PURE__ */ jsx("div", { role: "columnheader" }),
|
|
541
|
+
cols.map((label, c) => /* @__PURE__ */ jsx(
|
|
542
|
+
"div",
|
|
543
|
+
{
|
|
544
|
+
role: "columnheader",
|
|
545
|
+
"data-react-fancy-timegrid-col": c,
|
|
546
|
+
onClick: toggleStripsOnHeaderClick ? () => pickStrip({ r: 0, c }, { r: nRows - 1, c }) : void 0,
|
|
547
|
+
className: toggleStripsOnHeaderClick ? `${HEADER_COL} ${HEADER_HOVER}` : HEADER_COL,
|
|
548
|
+
children: sparseColLabels ? c % colStep === 0 ? label : "" : label
|
|
549
|
+
},
|
|
550
|
+
c
|
|
551
|
+
))
|
|
552
|
+
] }),
|
|
553
|
+
bodyRows
|
|
554
|
+
]
|
|
555
|
+
}
|
|
556
|
+
),
|
|
557
|
+
activeReasons ? /* @__PURE__ */ jsx(
|
|
558
|
+
"div",
|
|
559
|
+
{
|
|
560
|
+
id: tipId,
|
|
561
|
+
role: "tooltip",
|
|
562
|
+
"data-react-fancy-timegrid-tooltip": "",
|
|
563
|
+
className: TOOLTIP,
|
|
564
|
+
style: tipPos ? { left: tipPos.left, top: tipPos.top } : void 0,
|
|
565
|
+
children: activeReasons
|
|
566
|
+
}
|
|
567
|
+
) : null
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
var TONE_CLASSES = {
|
|
573
|
+
violet: "bg-violet-500 hover:bg-violet-600",
|
|
574
|
+
emerald: "bg-emerald-500 hover:bg-emerald-600",
|
|
575
|
+
sky: "bg-sky-500 hover:bg-sky-600",
|
|
576
|
+
rose: "bg-rose-500 hover:bg-rose-600",
|
|
577
|
+
amber: "bg-amber-500 hover:bg-amber-600",
|
|
578
|
+
indigo: "bg-indigo-500 hover:bg-indigo-600",
|
|
579
|
+
blue: "bg-blue-500 hover:bg-blue-600",
|
|
580
|
+
zinc: "bg-zinc-500 hover:bg-zinc-600"
|
|
581
|
+
};
|
|
582
|
+
var OFF_CLASSES = "bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] hover:bg-zinc-200 dark:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))] dark:hover:bg-zinc-700";
|
|
583
|
+
var NO_VALUE = [];
|
|
584
|
+
var noop = () => {
|
|
585
|
+
};
|
|
586
|
+
var TimeGrid = forwardRef(
|
|
587
|
+
(props, ref) => props.cells ? /* @__PURE__ */ jsx(TypedTimeGrid, { ref, ...props }) : /* @__PURE__ */ jsx(BooleanTimeGrid, { ref, ...props })
|
|
588
|
+
);
|
|
589
|
+
var BooleanTimeGrid = forwardRef(
|
|
590
|
+
({
|
|
591
|
+
rows,
|
|
592
|
+
cols,
|
|
593
|
+
value = NO_VALUE,
|
|
594
|
+
onChange = noop,
|
|
595
|
+
toneOn = "violet",
|
|
596
|
+
cellWidth = 20,
|
|
597
|
+
cellHeight = 16,
|
|
598
|
+
sparseColLabels = true,
|
|
599
|
+
toggleStripsOnHeaderClick = true,
|
|
600
|
+
ariaCell,
|
|
601
|
+
cellId,
|
|
602
|
+
className
|
|
603
|
+
}, ref) => {
|
|
604
|
+
const [drag, setDrag] = useState(null);
|
|
605
|
+
const setCell = (r, c, v) => {
|
|
606
|
+
if (value[r]?.[c] === v) return;
|
|
607
|
+
const next = value.map((row) => row.slice());
|
|
608
|
+
next[r][c] = v;
|
|
609
|
+
onChange(next);
|
|
610
|
+
};
|
|
611
|
+
const toggleRow = (r) => {
|
|
612
|
+
if (!toggleStripsOnHeaderClick) return;
|
|
613
|
+
const all = value[r].every(Boolean);
|
|
614
|
+
const next = value.map((row) => row.slice());
|
|
615
|
+
for (let c = 0; c < cols.length; c++) next[r][c] = !all;
|
|
616
|
+
onChange(next);
|
|
617
|
+
};
|
|
618
|
+
const toggleCol = (c) => {
|
|
619
|
+
if (!toggleStripsOnHeaderClick) return;
|
|
620
|
+
const all = value.every((row) => row[c]);
|
|
621
|
+
const next = value.map((row) => row.slice());
|
|
622
|
+
for (let r = 0; r < rows.length; r++) next[r][c] = !all;
|
|
623
|
+
onChange(next);
|
|
624
|
+
};
|
|
625
|
+
const colStep = Math.max(1, Math.floor(cols.length / 6));
|
|
626
|
+
const onTone = TONE_CLASSES[toneOn];
|
|
627
|
+
return /* @__PURE__ */ jsx(
|
|
628
|
+
"div",
|
|
629
|
+
{
|
|
630
|
+
ref,
|
|
631
|
+
"data-react-fancy-timegrid": "",
|
|
632
|
+
className: cn(
|
|
633
|
+
"select-none overflow-x-auto",
|
|
634
|
+
className
|
|
635
|
+
),
|
|
636
|
+
onMouseLeave: () => setDrag(null),
|
|
637
|
+
onMouseUp: () => setDrag(null),
|
|
638
|
+
children: /* @__PURE__ */ jsxs("table", { className: "text-[10px]", children: [
|
|
639
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
640
|
+
/* @__PURE__ */ jsx("th", {}),
|
|
641
|
+
cols.map((label, c) => /* @__PURE__ */ jsx(
|
|
642
|
+
"th",
|
|
643
|
+
{
|
|
644
|
+
onClick: () => toggleCol(c),
|
|
645
|
+
style: { width: cellWidth },
|
|
646
|
+
"data-react-fancy-timegrid-col": c,
|
|
647
|
+
className: cn(
|
|
648
|
+
"text-zinc-400",
|
|
649
|
+
toggleStripsOnHeaderClick && "cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-200"
|
|
650
|
+
),
|
|
651
|
+
children: sparseColLabels ? c % colStep === 0 ? label : "" : label
|
|
652
|
+
},
|
|
653
|
+
c
|
|
654
|
+
))
|
|
655
|
+
] }) }),
|
|
656
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((label, r) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
657
|
+
/* @__PURE__ */ jsx(
|
|
658
|
+
"th",
|
|
659
|
+
{
|
|
660
|
+
onClick: () => toggleRow(r),
|
|
661
|
+
"data-react-fancy-timegrid-row": r,
|
|
662
|
+
className: cn(
|
|
663
|
+
"pr-1 text-right font-medium text-zinc-500",
|
|
664
|
+
toggleStripsOnHeaderClick && "cursor-pointer hover:text-zinc-800 dark:hover:text-zinc-200"
|
|
665
|
+
),
|
|
666
|
+
children: label
|
|
667
|
+
}
|
|
668
|
+
),
|
|
669
|
+
cols.map((_, c) => {
|
|
670
|
+
const on = value[r]?.[c] ?? false;
|
|
671
|
+
return /* @__PURE__ */ jsx("td", { className: "p-px", children: /* @__PURE__ */ jsx(
|
|
672
|
+
"button",
|
|
673
|
+
{
|
|
674
|
+
type: "button",
|
|
675
|
+
onMouseDown: () => {
|
|
676
|
+
const v = !on;
|
|
677
|
+
setDrag(v);
|
|
678
|
+
setCell(r, c, v);
|
|
679
|
+
},
|
|
680
|
+
onMouseEnter: () => drag !== null && setCell(r, c, drag),
|
|
681
|
+
style: { width: cellWidth, height: cellHeight },
|
|
682
|
+
"data-react-fancy-timegrid-cell": cellId ? cellId(r, c) : `${r}:${c}`,
|
|
683
|
+
"aria-pressed": on,
|
|
684
|
+
"aria-label": ariaCell ? ariaCell(r, c, on) : `${rows[r]} ${cols[c]} ${on ? "on" : "off"}`,
|
|
685
|
+
className: cn(
|
|
686
|
+
"block rounded-sm transition",
|
|
687
|
+
on ? onTone : OFF_CLASSES
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
) }, c);
|
|
691
|
+
})
|
|
692
|
+
] }, r)) })
|
|
693
|
+
] })
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
TimeGrid.displayName = "TimeGrid";
|
|
699
|
+
BooleanTimeGrid.displayName = "BooleanTimeGrid";
|
|
700
|
+
|
|
701
|
+
export { TimeGrid };
|
|
702
|
+
//# sourceMappingURL=chunk-SFJ5WOX5.js.map
|
|
703
|
+
//# sourceMappingURL=chunk-SFJ5WOX5.js.map
|