@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 211,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -9964,25 +9964,94 @@ function binarySearchLastLE(arr, target) {
9964
9964
  }
9965
9965
  function intervalPack(items) {
9966
9966
  const sorted = [...items].sort((a, b) => a.startIdx - b.startIdx || a.endIdx - b.endIdx);
9967
- const lanes = [];
9968
9967
  const out = [];
9969
- for (const it of sorted) {
9970
- let lane = -1;
9971
- for (let i = 0; i < lanes.length; i++) {
9972
- if (lanes[i].endIdx <= it.startIdx) {
9973
- lane = i;
9974
- break;
9968
+ const active = [];
9969
+ const freeLanes = [];
9970
+ let nextLane = 0;
9971
+ const pushActive = (item) => {
9972
+ active.push(item);
9973
+ let i = active.length - 1;
9974
+ while (i > 0) {
9975
+ const p = i - 1 >> 1;
9976
+ const a = active[i];
9977
+ const b = active[p];
9978
+ if (a.endIdx > b.endIdx || a.endIdx === b.endIdx && a.lane >= b.lane) break;
9979
+ active[i] = b;
9980
+ active[p] = a;
9981
+ i = p;
9982
+ }
9983
+ };
9984
+ const popActive = () => {
9985
+ const top = active[0];
9986
+ const last = active.pop();
9987
+ if (active.length > 0) {
9988
+ active[0] = last;
9989
+ let i = 0;
9990
+ while (true) {
9991
+ const l = i * 2 + 1;
9992
+ const r = l + 1;
9993
+ let m = i;
9994
+ if (l < active.length) {
9995
+ const al = active[l];
9996
+ const am = active[m];
9997
+ if (al.endIdx < am.endIdx || al.endIdx === am.endIdx && al.lane < am.lane) m = l;
9998
+ }
9999
+ if (r < active.length) {
10000
+ const ar = active[r];
10001
+ const am = active[m];
10002
+ if (ar.endIdx < am.endIdx || ar.endIdx === am.endIdx && ar.lane < am.lane) m = r;
10003
+ }
10004
+ if (m === i) break;
10005
+ const cur = active[i];
10006
+ active[i] = active[m];
10007
+ active[m] = cur;
10008
+ i = m;
9975
10009
  }
9976
10010
  }
9977
- if (lane === -1) {
9978
- lane = lanes.length;
9979
- lanes.push({ endIdx: it.endIdx });
9980
- } else {
9981
- lanes[lane].endIdx = it.endIdx;
10011
+ return top;
10012
+ };
10013
+ const pushFreeLane = (lane) => {
10014
+ freeLanes.push(lane);
10015
+ let i = freeLanes.length - 1;
10016
+ while (i > 0) {
10017
+ const p = i - 1 >> 1;
10018
+ if (freeLanes[p] <= freeLanes[i]) break;
10019
+ const tmp = freeLanes[p];
10020
+ freeLanes[p] = freeLanes[i];
10021
+ freeLanes[i] = tmp;
10022
+ i = p;
9982
10023
  }
10024
+ };
10025
+ const popFreeLane = () => {
10026
+ const top = freeLanes[0];
10027
+ const last = freeLanes.pop();
10028
+ if (freeLanes.length > 0) {
10029
+ freeLanes[0] = last;
10030
+ let i = 0;
10031
+ while (true) {
10032
+ const l = i * 2 + 1;
10033
+ const r = l + 1;
10034
+ let m = i;
10035
+ if (l < freeLanes.length && freeLanes[l] < freeLanes[m]) m = l;
10036
+ if (r < freeLanes.length && freeLanes[r] < freeLanes[m]) m = r;
10037
+ if (m === i) break;
10038
+ const tmp = freeLanes[i];
10039
+ freeLanes[i] = freeLanes[m];
10040
+ freeLanes[m] = tmp;
10041
+ i = m;
10042
+ }
10043
+ }
10044
+ return top;
10045
+ };
10046
+ for (const it of sorted) {
10047
+ while (active.length > 0 && active[0].endIdx <= it.startIdx) {
10048
+ pushFreeLane(popActive().lane);
10049
+ }
10050
+ const lane = freeLanes.length > 0 ? popFreeLane() : nextLane++;
10051
+ pushActive({ endIdx: it.endIdx, lane });
9983
10052
  out.push({ ...it, lane });
9984
10053
  }
9985
- return { packed: out, laneCount: lanes.length };
10054
+ return { packed: out, laneCount: nextLane };
9986
10055
  }
9987
10056
 
9988
10057
  // ../../components/ui/CalendarTimeline/hooks.ts
@@ -9995,10 +10064,12 @@ function useHorizontalScrollSync(args) {
9995
10064
  const left = leftRef?.current ?? null;
9996
10065
  if (!body || !header) return;
9997
10066
  let raf = 0;
9998
- let syncing = false;
9999
- const syncFrom = (source) => {
10000
- if (syncing) return;
10001
- syncing = true;
10067
+ let pendingSource = null;
10068
+ const flush = () => {
10069
+ raf = 0;
10070
+ const source = pendingSource;
10071
+ pendingSource = null;
10072
+ if (!source) return;
10002
10073
  if (source === "header") {
10003
10074
  const x = header.scrollLeft;
10004
10075
  if (body.scrollLeft !== x) body.scrollLeft = x;
@@ -10011,15 +10082,16 @@ function useHorizontalScrollSync(args) {
10011
10082
  if (header.scrollLeft !== x) header.scrollLeft = x;
10012
10083
  if (left && left.scrollTop !== y) left.scrollTop = y;
10013
10084
  }
10014
- cancelAnimationFrame(raf);
10015
- raf = requestAnimationFrame(() => {
10016
- syncing = false;
10017
- });
10018
10085
  };
10019
- const onBodyScroll = () => syncFrom("body");
10020
- const onHeaderScroll = () => syncFrom("header");
10021
- const onLeftScroll = () => syncFrom("left");
10022
- syncFrom("body");
10086
+ const scheduleSync = (source) => {
10087
+ pendingSource = source;
10088
+ if (raf) return;
10089
+ raf = requestAnimationFrame(flush);
10090
+ };
10091
+ const onBodyScroll = () => scheduleSync("body");
10092
+ const onHeaderScroll = () => scheduleSync("header");
10093
+ const onLeftScroll = () => scheduleSync("left");
10094
+ scheduleSync("body");
10023
10095
  body.addEventListener("scroll", onBodyScroll, { passive: true });
10024
10096
  header.addEventListener("scroll", onHeaderScroll, { passive: true });
10025
10097
  left?.addEventListener("scroll", onLeftScroll, { passive: true });
@@ -10047,6 +10119,10 @@ function useVirtualVariableRows(args) {
10047
10119
  const [viewportHeight, setViewportHeight] = React30.useState(0);
10048
10120
  const [scrollTop, setScrollTop] = React30.useState(0);
10049
10121
  React30.useEffect(() => {
10122
+ if (!enabled) {
10123
+ setViewportHeight(0);
10124
+ return;
10125
+ }
10050
10126
  const el = scrollRef.current;
10051
10127
  if (!el) return;
10052
10128
  const update = () => setViewportHeight(el.clientHeight);
@@ -10054,14 +10130,32 @@ function useVirtualVariableRows(args) {
10054
10130
  const ro = new ResizeObserver(update);
10055
10131
  ro.observe(el);
10056
10132
  return () => ro.disconnect();
10057
- }, [scrollRef]);
10133
+ }, [enabled, scrollRef]);
10058
10134
  React30.useEffect(() => {
10135
+ if (!enabled) {
10136
+ setScrollTop(0);
10137
+ return;
10138
+ }
10059
10139
  const el = scrollRef.current;
10060
10140
  if (!el) return;
10061
- const onScroll = () => setScrollTop(el.scrollTop);
10141
+ let raf = 0;
10142
+ let nextTop = el.scrollTop;
10143
+ const commit = () => {
10144
+ raf = 0;
10145
+ setScrollTop(nextTop);
10146
+ };
10147
+ const onScroll = () => {
10148
+ nextTop = el.scrollTop;
10149
+ if (raf) return;
10150
+ raf = requestAnimationFrame(commit);
10151
+ };
10152
+ setScrollTop(nextTop);
10062
10153
  el.addEventListener("scroll", onScroll, { passive: true });
10063
- return () => el.removeEventListener("scroll", onScroll);
10064
- }, [scrollRef]);
10154
+ return () => {
10155
+ if (raf) cancelAnimationFrame(raf);
10156
+ el.removeEventListener("scroll", onScroll);
10157
+ };
10158
+ }, [enabled, scrollRef]);
10065
10159
  const prefix = React30.useMemo(() => {
10066
10160
  const out = new Array(itemCount + 1);
10067
10161
  out[0] = 0;
@@ -10582,7 +10676,7 @@ function CalendarTimelineHeader(props) {
10582
10676
  ]
10583
10677
  }
10584
10678
  ) : null,
10585
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { ref: headerRef, className: "flex-1 min-w-0 overflow-x-auto overflow-y-hidden", children: slotHeaderNodes })
10679
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { ref: headerRef, className: "flex-1 min-w-0 overflow-hidden ", children: slotHeaderNodes })
10586
10680
  ] })
