@tensor_1/react 2.32.0 → 2.33.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 +17 -0
- package/dist/SchedulingCalendar-C4XVbqZr.d.ts +339 -0
- package/dist/components/SchedulingCalendar.d.ts +4 -241
- package/dist/components/SchedulingCalendar.js +73 -14
- package/dist/components/SchedulingTimeGrid.d.ts +4 -0
- package/dist/components/SchedulingTimeGrid.js +521 -0
- package/dist/components/SchedulingTimeline.d.ts +1 -1
- package/dist/index.cjs +1144 -471
- package/dist/index.d.cts +174 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -1
- package/dist/labels-context.d.ts +1 -1
- package/dist/scheduling-calendar-model.d.ts +104 -1
- package/dist/scheduling-calendar-model.js +95 -1
- package/package.json +1 -1
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { zonedDateKey, zonedHourSlots, zonedTimeOfDay } from "../calendar-model.js";
|
|
5
|
+
import { layoutDaySpans, layoutTimeGridDay, placesInTopLane, proposeMove } from "../scheduling-calendar-model.js";
|
|
6
|
+
import { fillLabel } from "../fill-label.js";
|
|
7
|
+
const HOUR_MS = 36e5;
|
|
8
|
+
const MINUTES_PER_HOUR = 60;
|
|
9
|
+
const LINES_PER_HOUR = 3;
|
|
10
|
+
const DRAG_THRESHOLD = 4;
|
|
11
|
+
const DEFAULT_STEP = 15;
|
|
12
|
+
const vars = (values) => values;
|
|
13
|
+
const linesFor = (segment) => Math.max(0, Math.floor(segment.lengthHours * LINES_PER_HOUR - 0.25));
|
|
14
|
+
const slotSignature = (slots) => slots.map((slot) => `${slot.label}${slot.offsetLabel ?? ""}`).join(" ");
|
|
15
|
+
function uniqueById(items) {
|
|
16
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17
|
+
return items.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
|
|
18
|
+
}
|
|
19
|
+
const GridItem = memo(function GridItem2({ entry, segment, column, alone, name, status, movable, hintId, timeZone, continuesFrom, markers, renderEntry, renderMarker, handlers }) {
|
|
20
|
+
const lines = linesFor(segment);
|
|
21
|
+
const continuation = segment.part === "continuation";
|
|
22
|
+
const style = vars({
|
|
23
|
+
"--uix-scheduling-calendar-offset": segment.offsetHours,
|
|
24
|
+
"--uix-scheduling-calendar-length": segment.lengthHours,
|
|
25
|
+
"--uix-scheduling-calendar-lane": segment.lane,
|
|
26
|
+
"--uix-scheduling-calendar-lanes": segment.laneCount
|
|
27
|
+
});
|
|
28
|
+
const text = renderEntry?.(entry, { availableLines: lines }) ?? (lines > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29
|
+
!continuation && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__time", children: zonedTimeOfDay(entry.start, timeZone) }),
|
|
31
|
+
" "
|
|
32
|
+
] }),
|
|
33
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__title", children: entry.title })
|
|
34
|
+
] }) : null);
|
|
35
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36
|
+
continuation && lines > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__tg-from", children: continuesFrom }),
|
|
37
|
+
(!continuation || alone) && text != null && text !== false && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__entry-text", children: text }),
|
|
38
|
+
markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: markers.map((marker) => /* @__PURE__ */ jsx("span", { children: renderMarker(marker) }, marker.id)) })
|
|
39
|
+
] });
|
|
40
|
+
const shared = {
|
|
41
|
+
className: "uix-scheduling-calendar__entry uix-scheduling-calendar__tg-item",
|
|
42
|
+
"data-band": entry.band ?? "none",
|
|
43
|
+
"data-status": status,
|
|
44
|
+
"data-state": entry.state,
|
|
45
|
+
"data-part": segment.part,
|
|
46
|
+
"data-lines": Math.min(lines, 3),
|
|
47
|
+
"data-movable": movable || void 0,
|
|
48
|
+
style,
|
|
49
|
+
onClick: () => handlers.click(entry),
|
|
50
|
+
onPointerDown: movable ? (event) => handlers.pointerDown(event, entry, column, false) : void 0
|
|
51
|
+
};
|
|
52
|
+
if (continuation && !alone) return /* @__PURE__ */ jsx("div", { ...shared, "data-continuation-of": entry.id, "aria-hidden": "true", children: body });
|
|
53
|
+
return /* @__PURE__ */ jsx(
|
|
54
|
+
"button",
|
|
55
|
+
{
|
|
56
|
+
type: "button",
|
|
57
|
+
...shared,
|
|
58
|
+
"data-item-id": entry.id,
|
|
59
|
+
"aria-label": name,
|
|
60
|
+
"aria-describedby": movable ? hintId : void 0,
|
|
61
|
+
onKeyDown: movable ? (event) => handlers.keyDown(event, entry) : void 0,
|
|
62
|
+
onBlur: movable ? () => handlers.blur(entry) : void 0,
|
|
63
|
+
children: body
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}, (previous, next) => previous.entry === next.entry && previous.column === next.column && previous.alone === next.alone && previous.name === next.name && previous.status === next.status && previous.movable === next.movable && previous.hintId === next.hintId && previous.timeZone === next.timeZone && previous.continuesFrom === next.continuesFrom && previous.renderEntry === next.renderEntry && previous.segment.part === next.segment.part && previous.segment.offsetHours === next.segment.offsetHours && previous.segment.lengthHours === next.segment.lengthHours && previous.segment.lane === next.segment.lane && previous.segment.laneCount === next.segment.laneCount);
|
|
67
|
+
function SchedulingTimeGrid({
|
|
68
|
+
view,
|
|
69
|
+
days,
|
|
70
|
+
timeZone,
|
|
71
|
+
entries: givenEntries,
|
|
72
|
+
overlays: givenOverlays,
|
|
73
|
+
dayInfo,
|
|
74
|
+
labels,
|
|
75
|
+
gridLabel,
|
|
76
|
+
dateText,
|
|
77
|
+
formatInstant,
|
|
78
|
+
entryName,
|
|
79
|
+
entryStatus,
|
|
80
|
+
entryMarkers,
|
|
81
|
+
overlayName,
|
|
82
|
+
renderEntry,
|
|
83
|
+
renderMarker,
|
|
84
|
+
onSelectEntry,
|
|
85
|
+
onSelectOverlay,
|
|
86
|
+
onSelectDate,
|
|
87
|
+
onShowMore,
|
|
88
|
+
now,
|
|
89
|
+
canMove,
|
|
90
|
+
step: givenStep,
|
|
91
|
+
onProposeMove,
|
|
92
|
+
topLaneCrossMidnightMinutes,
|
|
93
|
+
topLaneCap,
|
|
94
|
+
windowLaneCap,
|
|
95
|
+
maxLanes,
|
|
96
|
+
emptyNote,
|
|
97
|
+
nothingToShow
|
|
98
|
+
}) {
|
|
99
|
+
const rootRef = useRef(null);
|
|
100
|
+
const hintId = useId();
|
|
101
|
+
const [widthCap, setWidthCap] = useState(Infinity);
|
|
102
|
+
const [ghost, setGhost] = useState(null);
|
|
103
|
+
const [announcement, setAnnouncement] = useState("");
|
|
104
|
+
const moving = canMove && onProposeMove !== void 0;
|
|
105
|
+
const step = Number.isFinite(givenStep) && givenStep > 0 ? givenStep : DEFAULT_STEP;
|
|
106
|
+
const laneOptions = useMemo(() => ({ crossMidnightMinutes: topLaneCrossMidnightMinutes }), [topLaneCrossMidnightMinutes]);
|
|
107
|
+
const entries = useMemo(() => uniqueById(givenEntries), [givenEntries]);
|
|
108
|
+
const overlays = useMemo(() => uniqueById(givenOverlays), [givenOverlays]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const root = rootRef.current;
|
|
111
|
+
if (!root || typeof ResizeObserver === "undefined") return void 0;
|
|
112
|
+
const column = root.querySelector("[data-tg-column]");
|
|
113
|
+
const probe = root.querySelector(".uix-scheduling-calendar__tg-probe");
|
|
114
|
+
if (!column || !probe) return void 0;
|
|
115
|
+
let frame = 0;
|
|
116
|
+
const measure = () => {
|
|
117
|
+
frame = 0;
|
|
118
|
+
if (!column.isConnected) return;
|
|
119
|
+
const lane = probe.getBoundingClientRect().width;
|
|
120
|
+
const width = column.getBoundingClientRect().width;
|
|
121
|
+
setWidthCap(lane > 0 && width > 0 ? Math.max(1, Math.floor(width / lane)) : Infinity);
|
|
122
|
+
};
|
|
123
|
+
const observer = new ResizeObserver(() => {
|
|
124
|
+
if (!frame) frame = requestAnimationFrame(measure);
|
|
125
|
+
});
|
|
126
|
+
observer.observe(column);
|
|
127
|
+
measure();
|
|
128
|
+
return () => {
|
|
129
|
+
observer.disconnect();
|
|
130
|
+
if (frame) cancelAnimationFrame(frame);
|
|
131
|
+
};
|
|
132
|
+
}, [days]);
|
|
133
|
+
const lanes = Math.max(1, Math.min(maxLanes, widthCap));
|
|
134
|
+
const entryById = useMemo(() => new Map(entries.map((entry) => [entry.id, entry])), [entries]);
|
|
135
|
+
const overlayById = useMemo(() => new Map(overlays.map((overlay) => [overlay.id, overlay])), [overlays]);
|
|
136
|
+
const columns = useMemo(() => days.map((date) => layoutTimeGridDay(entries, date, timeZone, { ...laneOptions, maxLanes: lanes })), [days, entries, timeZone, laneOptions, lanes]);
|
|
137
|
+
const slots = useMemo(() => days.map((date) => zonedHourSlots(date, timeZone)), [days, timeZone]);
|
|
138
|
+
const top = useMemo(() => layoutDaySpans(entries.filter((entry) => placesInTopLane(entry, timeZone, laneOptions)), days, { timeZone, laneCap: Math.max(0, topLaneCap) }), [entries, days, timeZone, laneOptions, topLaneCap]);
|
|
139
|
+
const drawable = useMemo(() => overlays.filter((overlay) => new Date(overlay.end).getTime() >= new Date(overlay.start).getTime()), [overlays]);
|
|
140
|
+
const strip = useMemo(() => layoutDaySpans(drawable, days, { timeZone, laneCap: Math.max(0, windowLaneCap) }), [drawable, days, timeZone, windowLaneCap]);
|
|
141
|
+
const shades = useMemo(() => layoutDaySpans(drawable.filter((overlay) => overlay.global), days, { timeZone }).placed, [drawable, days, timeZone]);
|
|
142
|
+
const lanesIn = (placed) => placed.reduce((max, span) => Math.max(max, span.lane + 1), 0);
|
|
143
|
+
const maxHours = Math.max(...columns.map((column) => column.dayHours));
|
|
144
|
+
const gutterIndex = useMemo(() => {
|
|
145
|
+
const signatures = slots.map(slotSignature);
|
|
146
|
+
const count = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const signature of signatures) count.set(signature, (count.get(signature) ?? 0) + 1);
|
|
148
|
+
let best = 0;
|
|
149
|
+
signatures.forEach((signature, index) => {
|
|
150
|
+
if ((count.get(signature) ?? 0) > (count.get(signatures[best]) ?? 0)) best = index;
|
|
151
|
+
});
|
|
152
|
+
return best;
|
|
153
|
+
}, [slots]);
|
|
154
|
+
const gutterSignature = slotSignature(slots[gutterIndex]);
|
|
155
|
+
const hourLabels = (index, inline) => slots[index].map((slot) => /* @__PURE__ */ jsxs(
|
|
156
|
+
"div",
|
|
157
|
+
{
|
|
158
|
+
className: inline ? "uix-scheduling-calendar__tg-hour uix-scheduling-calendar__tg-hour--inline" : "uix-scheduling-calendar__tg-hour",
|
|
159
|
+
"data-hour": slot.label,
|
|
160
|
+
style: vars({ "--uix-scheduling-calendar-offset": (slot.instant.getTime() - columns[index].dayStart) / HOUR_MS }),
|
|
161
|
+
children: [
|
|
162
|
+
slot.label,
|
|
163
|
+
slot.offsetLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__tg-offset", children: slot.offsetLabel })
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
slot.instant.getTime()
|
|
167
|
+
));
|
|
168
|
+
const movable = (entry) => moving && entry.movable !== false;
|
|
169
|
+
const live = useRef({ ghost, entryById, timeZone, step, onProposeMove, onSelectEntry, labels, formatInstant, days, columns, movable });
|
|
170
|
+
live.current = { ghost, entryById, timeZone, step, onProposeMove, onSelectEntry, labels, formatInstant, days, columns, movable };
|
|
171
|
+
const dragRef = useRef(null);
|
|
172
|
+
const suppressClick = useRef(false);
|
|
173
|
+
const refocus = useRef(null);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (ghost && !entryById.has(ghost.id)) setGhost(null);
|
|
176
|
+
}, [ghost, entryById]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const id = refocus.current;
|
|
179
|
+
const root = rootRef.current;
|
|
180
|
+
if (!id || !root) return;
|
|
181
|
+
const active = document.activeElement;
|
|
182
|
+
const lost = !active || active === document.body;
|
|
183
|
+
if (!lost) {
|
|
184
|
+
if (active.getAttribute("data-item-id") !== id) refocus.current = null;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const candidate of Array.from(root.querySelectorAll("[data-item-id]"))) {
|
|
188
|
+
if (candidate.getAttribute("data-item-id") === id) {
|
|
189
|
+
candidate.focus();
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const send = useCallback((id, delta) => {
|
|
195
|
+
const state = live.current;
|
|
196
|
+
const entry = state.entryById.get(id);
|
|
197
|
+
if (!entry || !state.onProposeMove || !state.movable(entry) || delta.days === 0 && delta.minutes === 0) {
|
|
198
|
+
setGhost(null);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const proposal = proposeMove(entry, delta, state.timeZone);
|
|
202
|
+
const result = state.onProposeMove(id, proposal);
|
|
203
|
+
if (result && typeof result.then === "function") {
|
|
204
|
+
setGhost({ id, delta, source: "sent", proposal });
|
|
205
|
+
const clear = () => setGhost((current) => current && current.source === "sent" && current.proposal === proposal ? null : current);
|
|
206
|
+
result.then(clear, clear);
|
|
207
|
+
} else setGhost(null);
|
|
208
|
+
}, []);
|
|
209
|
+
const handlers = useMemo(() => ({
|
|
210
|
+
click: (entry) => {
|
|
211
|
+
live.current.onSelectEntry?.(entry);
|
|
212
|
+
},
|
|
213
|
+
keyDown: (event, entry) => {
|
|
214
|
+
const state = live.current;
|
|
215
|
+
const pending = state.ghost && state.ghost.id === entry.id && state.ghost.source === "keys" ? state.ghost.delta : null;
|
|
216
|
+
if (event.key === "Enter" && pending) {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
refocus.current = entry.id;
|
|
219
|
+
send(entry.id, pending);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (event.key === "Escape" && pending) {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
event.stopPropagation();
|
|
225
|
+
setGhost(null);
|
|
226
|
+
setAnnouncement(state.labels.moveCancelled);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (!event.shiftKey) return;
|
|
230
|
+
const base = pending ?? { days: 0, minutes: 0 };
|
|
231
|
+
const next = event.key === "ArrowUp" ? { ...base, minutes: base.minutes - state.step } : event.key === "ArrowDown" ? { ...base, minutes: base.minutes + state.step } : event.key === "ArrowLeft" ? { ...base, days: base.days - 1 } : event.key === "ArrowRight" ? { ...base, days: base.days + 1 } : null;
|
|
232
|
+
if (!next) return;
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
if (next.days === 0 && next.minutes === 0) {
|
|
235
|
+
setGhost(null);
|
|
236
|
+
setAnnouncement(state.labels.moveCancelled);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const proposal = proposeMove(entry, next, state.timeZone);
|
|
240
|
+
setGhost({ id: entry.id, delta: next, source: "keys", proposal });
|
|
241
|
+
const proposed = fillLabel(state.labels.moveProposed, { start: state.formatInstant(proposal.start), end: state.formatInstant(proposal.end) });
|
|
242
|
+
setAnnouncement(proposal.adjusted ? `${fillLabel(state.labels.gapForward, { time: zonedTimeOfDay(proposal.start, state.timeZone) })} ${proposed}` : proposed);
|
|
243
|
+
},
|
|
244
|
+
blur: (entry) => {
|
|
245
|
+
refocus.current = null;
|
|
246
|
+
setGhost((current) => current && current.id === entry.id && current.source === "keys" ? null : current);
|
|
247
|
+
},
|
|
248
|
+
pointerDown: (event, entry, column, top2) => {
|
|
249
|
+
if (event.button !== 0 || dragRef.current) return;
|
|
250
|
+
const root = rootRef.current;
|
|
251
|
+
if (!root) return;
|
|
252
|
+
const elements = Array.from(root.querySelectorAll("[data-tg-column]"));
|
|
253
|
+
const rects = elements.map((element) => element.getBoundingClientRect());
|
|
254
|
+
const under = rects.findIndex((rect) => event.clientX >= rect.left && event.clientX < rect.right);
|
|
255
|
+
const from = under === -1 ? column : under;
|
|
256
|
+
const hours = live.current.columns[from]?.dayHours ?? 1;
|
|
257
|
+
const finish = (commit) => {
|
|
258
|
+
const drag = dragRef.current;
|
|
259
|
+
if (!drag) return;
|
|
260
|
+
drag.stop();
|
|
261
|
+
dragRef.current = null;
|
|
262
|
+
if (!drag.moved) return;
|
|
263
|
+
suppressClick.current = true;
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
suppressClick.current = false;
|
|
266
|
+
}, 0);
|
|
267
|
+
if (commit && !drag.dropped) send(drag.id, drag.delta);
|
|
268
|
+
else setGhost(null);
|
|
269
|
+
};
|
|
270
|
+
const onMove = (move) => {
|
|
271
|
+
const drag = dragRef.current;
|
|
272
|
+
if (!drag || move.pointerId !== drag.pointerId) return;
|
|
273
|
+
if (move.buttons === 0 && move.pointerType === "mouse") {
|
|
274
|
+
finish(false);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (drag.dropped) return;
|
|
278
|
+
const dx = move.clientX - drag.x;
|
|
279
|
+
const dy = move.clientY - drag.y;
|
|
280
|
+
if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
281
|
+
drag.moved = true;
|
|
282
|
+
const target = drag.rects.findIndex((rect) => move.clientX >= rect.left && move.clientX < rect.right);
|
|
283
|
+
const stepMinutes = live.current.step;
|
|
284
|
+
const delta = {
|
|
285
|
+
days: target === -1 ? drag.delta.days : target - drag.column,
|
|
286
|
+
minutes: drag.top || drag.hourPx <= 0 ? 0 : Math.round(dy / drag.hourPx * MINUTES_PER_HOUR / stepMinutes) * stepMinutes
|
|
287
|
+
};
|
|
288
|
+
if (delta.days === drag.delta.days && delta.minutes === drag.delta.minutes) return;
|
|
289
|
+
drag.delta = delta;
|
|
290
|
+
const dragged = live.current.entryById.get(drag.id);
|
|
291
|
+
if (dragged) setGhost({ id: drag.id, delta, source: "pointer", proposal: proposeMove(dragged, delta, live.current.timeZone) });
|
|
292
|
+
};
|
|
293
|
+
const onUp = (up) => {
|
|
294
|
+
if (up.pointerId === dragRef.current?.pointerId) finish(true);
|
|
295
|
+
};
|
|
296
|
+
const onCancel = (cancel) => {
|
|
297
|
+
if (cancel.pointerId === dragRef.current?.pointerId) finish(false);
|
|
298
|
+
};
|
|
299
|
+
const onKey = (key) => {
|
|
300
|
+
const drag = dragRef.current;
|
|
301
|
+
if (key.key !== "Escape" || !drag?.moved || drag.dropped) return;
|
|
302
|
+
key.preventDefault();
|
|
303
|
+
key.stopPropagation();
|
|
304
|
+
drag.dropped = true;
|
|
305
|
+
setGhost(null);
|
|
306
|
+
};
|
|
307
|
+
window.addEventListener("pointermove", onMove);
|
|
308
|
+
window.addEventListener("pointerup", onUp);
|
|
309
|
+
window.addEventListener("pointercancel", onCancel);
|
|
310
|
+
window.addEventListener("keydown", onKey, true);
|
|
311
|
+
dragRef.current = {
|
|
312
|
+
id: entry.id,
|
|
313
|
+
pointerId: event.pointerId,
|
|
314
|
+
x: event.clientX,
|
|
315
|
+
y: event.clientY,
|
|
316
|
+
column: from,
|
|
317
|
+
top: top2,
|
|
318
|
+
moved: false,
|
|
319
|
+
dropped: false,
|
|
320
|
+
rects,
|
|
321
|
+
hourPx: (rects[from]?.height ?? 0) / hours,
|
|
322
|
+
delta: { days: 0, minutes: 0 },
|
|
323
|
+
stop: () => {
|
|
324
|
+
window.removeEventListener("pointermove", onMove);
|
|
325
|
+
window.removeEventListener("pointerup", onUp);
|
|
326
|
+
window.removeEventListener("pointercancel", onCancel);
|
|
327
|
+
window.removeEventListener("keydown", onKey, true);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}), [send]);
|
|
332
|
+
useEffect(() => () => {
|
|
333
|
+
dragRef.current?.stop();
|
|
334
|
+
dragRef.current = null;
|
|
335
|
+
}, []);
|
|
336
|
+
const gridColumns = (span) => `${span.startCol + 2} / ${span.endCol + 3}`;
|
|
337
|
+
const nowAt = now === void 0 ? null : new Date(now).getTime();
|
|
338
|
+
const nowColumn = nowAt === null || Number.isNaN(nowAt) ? -1 : days.indexOf(zonedDateKey(new Date(nowAt), timeZone));
|
|
339
|
+
const topHiddenOn = (date) => top.hidden.filter((id) => {
|
|
340
|
+
const entry = entryById.get(id);
|
|
341
|
+
return entry ? layoutDaySpans([entry], [date], { timeZone }).placed.length > 0 : false;
|
|
342
|
+
});
|
|
343
|
+
const windowLanes = lanesIn(strip.placed);
|
|
344
|
+
const topLanes = lanesIn(top.placed);
|
|
345
|
+
const moreWindows = strip.hidden.length;
|
|
346
|
+
const firstHiddenWindowDay = strip.firstHiddenDay ?? days[0];
|
|
347
|
+
const ghostEntry = ghost ? entryById.get(ghost.id) : void 0;
|
|
348
|
+
const ghostTop = ghostEntry ? placesInTopLane(ghostEntry, timeZone, laneOptions) : false;
|
|
349
|
+
const ghostParts = ghost && ghostEntry && !ghostTop ? days.map((date) => layoutTimeGridDay([{ id: "ghost", start: ghost.proposal.start, end: ghost.proposal.end }], date, timeZone, { crossMidnightMinutes: Infinity }).segments) : null;
|
|
350
|
+
const ghostSpan = ghost && ghostEntry && ghostTop ? layoutDaySpans([{ id: "ghost", start: ghost.proposal.start, end: ghost.proposal.end }], days, { timeZone }).placed[0] : void 0;
|
|
351
|
+
const ghostLane = ghost ? top.placed.find((span) => span.id === ghost.id)?.lane ?? 0 : 0;
|
|
352
|
+
return /* @__PURE__ */ jsxs(
|
|
353
|
+
"div",
|
|
354
|
+
{
|
|
355
|
+
ref: rootRef,
|
|
356
|
+
className: "uix-scheduling-calendar__timegrid",
|
|
357
|
+
"data-view": view,
|
|
358
|
+
role: "group",
|
|
359
|
+
"aria-label": gridLabel,
|
|
360
|
+
style: vars({ "--uix-scheduling-calendar-days": days.length, "--uix-scheduling-calendar-hours": maxHours }),
|
|
361
|
+
onClickCapture: (event) => {
|
|
362
|
+
if (suppressClick.current) {
|
|
363
|
+
suppressClick.current = false;
|
|
364
|
+
event.preventDefault();
|
|
365
|
+
event.stopPropagation();
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-head", children: [
|
|
370
|
+
/* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__tg-zone", children: fillLabel(labels.timeZone, { timeZone }) }),
|
|
371
|
+
days.map((date, index) => {
|
|
372
|
+
const info = dayInfo?.[date];
|
|
373
|
+
const hiddenIds = dayInfo ? [] : [...columns[index].hidden, ...topHiddenOn(date)];
|
|
374
|
+
const more = info ? info.overflowCount : hiddenIds.length;
|
|
375
|
+
const moreText = fillLabel(labels.moreSpans, { count: more });
|
|
376
|
+
const moreName = fillLabel(labels.moreEntriesLabel, { count: more, date: dateText(date, "day") });
|
|
377
|
+
const headName = info ? info.label ?? `${dateText(date, "day")}, ${fillLabel(labels.dayCount, { count: info.count })}` : dateText(date, "day");
|
|
378
|
+
return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-dayhead", role: "group", "aria-label": headName, "data-date": date, children: [
|
|
379
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, "aria-label": dateText(date, "day"), onClick: onSelectDate ? () => onSelectDate(date) : void 0, children: dateText(date, "column") }),
|
|
380
|
+
info && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__count", "aria-hidden": "true", children: info.count }),
|
|
381
|
+
info?.markers && info.markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: info.markers.map((marker) => /* @__PURE__ */ jsx("span", { children: renderMarker(marker, true) }, marker.id)) }),
|
|
382
|
+
more > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, hiddenIds.map((id) => entryById.get(id)).filter(Boolean)), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
|
|
383
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
|
|
384
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
|
|
385
|
+
] }))
|
|
386
|
+
] }, date);
|
|
387
|
+
})
|
|
388
|
+
] }),
|
|
389
|
+
(windowLanes > 0 || moreWindows > 0) && /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-strip", role: "group", "aria-label": labels.windows, style: vars({ "--uix-scheduling-calendar-lanes": Math.max(windowLanes, 1) }), children: [
|
|
390
|
+
strip.placed.map((placed) => {
|
|
391
|
+
const overlay = overlayById.get(placed.id);
|
|
392
|
+
if (!overlay) return null;
|
|
393
|
+
return /* @__PURE__ */ jsx(
|
|
394
|
+
"button",
|
|
395
|
+
{
|
|
396
|
+
type: "button",
|
|
397
|
+
className: "uix-scheduling-calendar__window",
|
|
398
|
+
"data-overlay-id": overlay.id,
|
|
399
|
+
"data-pattern": overlay.pattern ?? "solid",
|
|
400
|
+
"data-global": overlay.global || void 0,
|
|
401
|
+
"data-kind": overlay.kind,
|
|
402
|
+
"data-continues-before": placed.continuesBefore || void 0,
|
|
403
|
+
"data-continues-after": placed.continuesAfter || void 0,
|
|
404
|
+
style: { gridColumn: gridColumns(placed), ...vars({ "--uix-scheduling-calendar-lane": placed.lane }) },
|
|
405
|
+
onClick: () => onSelectOverlay?.(overlay),
|
|
406
|
+
"aria-label": overlayName(overlay),
|
|
407
|
+
children: /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__window-text", children: [
|
|
408
|
+
overlay.kindLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-kind", children: overlay.kindLabel }),
|
|
409
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-name", children: overlay.label }),
|
|
410
|
+
overlay.scopeLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-scope", children: overlay.scopeLabel })
|
|
411
|
+
] })
|
|
412
|
+
},
|
|
413
|
+
placed.id
|
|
414
|
+
);
|
|
415
|
+
}),
|
|
416
|
+
moreWindows > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__rowmore", "aria-label": fillLabel(labels.moreWindowsLabel, { count: moreWindows, date: dateText(firstHiddenWindowDay, "day") }), onClick: () => onShowMore(firstHiddenWindowDay, []), children: fillLabel(labels.moreWindows, { count: moreWindows }) }) : /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__rowmore", children: fillLabel(labels.moreWindows, { count: moreWindows }) }))
|
|
417
|
+
] }),
|
|
418
|
+
(topLanes > 0 || ghostSpan) && /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-strip uix-scheduling-calendar__tg-top", role: "group", "aria-label": labels.topLane, style: vars({ "--uix-scheduling-calendar-lanes": Math.max(topLanes, 1) }), children: [
|
|
419
|
+
top.placed.map((placed) => {
|
|
420
|
+
const entry = entryById.get(placed.id);
|
|
421
|
+
if (!entry) return null;
|
|
422
|
+
const canDrag = movable(entry);
|
|
423
|
+
const markers = entryMarkers(entry);
|
|
424
|
+
return /* @__PURE__ */ jsxs(
|
|
425
|
+
"button",
|
|
426
|
+
{
|
|
427
|
+
type: "button",
|
|
428
|
+
className: "uix-scheduling-calendar__entry uix-scheduling-calendar__tg-span",
|
|
429
|
+
"data-item-id": entry.id,
|
|
430
|
+
"data-band": entry.band ?? "none",
|
|
431
|
+
"data-status": entryStatus(entry),
|
|
432
|
+
"data-state": entry.state,
|
|
433
|
+
"data-movable": canDrag || void 0,
|
|
434
|
+
"data-continues-before": placed.continuesBefore || void 0,
|
|
435
|
+
"data-continues-after": placed.continuesAfter || void 0,
|
|
436
|
+
style: { gridColumn: gridColumns(placed), ...vars({ "--uix-scheduling-calendar-lane": placed.lane }) },
|
|
437
|
+
"aria-label": entryName(entry),
|
|
438
|
+
"aria-describedby": canDrag ? hintId : void 0,
|
|
439
|
+
onClick: () => handlers.click(entry),
|
|
440
|
+
onKeyDown: canDrag ? (event) => handlers.keyDown(event, entry) : void 0,
|
|
441
|
+
onBlur: canDrag ? () => handlers.blur(entry) : void 0,
|
|
442
|
+
onPointerDown: canDrag ? (event) => handlers.pointerDown(event, entry, placed.startCol, true) : void 0,
|
|
443
|
+
children: [
|
|
444
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__entry-text", children: renderEntry?.(entry, { availableLines: 1 }) ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
445
|
+
!placed.continuesBefore && !entry.allDay && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
446
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__time", children: zonedTimeOfDay(entry.start, timeZone) }),
|
|
447
|
+
" "
|
|
448
|
+
] }),
|
|
449
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__title", children: entry.title })
|
|
450
|
+
] }) }),
|
|
451
|
+
markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: markers.map((marker) => /* @__PURE__ */ jsx("span", { children: renderMarker(marker) }, marker.id)) })
|
|
452
|
+
]
|
|
453
|
+
},
|
|
454
|
+
placed.id
|
|
455
|
+
);
|
|
456
|
+
}),
|
|
457
|
+
ghostSpan && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__tg-ghost", "aria-hidden": "true", "data-sent": ghost?.source === "sent" || void 0, style: { gridColumn: gridColumns(ghostSpan), ...vars({ "--uix-scheduling-calendar-lane": ghostLane }) } })
|
|
458
|
+
] }),
|
|
459
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-body", children: [
|
|
460
|
+
/* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__tg-gutter", "aria-hidden": "true", children: hourLabels(gutterIndex, false) }),
|
|
461
|
+
shades.map((placed) => {
|
|
462
|
+
const overlay = overlayById.get(placed.id);
|
|
463
|
+
const first = columns[placed.startCol];
|
|
464
|
+
const last = columns[placed.endCol];
|
|
465
|
+
const from = placed.continuesBefore ? 0 : Math.max(0, (new Date(overlay.start).getTime() - first.dayStart) / HOUR_MS) / maxHours;
|
|
466
|
+
const to = (placed.continuesAfter ? last.dayHours : Math.min(last.dayHours, (new Date(overlay.end).getTime() - last.dayStart) / HOUR_MS)) / maxHours;
|
|
467
|
+
const width = 100 / (placed.endCol - placed.startCol + 1);
|
|
468
|
+
const percent = (value) => `${Math.round(value * 1e4) / 100}%`;
|
|
469
|
+
const clipPath = placed.startCol === placed.endCol ? `polygon(0 ${percent(from)}, 100% ${percent(from)}, 100% ${percent(to)}, 0 ${percent(to)})` : `polygon(0 ${percent(from)}, ${width}% ${percent(from)}, ${width}% 0, 100% 0, 100% ${percent(to)}, ${100 - width}% ${percent(to)}, ${100 - width}% 100%, 0 100%)`;
|
|
470
|
+
return /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__tg-shade", "aria-hidden": "true", "data-pattern": overlay.pattern ?? "solid", style: { gridColumn: gridColumns(placed), clipPath } }, `shade-${placed.id}`);
|
|
471
|
+
}),
|
|
472
|
+
columns.map((column, index) => /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-column", "data-tg-column": column.date, style: { gridColumn: `${index + 2}`, ...vars({ "--uix-scheduling-calendar-hours": column.dayHours }) }, children: [
|
|
473
|
+
slotSignature(slots[index]) !== gutterSignature && hourLabels(index, true),
|
|
474
|
+
column.segments.map((segment) => {
|
|
475
|
+
const entry = entryById.get(segment.id);
|
|
476
|
+
const alone = segment.part === "continuation" && index === 0;
|
|
477
|
+
return /* @__PURE__ */ jsx(
|
|
478
|
+
GridItem,
|
|
479
|
+
{
|
|
480
|
+
entry,
|
|
481
|
+
segment,
|
|
482
|
+
column: index,
|
|
483
|
+
alone,
|
|
484
|
+
name: entryName(entry),
|
|
485
|
+
status: entryStatus(entry),
|
|
486
|
+
movable: movable(entry),
|
|
487
|
+
hintId,
|
|
488
|
+
timeZone,
|
|
489
|
+
continuesFrom: fillLabel(labels.continuesFrom, { time: zonedTimeOfDay(entry.start, timeZone) }),
|
|
490
|
+
markers: entryMarkers(entry),
|
|
491
|
+
renderEntry,
|
|
492
|
+
renderMarker,
|
|
493
|
+
handlers
|
|
494
|
+
},
|
|
495
|
+
`${segment.id}-${segment.part}`
|
|
496
|
+
);
|
|
497
|
+
}),
|
|
498
|
+
ghostParts?.[index].map((segment) => /* @__PURE__ */ jsx(
|
|
499
|
+
"div",
|
|
500
|
+
{
|
|
501
|
+
className: "uix-scheduling-calendar__tg-ghost",
|
|
502
|
+
"aria-hidden": "true",
|
|
503
|
+
"data-sent": ghost?.source === "sent" || void 0,
|
|
504
|
+
style: vars({ "--uix-scheduling-calendar-offset": segment.offsetHours, "--uix-scheduling-calendar-length": segment.lengthHours })
|
|
505
|
+
},
|
|
506
|
+
`ghost-${segment.part}`
|
|
507
|
+
)),
|
|
508
|
+
index === nowColumn && nowAt !== null && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__tg-now", "aria-hidden": "true", style: vars({ "--uix-scheduling-calendar-offset": (nowAt - column.dayStart) / HOUR_MS }) })
|
|
509
|
+
] }, column.date))
|
|
510
|
+
] }),
|
|
511
|
+
emptyNote != null && emptyNote !== false && nothingToShow && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__empty", children: emptyNote }),
|
|
512
|
+
moving && /* @__PURE__ */ jsx("p", { id: hintId, className: "uix-visually-hidden", children: labels.moveHint }),
|
|
513
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__tg-probe", "aria-hidden": "true", children: "00:00 abcdef" }),
|
|
514
|
+
/* @__PURE__ */ jsx("div", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
|
|
515
|
+
]
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
export {
|
|
520
|
+
SchedulingTimeGrid
|
|
521
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { TimelineRange, TimelineScale } from '../scheduling-timeline-model.js';
|
|
4
|
-
import { SchedulingEntryState } from '
|
|
4
|
+
import { i as SchedulingEntryState } from '../SchedulingCalendar-C4XVbqZr.js';
|
|
5
5
|
import '../calendar-model.js';
|
|
6
6
|
import '../scheduling-calendar-model.js';
|
|
7
7
|
|