@particle-academy/react-fancy 5.54.1 → 5.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/button.cjs +3 -3
  3. package/dist/button.js +1 -1
  4. package/dist/{chunk-ZFU4FY3Q.js → chunk-4FZOH7BJ.js} +3 -3
  5. package/dist/{chunk-ZFU4FY3Q.js.map → chunk-4FZOH7BJ.js.map} +1 -1
  6. package/dist/{chunk-TCW72JBU.cjs → chunk-6XX6OHAP.cjs} +15 -15
  7. package/dist/{chunk-TCW72JBU.cjs.map → chunk-6XX6OHAP.cjs.map} +1 -1
  8. package/dist/{chunk-I2TBENUQ.cjs → chunk-7R7HENZG.cjs} +6 -6
  9. package/dist/{chunk-I2TBENUQ.cjs.map → chunk-7R7HENZG.cjs.map} +1 -1
  10. package/dist/{chunk-XOXA3QZR.cjs → chunk-C5YX5XVL.cjs} +3 -3
  11. package/dist/chunk-C5YX5XVL.cjs.map +1 -0
  12. package/dist/{chunk-EULOJOTU.js → chunk-DAJPPKPO.js} +3 -3
  13. package/dist/{chunk-EULOJOTU.js.map → chunk-DAJPPKPO.js.map} +1 -1
  14. package/dist/{chunk-REKY2CM6.js → chunk-KQYOIPHQ.js} +3 -3
  15. package/dist/{chunk-REKY2CM6.js.map → chunk-KQYOIPHQ.js.map} +1 -1
  16. package/dist/{chunk-4I4Y5MZO.cjs → chunk-LVE6ERRS.cjs} +4 -4
  17. package/dist/{chunk-4I4Y5MZO.cjs.map → chunk-LVE6ERRS.cjs.map} +1 -1
  18. package/dist/chunk-SFJ5WOX5.js +703 -0
  19. package/dist/chunk-SFJ5WOX5.js.map +1 -0
  20. package/dist/{chunk-6GKR5OAW.cjs → chunk-SIEPD6NR.cjs} +4 -4
  21. package/dist/{chunk-6GKR5OAW.cjs.map → chunk-SIEPD6NR.cjs.map} +1 -1
  22. package/dist/{chunk-45JO5BEZ.js → chunk-TLRRMYNV.js} +3 -3
  23. package/dist/chunk-TLRRMYNV.js.map +1 -0
  24. package/dist/chunk-XO4UWQCU.cjs +705 -0
  25. package/dist/chunk-XO4UWQCU.cjs.map +1 -0
  26. package/dist/{chunk-TBXZ3NGR.js → chunk-YBRQTZKV.js} +3 -3
  27. package/dist/{chunk-TBXZ3NGR.js.map → chunk-YBRQTZKV.js.map} +1 -1
  28. package/dist/index.cjs +27 -27
  29. package/dist/index.d.cts +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +6 -6
  32. package/dist/json-editor.cjs +13 -13
  33. package/dist/json-editor.js +2 -2
  34. package/dist/magic-wand.cjs +3 -3
  35. package/dist/magic-wand.js +2 -2
  36. package/dist/prompt-input.cjs +3 -3
  37. package/dist/prompt-input.js +2 -2
  38. package/dist/reason-tag.cjs +3 -3
  39. package/dist/reason-tag.js +2 -2
  40. package/dist/time-grid.cjs +3 -2
  41. package/dist/time-grid.d.cts +120 -17
  42. package/dist/time-grid.d.ts +120 -17
  43. package/dist/time-grid.js +2 -1
  44. package/package.json +1 -1
  45. package/dist/chunk-45JO5BEZ.js.map +0 -1
  46. package/dist/chunk-GF23UBOT.js +0 -129
  47. package/dist/chunk-GF23UBOT.js.map +0 -1
  48. package/dist/chunk-WFHK2DKG.cjs +0 -131
  49. package/dist/chunk-WFHK2DKG.cjs.map +0 -1
  50. package/dist/chunk-XOXA3QZR.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