10587
10681
  ] });
10588
10682
  }
@@ -11100,9 +11194,21 @@ function useVisibleSlotRange(args) {
11100
11194
  updateWidth();
11101
11195
  const ro = new ResizeObserver(updateWidth);
11102
11196
  ro.observe(el);
11103
- const onScroll = () => setScrollLeft(el.scrollLeft);
11197
+ let raf = 0;
11198
+ let nextLeft = el.scrollLeft;
11199
+ const commit = () => {
11200
+ raf = 0;
11201
+ setScrollLeft(nextLeft);
11202
+ };
11203
+ const onScroll = () => {
11204
+ nextLeft = el.scrollLeft;
11205
+ if (raf) return;
11206
+ raf = requestAnimationFrame(commit);
11207
+ };
11208
+ setScrollLeft(nextLeft);
11104
11209
  el.addEventListener("scroll", onScroll, { passive: true });
11105
11210
  return () => {
11211
+ if (raf) cancelAnimationFrame(raf);
11106
11212
  ro.disconnect();
11107
11213
  el.removeEventListener("scroll", onScroll);
11108
11214
  };
@@ -11457,7 +11563,7 @@ function CalendarTimeline({
11457
11563
  }
11458
11564
  }, [activeEventSheetOpen, activeSelectedEventId, effectiveEnableEventSheet, selectedEvent, setEventSheetOpen]);
