@underverse-ui/underverse 1.0.35 → 1.0.37

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.js CHANGED
@@ -9777,25 +9777,94 @@ function binarySearchLastLE(arr, target) {
9777
9777
  }
9778
9778
  function intervalPack(items) {
9779
9779
  const sorted = [...items].sort((a, b) => a.startIdx - b.startIdx || a.endIdx - b.endIdx);
9780
- const lanes = [];
9781
9780
  const out = [];
9782
- for (const it of sorted) {
9783
- let lane = -1;
9784
- for (let i = 0; i < lanes.length; i++) {
9785
- if (lanes[i].endIdx <= it.startIdx) {
9786
- lane = i;
9787
- break;
9781
+ const active = [];
9782
+ const freeLanes = [];
9783
+ let nextLane = 0;
9784
+ const pushActive = (item) => {
9785
+ active.push(item);
9786
+ let i = active.length - 1;
9787
+ while (i > 0) {
9788
+ const p = i - 1 >> 1;
9789
+ const a = active[i];
9790
+ const b = active[p];
9791
+ if (a.endIdx > b.endIdx || a.endIdx === b.endIdx && a.lane >= b.lane) break;
9792
+ active[i] = b;
9793
+ active[p] = a;
9794
+ i = p;
9795
+ }
9796
+ };
9797
+ const popActive = () => {
9798
+ const top = active[0];
9799
+ const last = active.pop();
9800
+ if (active.length > 0) {
9801
+ active[0] = last;
9802
+ let i = 0;
9803
+ while (true) {
9804
+ const l = i * 2 + 1;
9805
+ const r = l + 1;
9806
+ let m = i;
9807
+ if (l < active.length) {
9808
+ const al = active[l];
9809
+ const am = active[m];
9810
+ if (al.endIdx < am.endIdx || al.endIdx === am.endIdx && al.lane < am.lane) m = l;
9811
+ }
9812
+ if (r < active.length) {
9813
+ const ar = active[r];
9814
+ const am = active[m];
9815
+ if (ar.endIdx < am.endIdx || ar.endIdx === am.endIdx && ar.lane < am.lane) m = r;
9816
+ }
9817
+ if (m === i) break;
9818
+ const cur = active[i];
9819
+ active[i] = active[m];
9820
+ active[m] = cur;
9821
+ i = m;
9788
9822
  }
9789
9823
  }
9790
- if (lane === -1) {
9791
- lane = lanes.length;
9792
- lanes.push({ endIdx: it.endIdx });
9793
- } else {
9794
- lanes[lane].endIdx = it.endIdx;
9824
+ return top;
9825
+ };
9826
+ const pushFreeLane = (lane) => {
9827
+ freeLanes.push(lane);
9828
+ let i = freeLanes.length - 1;
9829
+ while (i > 0) {
9830
+ const p = i - 1 >> 1;
9831
+ if (freeLanes[p] <= freeLanes[i]) break;
9832
+ const tmp = freeLanes[p];
9833
+ freeLanes[p] = freeLanes[i];
9834
+ freeLanes[i] = tmp;
9835
+ i = p;
9795
9836
  }
9837
+ };
9838
+ const popFreeLane = () => {
9839
+ const top = freeLanes[0];
9840
+ const last = freeLanes.pop();
9841
+ if (freeLanes.length > 0) {
9842
+ freeLanes[0] = last;
9843
+ let i = 0;
9844
+ while (true) {
9845
+ const l = i * 2 + 1;
9846
+ const r = l + 1;
9847
+ let m = i;
9848
+ if (l < freeLanes.length && freeLanes[l] < freeLanes[m]) m = l;
9849
+ if (r < freeLanes.length && freeLanes[r] < freeLanes[m]) m = r;
9850
+ if (m === i) break;
9851
+ const tmp = freeLanes[i];
9852
+ freeLanes[i] = freeLanes[m];
9853
+ freeLanes[m] = tmp;
9854
+ i = m;
9855
+ }
9856
+ }
9857
+ return top;
9858
+ };
9859
+ for (const it of sorted) {
9860
+ while (active.length > 0 && active[0].endIdx <= it.startIdx) {
9861
+ pushFreeLane(popActive().lane);
9862
+ }
9863
+ const lane = freeLanes.length > 0 ? popFreeLane() : nextLane++;
9864
+ pushActive({ endIdx: it.endIdx, lane });
9796
9865
  out.push({ ...it, lane });
9797
9866
  }
9798
- return { packed: out, laneCount: lanes.length };
9867
+ return { packed: out, laneCount: nextLane };
9799
9868
  }
9800
9869
 
9801
9870
  // ../../components/ui/CalendarTimeline/hooks.ts
@@ -9808,10 +9877,12 @@ function useHorizontalScrollSync(args) {
9808
9877
  const left = leftRef?.current ?? null;
9809
9878
  if (!body || !header) return;
9810
9879
  let raf = 0;
9811
- let syncing = false;
9812
- const syncFrom = (source) => {
9813
- if (syncing) return;
9814
- syncing = true;
9880
+ let pendingSource = null;
9881
+ const flush = () => {
9882
+ raf = 0;
9883
+ const source = pendingSource;
9884
+ pendingSource = null;
9885
+ if (!source) return;
9815
9886
  if (source === "header") {
9816
9887
  const x = header.scrollLeft;
9817
9888
  if (body.scrollLeft !== x) body.scrollLeft = x;
@@ -9824,15 +9895,16 @@ function useHorizontalScrollSync(args) {
9824
9895
  if (header.scrollLeft !== x) header.scrollLeft = x;
9825
9896
  if (left && left.scrollTop !== y) left.scrollTop = y;
9826
9897
  }
9827
- cancelAnimationFrame(raf);
9828
- raf = requestAnimationFrame(() => {
9829
- syncing = false;
9830
- });
9831
9898
  };
9832
- const onBodyScroll = () => syncFrom("body");
9833
- const onHeaderScroll = () => syncFrom("header");
9834
- const onLeftScroll = () => syncFrom("left");
9835
- syncFrom("body");
9899
+ const scheduleSync = (source) => {
9900
+ pendingSource = source;
9901
+ if (raf) return;
9902
+ raf = requestAnimationFrame(flush);
9903
+ };
9904
+ const onBodyScroll = () => scheduleSync("body");
9905
+ const onHeaderScroll = () => scheduleSync("header");
9906
+ const onLeftScroll = () => scheduleSync("left");
9907
+ scheduleSync("body");
9836
9908
  body.addEventListener("scroll", onBodyScroll, { passive: true });
9837
9909
  header.addEventListener("scroll", onHeaderScroll, { passive: true });
9838
9910
  left?.addEventListener("scroll", onLeftScroll, { passive: true });
@@ -9860,6 +9932,10 @@ function useVirtualVariableRows(args) {
9860
9932
  const [viewportHeight, setViewportHeight] = React30.useState(0);
9861
9933
  const [scrollTop, setScrollTop] = React30.useState(0);
9862
9934
  React30.useEffect(() => {
9935
+ if (!enabled) {
9936
+ setViewportHeight(0);
9937
+ return;
9938
+ }
9863
9939
  const el = scrollRef.current;
9864
9940
  if (!el) return;
9865
9941
  const update = () => setViewportHeight(el.clientHeight);
@@ -9867,14 +9943,32 @@ function useVirtualVariableRows(args) {
9867
9943
  const ro = new ResizeObserver(update);
9868
9944
  ro.observe(el);
9869
9945
  return () => ro.disconnect();
9870
- }, [scrollRef]);
9946
+ }, [enabled, scrollRef]);
9871
9947
  React30.useEffect(() => {
9948
+ if (!enabled) {
9949
+ setScrollTop(0);
9950
+ return;
9951
+ }
9872
9952
  const el = scrollRef.current;
9873
9953
  if (!el) return;
9874
- const onScroll = () => setScrollTop(el.scrollTop);
9954
+ let raf = 0;
9955
+ let nextTop = el.scrollTop;
9956
+ const commit = () => {
9957
+ raf = 0;
9958
+ setScrollTop(nextTop);
9959
+ };
9960
+ const onScroll = () => {
9961
+ nextTop = el.scrollTop;
9962
+ if (raf) return;
9963
+ raf = requestAnimationFrame(commit);
9964
+ };
9965
+ setScrollTop(nextTop);
9875
9966
  el.addEventListener("scroll", onScroll, { passive: true });
9876
- return () => el.removeEventListener("scroll", onScroll);
9877
- }, [scrollRef]);
9967
+ return () => {
9968
+ if (raf) cancelAnimationFrame(raf);
9969
+ el.removeEventListener("scroll", onScroll);
9970
+ };
9971
+ }, [enabled, scrollRef]);
9878
9972
  const prefix = React30.useMemo(() => {
9879
9973
  const out = new Array(itemCount + 1);
9880
9974
  out[0] = 0;
@@ -10395,7 +10489,7 @@ function CalendarTimelineHeader(props) {
10395
10489
  ]
10396
10490
  }
10397
10491
  ) : null,
10398
- /* @__PURE__ */ jsx37("div", { ref: headerRef, className: "flex-1 min-w-0 overflow-x-auto overflow-y-hidden", children: slotHeaderNodes })
10492
+ /* @__PURE__ */ jsx37("div", { ref: headerRef, className: "flex-1 min-w-0 overflow-hidden ", children: slotHeaderNodes })
10399
10493
  ] })
10400
10494
  ] });
