@underverse-ui/underverse 0.2.98 → 0.2.99
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/dist/index.cjs +152 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +152 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8121,6 +8121,22 @@ function startOfZonedDay(date, timeZone) {
|
|
|
8121
8121
|
const p = getZonedParts(date, timeZone);
|
|
8122
8122
|
return new Date(zonedTimeToUtcMs({ ...p, hour: 0, minute: 0, second: 0 }, timeZone));
|
|
8123
8123
|
}
|
|
8124
|
+
function zonedDateAtTime(date, timeZone, time) {
|
|
8125
|
+
const p = getZonedParts(date, timeZone);
|
|
8126
|
+
return new Date(
|
|
8127
|
+
zonedTimeToUtcMs(
|
|
8128
|
+
{
|
|
8129
|
+
year: p.year,
|
|
8130
|
+
month: p.month,
|
|
8131
|
+
day: p.day,
|
|
8132
|
+
hour: time.hour,
|
|
8133
|
+
minute: time.minute ?? 0,
|
|
8134
|
+
second: time.second ?? 0
|
|
8135
|
+
},
|
|
8136
|
+
timeZone
|
|
8137
|
+
)
|
|
8138
|
+
);
|
|
8139
|
+
}
|
|
8124
8140
|
function startOfZonedMonth(date, timeZone) {
|
|
8125
8141
|
const p = getZonedParts(date, timeZone);
|
|
8126
8142
|
return new Date(zonedTimeToUtcMs({ year: p.year, month: p.month, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
|
|
@@ -8429,17 +8445,24 @@ function getGroupResourceCounts(resources) {
|
|
|
8429
8445
|
return counts;
|
|
8430
8446
|
}
|
|
8431
8447
|
function computeSlotStarts(args) {
|
|
8432
|
-
const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes } = args;
|
|
8433
|
-
const
|
|
8448
|
+
const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours } = args;
|
|
8449
|
+
const baseDayStart = startOfZonedDay(date, timeZone);
|
|
8450
|
+
const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : baseDayStart;
|
|
8434
8451
|
if (view === "day") {
|
|
8435
8452
|
const step = Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes)));
|
|
8436
8453
|
const stepMs = step * 6e4;
|
|
8437
|
-
const
|
|
8454
|
+
const hours = workHours ?? { startHour: 8, endHour: 17 };
|
|
8455
|
+
const boundedStartHour = clamp3(Math.trunc(hours.startHour), 0, 23);
|
|
8456
|
+
const boundedEndHour = clamp3(Math.trunc(hours.endHour), 1, 24);
|
|
8457
|
+
const isWork = dayRangeMode === "work";
|
|
8458
|
+
const start2 = isWork ? zonedDateAtTime(baseDayStart, timeZone, { hour: boundedStartHour }) : start;
|
|
8459
|
+
const end2 = isWork ? boundedEndHour === 24 ? addZonedDays(baseDayStart, 1, timeZone) : zonedDateAtTime(baseDayStart, timeZone, { hour: clamp3(boundedEndHour, 0, 23) }) : addZonedDays(start, 1, timeZone);
|
|
8460
|
+
const end3 = end2.getTime() > start2.getTime() ? end2 : addZonedDays(start2, 1, timeZone);
|
|
8438
8461
|
const slotStarts2 = [];
|
|
8439
|
-
for (let cur2 =
|
|
8462
|
+
for (let cur2 = start2.getTime(), guard2 = 0; cur2 < end3.getTime() && guard2++ < 2e3; cur2 += stepMs) {
|
|
8440
8463
|
slotStarts2.push(new Date(cur2));
|
|
8441
8464
|
}
|
|
8442
|
-
return { start, end:
|
|
8465
|
+
return { start: start2, end: end3, slotStarts: slotStarts2 };
|
|
8443
8466
|
}
|
|
8444
8467
|
const end = view === "month" ? startOfZonedMonth(addZonedMonths(start, 1, timeZone), timeZone) : addZonedDays(start, 7, timeZone);
|
|
8445
8468
|
const slotStarts = [];
|
|
@@ -8739,6 +8762,8 @@ function CalendarTimeline({
|
|
|
8739
8762
|
enableLayoutResize,
|
|
8740
8763
|
slotMinWidth,
|
|
8741
8764
|
dayTimeStepMinutes = 60,
|
|
8765
|
+
dayRangeMode,
|
|
8766
|
+
workHours,
|
|
8742
8767
|
maxLanesPerRow = 3,
|
|
8743
8768
|
now,
|
|
8744
8769
|
renderResource,
|
|
@@ -8900,7 +8925,9 @@ function CalendarTimeline({
|
|
|
8900
8925
|
date: activeDate,
|
|
8901
8926
|
timeZone: resolvedTimeZone,
|
|
8902
8927
|
weekStartsOn,
|
|
8903
|
-
dayTimeStepMinutes
|
|
8928
|
+
dayTimeStepMinutes,
|
|
8929
|
+
dayRangeMode,
|
|
8930
|
+
workHours
|
|
8904
8931
|
});
|
|
8905
8932
|
const todayStart = startOfZonedDay(resolvedNow, resolvedTimeZone).getTime();
|
|
8906
8933
|
const slotItems = slotStarts2.map((s) => ({
|
|
@@ -8909,7 +8936,7 @@ function CalendarTimeline({
|
|
|
8909
8936
|
isToday: startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart
|
|
8910
8937
|
}));
|
|
8911
8938
|
return { slots: slotItems, range: { start, end } };
|
|
8912
|
-
}, [activeView, activeDate, resolvedTimeZone, resolvedLocale, weekStartsOn, dayTimeStepMinutes, resolvedNow, formatters]);
|
|
8939
|
+
}, [activeView, activeDate, resolvedTimeZone, resolvedLocale, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours, resolvedNow, formatters]);
|
|
8913
8940
|
React28.useEffect(() => {
|
|
8914
8941
|
onRangeChange?.(range);
|
|
8915
8942
|
}, [range.start, range.end, onRangeChange]);
|
|
@@ -8919,7 +8946,7 @@ function CalendarTimeline({
|
|
|
8919
8946
|
const bodyClientWidth = useClientWidth(bodyRef);
|
|
8920
8947
|
const slotStarts = React28.useMemo(() => slots.map((s) => s.start), [slots]);
|
|
8921
8948
|
const slotWidth = React28.useMemo(() => {
|
|
8922
|
-
const baseSlotWidth = activeView === "month" ? effectiveSlotMinWidth * 3 : effectiveSlotMinWidth;
|
|
8949
|
+
const baseSlotWidth = activeView === "month" ? effectiveSlotMinWidth * 3 : activeView === "day" ? effectiveSlotMinWidth * 3 : effectiveSlotMinWidth;
|
|
8923
8950
|
if (activeView !== "week") return baseSlotWidth;
|
|
8924
8951
|
if (bodyClientWidth <= 0) return baseSlotWidth;
|
|
8925
8952
|
if (slots.length <= 0) return baseSlotWidth;
|
|
@@ -9180,18 +9207,33 @@ function CalendarTimeline({
|
|
|
9180
9207
|
const dragRef = React28.useRef(null);
|
|
9181
9208
|
const [preview, setPreview] = React28.useState(null);
|
|
9182
9209
|
const suppressNextEventClickRef = React28.useRef(false);
|
|
9210
|
+
const autoScrollStateRef = React28.useRef({
|
|
9211
|
+
dir: 0,
|
|
9212
|
+
speed: 0,
|
|
9213
|
+
lastClientX: 0,
|
|
9214
|
+
lastClientY: 0
|
|
9215
|
+
});
|
|
9216
|
+
const autoScrollRafRef = React28.useRef(null);
|
|
9217
|
+
const stopAutoScroll = React28.useCallback(() => {
|
|
9218
|
+
if (autoScrollRafRef.current != null) cancelAnimationFrame(autoScrollRafRef.current);
|
|
9219
|
+
autoScrollRafRef.current = null;
|
|
9220
|
+
autoScrollStateRef.current.dir = 0;
|
|
9221
|
+
autoScrollStateRef.current.speed = 0;
|
|
9222
|
+
}, []);
|
|
9183
9223
|
const getPointerContext = React28.useCallback(
|
|
9184
9224
|
(clientX, clientY, opts) => {
|
|
9185
9225
|
const body = bodyRef.current;
|
|
9186
9226
|
if (!body) return null;
|
|
9187
|
-
const el = document.elementFromPoint(clientX, clientY);
|
|
9188
|
-
if (!el || !body.contains(el)) return null;
|
|
9189
9227
|
const bodyRect = body.getBoundingClientRect();
|
|
9190
|
-
const
|
|
9228
|
+
const probeX = clamp3(clientX, bodyRect.left + 1, bodyRect.right - 1);
|
|
9229
|
+
const probeY = clamp3(clientY, bodyRect.top + 1, bodyRect.bottom - 1);
|
|
9230
|
+
const el = document.elementFromPoint(probeX, probeY);
|
|
9231
|
+
const x = probeX - bodyRect.left + body.scrollLeft;
|
|
9191
9232
|
const epsilon = opts?.biasLeft ? 0.01 : 0;
|
|
9192
9233
|
const slotIdx = clamp3(Math.floor((x - epsilon) / slotWidth), 0, Math.max(0, slots.length - 1));
|
|
9193
|
-
const rowEl = el
|
|
9194
|
-
const rid = rowEl?.dataset?.uvCtRow ?? null;
|
|
9234
|
+
const rowEl = el && body.contains(el) ? el.closest?.("[data-uv-ct-row]") ?? null : null;
|
|
9235
|
+
const rid = rowEl?.dataset?.uvCtRow ?? opts?.fallbackResourceId ?? null;
|
|
9236
|
+
if (!rid) return null;
|
|
9195
9237
|
return { slotIdx, resourceId: rid, x };
|
|
9196
9238
|
},
|
|
9197
9239
|
[slotWidth, slots.length]
|
|
@@ -9207,6 +9249,97 @@ function CalendarTimeline({
|
|
|
9207
9249
|
},
|
|
9208
9250
|
[activeView, dayTimeStepMinutes, resolvedTimeZone, slotStarts]
|
|
9209
9251
|
);
|
|
9252
|
+
const updateDragPreview = React28.useCallback(
|
|
9253
|
+
(clientX, clientY) => {
|
|
9254
|
+
const drag = dragRef.current;
|
|
9255
|
+
if (!drag) return;
|
|
9256
|
+
const ctx = getPointerContext(clientX, clientY, drag.mode === "create" ? { biasLeft: true, fallbackResourceId: drag.resourceId } : { fallbackResourceId: drag.resourceId });
|
|
9257
|
+
if (!ctx) return;
|
|
9258
|
+
const { slotIdx } = ctx;
|
|
9259
|
+
const movedEnough = Math.abs(clientX - drag.startClientX) > 3 || Math.abs(clientY - drag.startClientY) > 3 || slotIdx !== drag.startSlotIdx || ctx.resourceId !== drag.startRowResourceId;
|
|
9260
|
+
if (movedEnough) suppressNextEventClickRef.current = true;
|
|
9261
|
+
if (drag.mode === "create") {
|
|
9262
|
+
const a = Math.min(drag.startSlotIdx, slotIdx);
|
|
9263
|
+
const b = Math.max(drag.startSlotIdx, slotIdx) + 1;
|
|
9264
|
+
const s = slotToDate(a).start;
|
|
9265
|
+
const e2 = b >= slots.length ? range.end : slotToDate(b).start;
|
|
9266
|
+
setPreview({ resourceId: drag.resourceId, start: s, end: e2 });
|
|
9267
|
+
return;
|
|
9268
|
+
}
|
|
9269
|
+
const targetSlotStart = slotToDate(slotIdx).start;
|
|
9270
|
+
const originSlotStart = slotToDate(drag.startSlotIdx).start;
|
|
9271
|
+
const deltaMs = targetSlotStart.getTime() - originSlotStart.getTime();
|
|
9272
|
+
if (drag.mode === "move") {
|
|
9273
|
+
const nextStart = new Date(drag.originStart.getTime() + deltaMs);
|
|
9274
|
+
const nextEnd = new Date(drag.originEnd.getTime() + deltaMs);
|
|
9275
|
+
setPreview({ eventId: drag.eventId, resourceId: ctx.resourceId, start: nextStart, end: nextEnd });
|
|
9276
|
+
drag.resourceId = ctx.resourceId;
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
if (drag.mode === "resize-start") {
|
|
9280
|
+
const nextStart = new Date(clamp3(targetSlotStart.getTime(), range.start.getTime(), drag.originEnd.getTime() - 6e4));
|
|
9281
|
+
setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: nextStart, end: drag.originEnd });
|
|
9282
|
+
return;
|
|
9283
|
+
}
|
|
9284
|
+
if (drag.mode === "resize-end") {
|
|
9285
|
+
const nextEnd = new Date(clamp3(targetSlotStart.getTime(), drag.originStart.getTime() + 6e4, range.end.getTime()));
|
|
9286
|
+
setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: drag.originStart, end: nextEnd });
|
|
9287
|
+
return;
|
|
9288
|
+
}
|
|
9289
|
+
},
|
|
9290
|
+
[getPointerContext, range.end, range.start, slotToDate, slots.length]
|
|
9291
|
+
);
|
|
9292
|
+
const autoScrollTick = React28.useCallback(() => {
|
|
9293
|
+
const drag = dragRef.current;
|
|
9294
|
+
const body = bodyRef.current;
|
|
9295
|
+
const st = autoScrollStateRef.current;
|
|
9296
|
+
if (!drag || !body || st.dir === 0) {
|
|
9297
|
+
stopAutoScroll();
|
|
9298
|
+
return;
|
|
9299
|
+
}
|
|
9300
|
+
const maxScrollLeft = Math.max(0, body.scrollWidth - body.clientWidth);
|
|
9301
|
+
const prevLeft = body.scrollLeft;
|
|
9302
|
+
const nextLeft = clamp3(prevLeft + st.dir * st.speed, 0, maxScrollLeft);
|
|
9303
|
+
if (nextLeft === prevLeft) {
|
|
9304
|
+
stopAutoScroll();
|
|
9305
|
+
return;
|
|
9306
|
+
}
|
|
9307
|
+
body.scrollLeft = nextLeft;
|
|
9308
|
+
updateDragPreview(st.lastClientX, st.lastClientY);
|
|
9309
|
+
autoScrollRafRef.current = requestAnimationFrame(autoScrollTick);
|
|
9310
|
+
}, [stopAutoScroll, updateDragPreview]);
|
|
9311
|
+
const updateAutoScrollFromPointer = React28.useCallback(
|
|
9312
|
+
(clientX, clientY) => {
|
|
9313
|
+
const body = bodyRef.current;
|
|
9314
|
+
if (!body) return;
|
|
9315
|
+
const rect = body.getBoundingClientRect();
|
|
9316
|
+
const edge = 56;
|
|
9317
|
+
let dir = 0;
|
|
9318
|
+
let speed = 0;
|
|
9319
|
+
if (clientX < rect.left + edge) {
|
|
9320
|
+
dir = -1;
|
|
9321
|
+
const dist = clientX - rect.left;
|
|
9322
|
+
const t2 = clamp3(1 - dist / edge, 0, 1);
|
|
9323
|
+
speed = 8 + t2 * 28;
|
|
9324
|
+
} else if (clientX > rect.right - edge) {
|
|
9325
|
+
dir = 1;
|
|
9326
|
+
const dist = rect.right - clientX;
|
|
9327
|
+
const t2 = clamp3(1 - dist / edge, 0, 1);
|
|
9328
|
+
speed = 8 + t2 * 28;
|
|
9329
|
+
}
|
|
9330
|
+
autoScrollStateRef.current.lastClientX = clientX;
|
|
9331
|
+
autoScrollStateRef.current.lastClientY = clientY;
|
|
9332
|
+
autoScrollStateRef.current.dir = dir;
|
|
9333
|
+
autoScrollStateRef.current.speed = speed;
|
|
9334
|
+
if (dir === 0) {
|
|
9335
|
+
stopAutoScroll();
|
|
9336
|
+
return;
|
|
9337
|
+
}
|
|
9338
|
+
if (autoScrollRafRef.current == null) autoScrollRafRef.current = requestAnimationFrame(autoScrollTick);
|
|
9339
|
+
},
|
|
9340
|
+
[autoScrollTick, stopAutoScroll]
|
|
9341
|
+
);
|
|
9342
|
+
React28.useEffect(() => stopAutoScroll, [stopAutoScroll]);
|
|
9210
9343
|
const onPointerDownEvent = (e, ev, mode) => {
|
|
9211
9344
|
if (e.button !== 0 || e.ctrlKey) return;
|
|
9212
9345
|
if (isViewOnly) return;
|
|
@@ -9218,6 +9351,8 @@ function CalendarTimeline({
|
|
|
9218
9351
|
suppressNextEventClickRef.current = false;
|
|
9219
9352
|
const startIdx = binarySearchLastLE(slotStarts, ev._start);
|
|
9220
9353
|
const endIdx = binarySearchFirstGE(slotStarts, ev._end);
|
|
9354
|
+
const pointerCtx = getPointerContext(e.clientX, e.clientY, { fallbackResourceId: ev.resourceId });
|
|
9355
|
+
const grabSlotIdx = pointerCtx?.slotIdx ?? startIdx;
|
|
9221
9356
|
dragRef.current = {
|
|
9222
9357
|
mode,
|
|
9223
9358
|
eventId: ev.id,
|
|
@@ -9226,7 +9361,7 @@ function CalendarTimeline({
|
|
|
9226
9361
|
originEnd: ev._end,
|
|
9227
9362
|
durationMs: ev._end.getTime() - ev._start.getTime(),
|
|
9228
9363
|
pointerId: e.pointerId,
|
|
9229
|
-
startSlotIdx:
|
|
9364
|
+
startSlotIdx: grabSlotIdx,
|
|
9230
9365
|
startRowResourceId: ev.resourceId,
|
|
9231
9366
|
startClientX: e.clientX,
|
|
9232
9367
|
startClientY: e.clientY
|
|
@@ -9282,44 +9417,14 @@ function CalendarTimeline({
|
|
|
9282
9417
|
const onPointerMove = (e) => {
|
|
9283
9418
|
const drag = dragRef.current;
|
|
9284
9419
|
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
const { slotIdx } = ctx;
|
|
9288
|
-
const movedEnough = Math.abs(e.clientX - drag.startClientX) > 3 || Math.abs(e.clientY - drag.startClientY) > 3 || slotIdx !== drag.startSlotIdx || ctx.resourceId !== drag.startRowResourceId;
|
|
9289
|
-
if (movedEnough) suppressNextEventClickRef.current = true;
|
|
9290
|
-
if (drag.mode === "create") {
|
|
9291
|
-
const a = Math.min(drag.startSlotIdx, slotIdx);
|
|
9292
|
-
const b = Math.max(drag.startSlotIdx, slotIdx) + 1;
|
|
9293
|
-
const s = slotToDate(a).start;
|
|
9294
|
-
const e2 = b >= slots.length ? range.end : slotToDate(b).start;
|
|
9295
|
-
setPreview({ resourceId: drag.resourceId, start: s, end: e2 });
|
|
9296
|
-
return;
|
|
9297
|
-
}
|
|
9298
|
-
const targetSlotStart = slotToDate(slotIdx).start;
|
|
9299
|
-
const originSlotStart = slotToDate(drag.startSlotIdx).start;
|
|
9300
|
-
const deltaMs = targetSlotStart.getTime() - originSlotStart.getTime();
|
|
9301
|
-
if (drag.mode === "move") {
|
|
9302
|
-
const nextStart = new Date(drag.originStart.getTime() + deltaMs);
|
|
9303
|
-
const nextEnd = new Date(drag.originEnd.getTime() + deltaMs);
|
|
9304
|
-
setPreview({ eventId: drag.eventId, resourceId: ctx.resourceId, start: nextStart, end: nextEnd });
|
|
9305
|
-
drag.resourceId = ctx.resourceId;
|
|
9306
|
-
return;
|
|
9307
|
-
}
|
|
9308
|
-
if (drag.mode === "resize-start") {
|
|
9309
|
-
const nextStart = new Date(clamp3(targetSlotStart.getTime(), range.start.getTime(), drag.originEnd.getTime() - 6e4));
|
|
9310
|
-
setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: nextStart, end: drag.originEnd });
|
|
9311
|
-
return;
|
|
9312
|
-
}
|
|
9313
|
-
if (drag.mode === "resize-end") {
|
|
9314
|
-
const nextEnd = new Date(clamp3(targetSlotStart.getTime(), drag.originStart.getTime() + 6e4, range.end.getTime()));
|
|
9315
|
-
setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: drag.originStart, end: nextEnd });
|
|
9316
|
-
return;
|
|
9317
|
-
}
|
|
9420
|
+
updateAutoScrollFromPointer(e.clientX, e.clientY);
|
|
9421
|
+
updateDragPreview(e.clientX, e.clientY);
|
|
9318
9422
|
};
|
|
9319
9423
|
const onPointerUp = (e) => {
|
|
9320
9424
|
const drag = dragRef.current;
|
|
9321
9425
|
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
9322
9426
|
dragRef.current = null;
|
|
9427
|
+
stopAutoScroll();
|
|
9323
9428
|
if (!preview) {
|
|
9324
9429
|
setPreview(null);
|
|
9325
9430
|
return;
|