11459
11565
  useHorizontalScrollSync({ bodyRef, headerRef, leftRef: showResourceColumn ? leftRef : void 0 });
11460
- const virt = virtualization?.enabled;
11566
+ const virt = virtualization == null ? rows.length > 60 : Boolean(virtualization.enabled);
11461
11567
  const overscan = virtualization?.overscan ?? 8;
11462
11568
  const isControlledRowHeights = rowHeights !== void 0;
11463
11569
  const [internalRowHeights, setInternalRowHeights] = React35.useState(() => defaultRowHeights ?? {});
@@ -11767,9 +11873,19 @@ function CalendarTimeline({
11767
11873
  setCreateOpen(false);
11768
11874
  }, [createEndIdx, createResourceId, createStartIdx, onCreateEvent, range.end, slotStarts]);
11769
11875
  const dragRef = React35.useRef(null);
11770
- const [preview, setPreview] = React35.useState(null);
11876
+ const [preview, setPreviewState] = React35.useState(null);
11877
+ const previewRef = React35.useRef(null);
11878
+ const setPreview = React35.useCallback((next) => {
11879
+ previewRef.current = next;
11880
+ setPreviewState(next);
11881
+ }, []);
11771
11882
  const suppressNextEventClickRef = React35.useRef(false);
11772
- const [hoverCell, setHoverCell] = React35.useState(null);
11883
+ const [hoverCell, setHoverCellState] = React35.useState(null);
11884
+ const hoverCellRef = React35.useRef(null);
11885
+ const setHoverCell = React35.useCallback((next) => {
11886
+ hoverCellRef.current = next;
11887
+ setHoverCellState(next);
11888
+ }, []);
11773
11889
  const autoScrollStateRef = React35.useRef({
11774
11890
  dir: 0,
11775
11891
  speed: 0,
@@ -11777,6 +11893,10 @@ function CalendarTimeline({
11777
11893
  lastClientY: 0
11778
11894
  });
11779
11895
  const autoScrollRafRef = React35.useRef(null);
11896
+ const dragPreviewRafRef = React35.useRef(null);
11897
+ const dragPreviewPointRef = React35.useRef(null);
11898
+ const hoverCellRafRef = React35.useRef(null);
11899
+ const hoverCellPendingRef = React35.useRef(null);
11780
11900
  const stopAutoScroll = React35.useCallback(() => {
11781
11901
  if (autoScrollRafRef.current != null) cancelAnimationFrame(autoScrollRafRef.current);
11782
11902
  autoScrollRafRef.current = null;
@@ -11859,6 +11979,41 @@ function CalendarTimeline({
11859
11979
  },
11860
11980
  [getPointerContext, range.end, range.start, slotToDate, slots.length]
11861
11981
  );
11982
+ const flushDragPreview = React35.useCallback(() => {
11983
+ dragPreviewRafRef.current = null;
11984
+ const point = dragPreviewPointRef.current;
11985
+ if (!point) return;
11986
+ updateDragPreview(point.x, point.y);
11987
+ }, [updateDragPreview]);
11988
+ const scheduleDragPreview = React35.useCallback(
11989
+ (clientX, clientY) => {
11990
+ dragPreviewPointRef.current = { x: clientX, y: clientY };
11991
+ if (dragPreviewRafRef.current != null) return;
11992
+ dragPreviewRafRef.current = requestAnimationFrame(flushDragPreview);
11993
+ },
11994
+ [flushDragPreview]
11995
+ );
11996
+ const applyHoverCell = React35.useCallback(
11997
+ (next) => {
11998
+ const prev = hoverCellRef.current;
11999
+ 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;
12000
+ if (same) return;
12001
+ setHoverCell(next);
12002
+ },
12003
+ [setHoverCell]
12004
+ );
12005
+ const flushHoverCell = React35.useCallback(() => {
12006
+ hoverCellRafRef.current = null;
12007
+ applyHoverCell(hoverCellPendingRef.current);
12008
+ }, [applyHoverCell]);
12009
+ const scheduleHoverCell = React35.useCallback(
12010
+ (next) => {
12011
+ hoverCellPendingRef.current = next;
12012
+ if (hoverCellRafRef.current != null) return;
12013
+ hoverCellRafRef.current = requestAnimationFrame(flushHoverCell);
12014
+ },
12015
+ [flushHoverCell]
12016
+ );
11862
12017
  const autoScrollTick = React35.useCallback(() => {
11863
12018
  const drag = dragRef.current;
11864
12019
  const body = bodyRef.current;
@@ -11910,6 +12065,12 @@ function CalendarTimeline({
11910
12065
  [autoScrollTick, stopAutoScroll]
11911
12066
  );
11912
12067
  React35.useEffect(() => stopAutoScroll, [stopAutoScroll]);
12068
+ React35.useEffect(() => {
12069
+ return () => {
12070
+ if (dragPreviewRafRef.current != null) cancelAnimationFrame(dragPreviewRafRef.current);
12071
+ if (hoverCellRafRef.current != null) cancelAnimationFrame(hoverCellRafRef.current);
12072
+ };
12073
+ }, []);
11913
12074
  const onPointerDownEvent = (e, ev, mode) => {
11914
12075
  if (e.button !== 0 || e.ctrlKey) return;
11915
12076
  if (isViewOnly) return;
@@ -11991,57 +12152,74 @@ function CalendarTimeline({
11991
12152
  if (!(interactions?.creatable ?? false)) return;
11992
12153
  const target = e.target;
11993
12154
  if (target?.closest?.("[data-uv-ct-event]")) {
11994
- if (hoverCell) setHoverCell(null);
12155
+ scheduleHoverCell(null);
11995
12156
  return;
11996
12157
  }
11997
12158
  const ctx = getPointerContext(e.clientX, e.clientY);
11998
12159
  if (!ctx) {
11999
- if (hoverCell) setHoverCell(null);
12160
+ scheduleHoverCell(null);
12000
12161
  return;
12001
12162
  }
12002
12163
  const rowEl = target?.closest?.("[data-uv-ct-row]");
12003
12164
  if (!rowEl) {
12004
- if (hoverCell) setHoverCell(null);
12165
+ scheduleHoverCell(null);
12005
12166
  return;
12006
12167
  }
12007
12168
  const rect = rowEl.getBoundingClientRect();
12008
12169
  const y = clamp5(e.clientY - rect.top, 0, rect.height);
12009
- if (!hoverCell || hoverCell.resourceId !== ctx.resourceId || hoverCell.slotIdx !== ctx.slotIdx || Math.abs(hoverCell.y - y) > 0.5) {
12010
- setHoverCell({ resourceId: ctx.resourceId, slotIdx: ctx.slotIdx, y });
12011
- }
12170
+ scheduleHoverCell({ resourceId: ctx.resourceId, slotIdx: ctx.slotIdx, y });
12012
12171
  return;
12013
12172
  }
12014
12173
  if (drag.pointerId !== e.pointerId) return;
12015
12174
  updateAutoScrollFromPointer(e.clientX, e.clientY);
12016
- updateDragPreview(e.clientX, e.clientY);
12175
+ scheduleDragPreview(e.clientX, e.clientY);
12017
12176
  };
12018
12177
  const onPointerUp = (e) => {
12019
12178
  const drag = dragRef.current;
12020
12179
  if (!drag || drag.pointerId !== e.pointerId) return;
12180
+ if (dragPreviewRafRef.current != null) {
12181
+ cancelAnimationFrame(dragPreviewRafRef.current);
12182
+ dragPreviewRafRef.current = null;
12183
+ }
12184
+ updateDragPreview(e.clientX, e.clientY);
12185
+ const committedPreview = previewRef.current;
12021
12186
  dragRef.current = null;
12022
12187
  stopAutoScroll();
12188
+ hoverCellPendingRef.current = null;
12189
+ if (hoverCellRafRef.current != null) {
12190
+ cancelAnimationFrame(hoverCellRafRef.current);
12191
+ hoverCellRafRef.current = null;
12192
+ }
12023
12193
  setHoverCell(null);
12024
- if (!preview) {
12194
+ if (!committedPreview) {
12025
12195
  setPreview(null);
12026
12196
  return;
12027
12197
  }
12028
12198
  if (drag.mode === "create" && onCreateEvent) {
12029
- onCreateEvent({ resourceId: preview.resourceId, start: preview.start, end: preview.end });
12199
+ onCreateEvent({ resourceId: committedPreview.resourceId, start: committedPreview.start, end: committedPreview.end });
12030
12200
  setPreview(null);
12031
12201
  return;
12032
12202
  }
12033
- if (drag.mode === "move" && preview.eventId && onEventMove) {
12034
- onEventMove({ eventId: preview.eventId, resourceId: preview.resourceId, start: preview.start, end: preview.end });
12203
+ if (drag.mode === "move" && committedPreview.eventId && onEventMove) {
12204
+ onEventMove({ eventId: committedPreview.eventId, resourceId: committedPreview.resourceId, start: committedPreview.start, end: committedPreview.end });
12035
12205
  setPreview(null);
12036
12206
  return;
12037
12207
  }
12038
- if ((drag.mode === "resize-start" || drag.mode === "resize-end") && preview.eventId && onEventResize) {
12039
- onEventResize({ eventId: preview.eventId, start: preview.start, end: preview.end });
12208
+ if ((drag.mode === "resize-start" || drag.mode === "resize-end") && committedPreview.eventId && onEventResize) {
12209
+ onEventResize({ eventId: committedPreview.eventId, start: committedPreview.start, end: committedPreview.end });
12040
12210
  setPreview(null);
12041
12211
  return;
12042
12212
  }
12043
12213
  setPreview(null);
12044
12214
  };
12215
+ const onBodyPointerLeave = React35.useCallback(() => {
12216
+ hoverCellPendingRef.current = null;
12217
+ if (hoverCellRafRef.current != null) {
12218
+ cancelAnimationFrame(hoverCellRafRef.current);
12219
+ hoverCellRafRef.current = null;
12220
+ }
12221
+ setHoverCell(null);
12222
+ }, [setHoverCell]);
12045
12223
  const renderGroupRow = (g) => {
12046
12224
  const isCollapsed = !!collapsed[g.id];
12047
12225
  const toggle = () => setCollapsed({ ...collapsed, [g.id]: !isCollapsed });
@@ -12206,7 +12384,7 @@ function CalendarTimeline({
12206
12384
  className: "relative flex-1 overflow-auto",
12207
12385
  onPointerMove,
12208
12386
  onPointerUp,
12209
- onPointerLeave: () => setHoverCell(null),
12387
+ onPointerLeave: onBodyPointerLeave,
12210
12388
  children: [
12211
12389
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { style: { height: topSpacer } }),
12212
12390
  gridWidth > 0 && renderedRowsHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "pointer-events-none absolute left-0 z-0", style: { top: topSpacer, width: gridWidth, height: renderedRowsHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(