10401
10495
  }
@@ -10913,9 +11007,21 @@ function useVisibleSlotRange(args) {
10913
11007
  updateWidth();
10914
11008
  const ro = new ResizeObserver(updateWidth);
10915
11009
  ro.observe(el);
10916
- const onScroll = () => setScrollLeft(el.scrollLeft);
11010
+ let raf = 0;
11011
+ let nextLeft = el.scrollLeft;
11012
+ const commit = () => {
11013
+ raf = 0;
11014
+ setScrollLeft(nextLeft);
11015
+ };
11016
+ const onScroll = () => {
11017
+ nextLeft = el.scrollLeft;
11018
+ if (raf) return;
11019
+ raf = requestAnimationFrame(commit);
11020
+ };
11021
+ setScrollLeft(nextLeft);
10917
11022
  el.addEventListener("scroll", onScroll, { passive: true });
10918
11023
  return () => {
11024
+ if (raf) cancelAnimationFrame(raf);
10919
11025
  ro.disconnect();
10920
11026
  el.removeEventListener("scroll", onScroll);
10921
11027
  };
@@ -11270,7 +11376,7 @@ function CalendarTimeline({
11270
11376
  }
11271
11377
  }, [activeEventSheetOpen, activeSelectedEventId, effectiveEnableEventSheet, selectedEvent, setEventSheetOpen]);
11272
11378
  useHorizontalScrollSync({ bodyRef, headerRef, leftRef: showResourceColumn ? leftRef : void 0 });
11273
- const virt = virtualization?.enabled;
11379
+ const virt = virtualization == null ? rows.length > 60 : Boolean(virtualization.enabled);
11274
11380
  const overscan = virtualization?.overscan ?? 8;
11275
11381
  const isControlledRowHeights = rowHeights !== void 0;
11276
11382
  const [internalRowHeights, setInternalRowHeights] = React35.useState(() => defaultRowHeights ?? {});
@@ -11580,9 +11686,19 @@ function CalendarTimeline({
11580
11686
  setCreateOpen(false);
11581
11687
  }, [createEndIdx, createResourceId, createStartIdx, onCreateEvent, range.end, slotStarts]);
11582
11688
  const dragRef = React35.useRef(null);
11583
- const [preview, setPreview] = React35.useState(null);
11689
+ const [preview, setPreviewState] = React35.useState(null);
11690
+ const previewRef = React35.useRef(null);
11691
+ const setPreview = React35.useCallback((next) => {
11692
+ previewRef.current = next;
11693
+ setPreviewState(next);
11694
+ }, []);
11584
11695
  const suppressNextEventClickRef = React35.useRef(false);
11585
- const [hoverCell, setHoverCell] = React35.useState(null);
11696
+ const [hoverCell, setHoverCellState] = React35.useState(null);
11697
+ const hoverCellRef = React35.useRef(null);
11698
+ const setHoverCell = React35.useCallback((next) => {
11699
+ hoverCellRef.current = next;
11700
+ setHoverCellState(next);
11701
+ }, []);
11586
11702
  const autoScrollStateRef = React35.useRef({
11587
11703
  dir: 0,
11588
11704
  speed: 0,
@@ -11590,6 +11706,10 @@ function CalendarTimeline({
11590
11706
  lastClientY: 0
11591
11707
  });
11592
11708
  const autoScrollRafRef = React35.useRef(null);
11709
+ const dragPreviewRafRef = React35.useRef(null);
11710
+ const dragPreviewPointRef = React35.useRef(null);
11711
+ const hoverCellRafRef = React35.useRef(null);
11712
+ const hoverCellPendingRef = React35.useRef(null);
11593
11713
  const stopAutoScroll = React35.useCallback(() => {
11594
11714
  if (autoScrollRafRef.current != null) cancelAnimationFrame(autoScrollRafRef.current);
11595
11715
  autoScrollRafRef.current = null;
@@ -11672,6 +11792,41 @@ function CalendarTimeline({
11672
11792
  },
11673
11793
  [getPointerContext, range.end, range.start, slotToDate, slots.length]
11674
11794
  );
11795
+ const flushDragPreview = React35.useCallback(() => {
11796
+ dragPreviewRafRef.current = null;
11797
+ const point = dragPreviewPointRef.current;
11798
+ if (!point) return;
11799
+ updateDragPreview(point.x, point.y);
11800
+ }, [updateDragPreview]);
11801
+ const scheduleDragPreview = React35.useCallback(
11802
+ (clientX, clientY) => {
11803
+ dragPreviewPointRef.current = { x: clientX, y: clientY };
11804
+ if (dragPreviewRafRef.current != null) return;
11805
+ dragPreviewRafRef.current = requestAnimationFrame(flushDragPreview);
11806
+ },
11807
+ [flushDragPreview]
11808
+ );
11809
+ const applyHoverCell = React35.useCallback(
11810
+ (next) => {
11811
+ const prev = hoverCellRef.current;
11812
+ const same = prev == null && next == null || prev != null && next != null && prev.resourceId === next.resourceId && prev.slotIdx === next.slotIdx && Math.abs(prev.y - next.y) <= 0.5;
11813
+ if (same) return;
11814
+ setHoverCell(next);
11815
+ },
11816
+ [setHoverCell]
11817
+ );
11818
+ const flushHoverCell = React35.useCallback(() => {
11819
+ hoverCellRafRef.current = null;
11820
+ applyHoverCell(hoverCellPendingRef.current);
11821
+ }, [applyHoverCell]);
11822
+ const scheduleHoverCell = React35.useCallback(
11823
+ (next) => {
11824
+ hoverCellPendingRef.current = next;
11825
+ if (hoverCellRafRef.current != null) return;
11826
+ hoverCellRafRef.current = requestAnimationFrame(flushHoverCell);
11827
+ },
11828
+ [flushHoverCell]
11829
+ );
11675
11830
  const autoScrollTick = React35.useCallback(() => {
11676
11831
  const drag = dragRef.current;
11677
11832
  const body = bodyRef.current;
@@ -11723,6 +11878,12 @@ function CalendarTimeline({
11723
11878
  [autoScrollTick, stopAutoScroll]
11724
11879
  );
11725
11880
  React35.useEffect(() => stopAutoScroll, [stopAutoScroll]);
11881
+ React35.useEffect(() => {
11882
+ return () => {
11883
+ if (dragPreviewRafRef.current != null) cancelAnimationFrame(dragPreviewRafRef.current);
11884
+ if (hoverCellRafRef.current != null) cancelAnimationFrame(hoverCellRafRef.current);
11885
+ };
11886
+ }, []);
11726
11887
  const onPointerDownEvent = (e, ev, mode) => {
11727
11888
  if (e.button !== 0 || e.ctrlKey) return;
11728
11889
  if (isViewOnly) return;
@@ -11804,57 +11965,74 @@ function CalendarTimeline({
11804
11965
  if (!(interactions?.creatable ?? false)) return;
11805
11966
  const target = e.target;
11806
11967
  if (target?.closest?.("[data-uv-ct-event]")) {
11807
- if (hoverCell) setHoverCell(null);
11968
+ scheduleHoverCell(null);
11808
11969
  return;
11809
11970
  }
11810
11971
  const ctx = getPointerContext(e.clientX, e.clientY);
11811
11972
  if (!ctx) {
11812
- if (hoverCell) setHoverCell(null);
11973
+ scheduleHoverCell(null);
11813
11974
  return;
11814
11975
  }
11815
11976
  const rowEl = target?.closest?.("[data-uv-ct-row]");
11816
11977
  if (!rowEl) {
11817
- if (hoverCell) setHoverCell(null);
11978
+ scheduleHoverCell(null);
11818
11979
  return;
11819
11980
  }
11820
11981
  const rect = rowEl.getBoundingClientRect();
11821
11982
  const y = clamp5(e.clientY - rect.top, 0, rect.height);
11822
- if (!hoverCell || hoverCell.resourceId !== ctx.resourceId || hoverCell.slotIdx !== ctx.slotIdx || Math.abs(hoverCell.y - y) > 0.5) {
11823
- setHoverCell({ resourceId: ctx.resourceId, slotIdx: ctx.slotIdx, y });
11824
- }
11983
+ scheduleHoverCell({ resourceId: ctx.resourceId, slotIdx: ctx.slotIdx, y });
11825
11984
  return;
11826
11985
  }
11827
11986
  if (drag.pointerId !== e.pointerId) return;
11828
11987
  updateAutoScrollFromPointer(e.clientX, e.clientY);
11829
- updateDragPreview(e.clientX, e.clientY);
11988
+ scheduleDragPreview(e.clientX, e.clientY);
11830
11989
  };
11831
11990
  const onPointerUp = (e) => {
11832
11991
  const drag = dragRef.current;
11833
11992
  if (!drag || drag.pointerId !== e.pointerId) return;
11993
+ if (dragPreviewRafRef.current != null) {
11994
+ cancelAnimationFrame(dragPreviewRafRef.current);
11995
+ dragPreviewRafRef.current = null;
11996
+ }
11997
+ updateDragPreview(e.clientX, e.clientY);
11998
+ const committedPreview = previewRef.current;
11834
11999
  dragRef.current = null;
11835
12000
  stopAutoScroll();
12001
+ hoverCellPendingRef.current = null;
12002
+ if (hoverCellRafRef.current != null) {
12003
+ cancelAnimationFrame(hoverCellRafRef.current);
12004
+ hoverCellRafRef.current = null;
12005
+ }
11836
12006
  setHoverCell(null);
11837
- if (!preview) {
12007
+ if (!committedPreview) {
11838
12008
  setPreview(null);
11839
12009
  return;
11840
12010
  }
11841
12011
  if (drag.mode === "create" && onCreateEvent) {
11842
- onCreateEvent({ resourceId: preview.resourceId, start: preview.start, end: preview.end });
12012
+ onCreateEvent({ resourceId: committedPreview.resourceId, start: committedPreview.start, end: committedPreview.end });
11843
12013
  setPreview(null);
11844
12014
  return;
11845
12015
  }
11846
- if (drag.mode === "move" && preview.eventId && onEventMove) {
11847
- onEventMove({ eventId: preview.eventId, resourceId: preview.resourceId, start: preview.start, end: preview.end });
12016
+ if (drag.mode === "move" && committedPreview.eventId && onEventMove) {
12017
+ onEventMove({ eventId: committedPreview.eventId, resourceId: committedPreview.resourceId, start: committedPreview.start, end: committedPreview.end });
11848
12018
  setPreview(null);
11849
12019
  return;
11850
12020
  }
11851
- if ((drag.mode === "resize-start" || drag.mode === "resize-end") && preview.eventId && onEventResize) {
11852
- onEventResize({ eventId: preview.eventId, start: preview.start, end: preview.end });
12021
+ if ((drag.mode === "resize-start" || drag.mode === "resize-end") && committedPreview.eventId && onEventResize) {
12022
+ onEventResize({ eventId: committedPreview.eventId, start: committedPreview.start, end: committedPreview.end });
11853
12023
  setPreview(null);
11854
12024
  return;
11855
12025
  }
11856
12026
  setPreview(null);
11857
12027
  };
12028
+ const onBodyPointerLeave = React35.useCallback(() => {
12029
+ hoverCellPendingRef.current = null;
12030
+ if (hoverCellRafRef.current != null) {
12031
+ cancelAnimationFrame(hoverCellRafRef.current);
12032
+ hoverCellRafRef.current = null;
12033
+ }
12034
+ setHoverCell(null);
12035
+ }, [setHoverCell]);
11858
12036
  const renderGroupRow = (g) => {
11859
12037
  const isCollapsed = !!collapsed[g.id];
11860
12038
  const toggle = () => setCollapsed({ ...collapsed, [g.id]: !isCollapsed });
@@ -12019,7 +12197,7 @@ function CalendarTimeline({
12019
12197
  className: "relative flex-1 overflow-auto",
12020
12198
  onPointerMove,
12021
12199
  onPointerUp,
12022
- onPointerLeave: () => setHoverCell(null),
12200
+ onPointerLeave: onBodyPointerLeave,
12023
12201
  children: [
12024
12202
  /* @__PURE__ */ jsx41("div", { style: { height: topSpacer } }),
12025
12203
  gridWidth > 0 && renderedRowsHeight > 0 ? /* @__PURE__ */ jsx41("div", { className: "pointer-events-none absolute left-0 z-0", style: { top: topSpacer, width: gridWidth, height: renderedRowsHeight }, children: /* @__PURE__ */ jsx41(