@underverse-ui/underverse 0.2.92 → 0.2.94

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 CHANGED
@@ -6600,6 +6600,13 @@ function Calendar2({
6600
6600
  maxEventsPerDay = 3,
6601
6601
  onEventClick,
6602
6602
  renderEvent,
6603
+ enableEventSheet,
6604
+ eventSheetSize = "md",
6605
+ renderEventSheet,
6606
+ selectedEventId,
6607
+ eventSheetOpen,
6608
+ onEventSheetOpenChange,
6609
+ onSelectedEventIdChange,
6603
6610
  ...rest
6604
6611
  }) {
6605
6612
  const isControlledMonth = month != null;
@@ -6630,6 +6637,60 @@ function Calendar2({
6630
6637
  }
6631
6638
  return map;
6632
6639
  }, [events]);
6640
+ const effectiveEnableEventSheet = enableEventSheet ?? !!renderEventSheet;
6641
+ const isEventSheetOpenControlled = eventSheetOpen !== void 0;
6642
+ const [internalEventSheetOpen, setInternalEventSheetOpen] = React24.useState(false);
6643
+ const activeEventSheetOpen = isEventSheetOpenControlled ? !!eventSheetOpen : internalEventSheetOpen;
6644
+ const isSelectedEventControlled = selectedEventId !== void 0;
6645
+ const [internalSelectedEventRef, setInternalSelectedEventRef] = React24.useState(null);
6646
+ const setEventSheetOpen = React24.useCallback(
6647
+ (open) => {
6648
+ if (!isEventSheetOpenControlled) setInternalEventSheetOpen(open);
6649
+ onEventSheetOpenChange?.(open);
6650
+ if (!open) {
6651
+ if (!isSelectedEventControlled) setInternalSelectedEventRef(null);
6652
+ onSelectedEventIdChange?.(void 0);
6653
+ }
6654
+ },
6655
+ [isEventSheetOpenControlled, isSelectedEventControlled, onEventSheetOpenChange, onSelectedEventIdChange]
6656
+ );
6657
+ const selectedEventRef = React24.useMemo(() => {
6658
+ if (isSelectedEventControlled && selectedEventId != null) {
6659
+ const ev = events.find((e) => e.id === selectedEventId);
6660
+ if (!ev) return null;
6661
+ const d = toDate(ev.date);
6662
+ const dayKey = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
6663
+ return { dayKey, eventId: selectedEventId };
6664
+ }
6665
+ return internalSelectedEventRef;
6666
+ }, [events, internalSelectedEventRef, isSelectedEventControlled, selectedEventId]);
6667
+ const selectedEvent = React24.useMemo(() => {
6668
+ if (!selectedEventRef) return null;
6669
+ const list = byDay.get(selectedEventRef.dayKey) || [];
6670
+ if (selectedEventRef.eventId != null) {
6671
+ return list.find((e) => e.id === selectedEventRef.eventId) || null;
6672
+ }
6673
+ const idx = selectedEventRef.index ?? -1;
6674
+ return idx >= 0 && idx < list.length ? list[idx] : null;
6675
+ }, [byDay, selectedEventRef]);
6676
+ const selectedEventDate = React24.useMemo(() => {
6677
+ if (!selectedEventRef) return null;
6678
+ const [y, m, d] = selectedEventRef.dayKey.split("-").map((x) => Number(x));
6679
+ if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return null;
6680
+ return new Date(y, m, d);
6681
+ }, [selectedEventRef]);
6682
+ const handleEventActivate = React24.useCallback(
6683
+ (event, date, dayKey, index) => {
6684
+ onEventClick?.(event, date);
6685
+ onSelectedEventIdChange?.(event.id ?? void 0);
6686
+ if (!effectiveEnableEventSheet) return;
6687
+ if (!isSelectedEventControlled) {
6688
+ setInternalSelectedEventRef({ dayKey, eventId: event.id, index });
6689
+ }
6690
+ setEventSheetOpen(true);
6691
+ },
6692
+ [effectiveEnableEventSheet, isSelectedEventControlled, onEventClick, onSelectedEventIdChange, setEventSheetOpen]
6693
+ );
6633
6694
  const isSelected = (d) => {
6634
6695
  if (!selected) return false;
6635
6696
  if (selectMode === "single" && selected instanceof Date) return isSameDay(selected, d);
@@ -6773,7 +6834,7 @@ function Calendar2({
6773
6834
  "button",
6774
6835
  {
6775
6836
  type: "button",
6776
- onClick: () => onEventClick?.(e, d),
6837
+ onClick: () => handleEventActivate(e, d, k, i),
6777
6838
  className: cn(
6778
6839
  "w-full text-left rounded-lg px-2 py-1",
6779
6840
  "transition-colors duration-150 hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
@@ -6937,7 +6998,7 @@ function Calendar2({
6937
6998
  "button",
6938
6999
  {
6939
7000
  type: "button",
6940
- onClick: () => onEventClick?.(e, d),
7001
+ onClick: () => handleEventActivate(e, d, k, i),
6941
7002
  className: cn(
6942
7003
  "w-full text-left rounded-lg px-2 py-1",
6943
7004
  "transition-colors duration-150 hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40",
@@ -6987,7 +7048,32 @@ function Calendar2({
6987
7048
  `wd-${idx}`
6988
7049
  );
6989
7050
  }) })
6990
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: cn(months > 1 ? "grid md:grid-cols-2 lg:grid-cols-3 gap-4" : ""), children: Array.from({ length: Math.max(1, months) }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(React24.Fragment, { children: renderMonth(addMonths(view, i)) }, `cal-month-${view.getFullYear()}-${view.getMonth()}-${i}`)) })
7051
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: cn(months > 1 ? "grid md:grid-cols-2 lg:grid-cols-3 gap-4" : ""), children: Array.from({ length: Math.max(1, months) }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(React24.Fragment, { children: renderMonth(addMonths(view, i)) }, `cal-month-${view.getFullYear()}-${view.getMonth()}-${i}`)) }),
7052
+ effectiveEnableEventSheet && selectedEvent && selectedEventDate ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
7053
+ Sheet,
7054
+ {
7055
+ open: activeEventSheetOpen,
7056
+ onOpenChange: setEventSheetOpen,
7057
+ side: "right",
7058
+ size: eventSheetSize,
7059
+ title: selectedEvent.title ?? "Event",
7060
+ description: selectedEventDate.toDateString(),
7061
+ children: renderEventSheet ? renderEventSheet({ event: selectedEvent, date: selectedEventDate, close: () => setEventSheetOpen(false) }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "space-y-3", children: [
7062
+ selectedEvent.id != null ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { children: [
7063
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "text-xs text-muted-foreground", children: "ID" }),
7064
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "font-mono text-xs break-all", children: String(selectedEvent.id) })
7065
+ ] }) : null,
7066
+ selectedEvent.badge ? /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { children: [
7067
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "text-xs text-muted-foreground", children: "Badge" }),
7068
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "text-sm", children: selectedEvent.badge })
7069
+ ] }) : null,
7070
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex items-center gap-2", children: [
7071
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "h-3 w-3 rounded-full", style: { backgroundColor: selectedEvent.color || "hsl(var(--primary))" } }),
7072
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "text-sm text-muted-foreground", children: "Color" })
7073
+ ] })
7074
+ ] })
7075
+ }
7076
+ ) : null
6991
7077
  ] });
6992
7078
  }
6993
7079
 
@@ -8150,8 +8236,19 @@ function useHorizontalScrollSync(args) {
8150
8236
  };
8151
8237
  }, [bodyRef, headerRef, leftRef]);
8152
8238
  }
8153
- function useVirtualRows(args) {
8154
- const { enabled, overscan, rowHeight, itemCount, scrollRef } = args;
8239
+ function lowerBound(arr, target) {
8240
+ let lo = 0;
8241
+ let hi = arr.length;
8242
+ while (lo < hi) {
8243
+ const mid = lo + hi >> 1;
8244
+ if (arr[mid] < target) lo = mid + 1;
8245
+ else hi = mid;
8246
+ }
8247
+ return lo;
8248
+ }
8249
+ function useVirtualVariableRows(args) {
8250
+ const { enabled, overscan, rowHeights, scrollRef } = args;
8251
+ const itemCount = rowHeights.length;
8155
8252
  const [viewportHeight, setViewportHeight] = React27.useState(0);
8156
8253
  const [scrollTop, setScrollTop] = React27.useState(0);
8157
8254
  React27.useEffect(() => {
@@ -8170,16 +8267,28 @@ function useVirtualRows(args) {
8170
8267
  el.addEventListener("scroll", onScroll, { passive: true });
8171
8268
  return () => el.removeEventListener("scroll", onScroll);
8172
8269
  }, [scrollRef]);
8270
+ const prefix = React27.useMemo(() => {
8271
+ const out = new Array(itemCount + 1);
8272
+ out[0] = 0;
8273
+ for (let i = 0; i < itemCount; i++) {
8274
+ out[i + 1] = out[i] + (rowHeights[i] ?? 0);
8275
+ }
8276
+ return out;
8277
+ }, [itemCount, rowHeights]);
8173
8278
  return React27.useMemo(() => {
8174
8279
  if (!enabled) {
8175
- return { startIndex: 0, endIndex: itemCount, topSpacer: 0, bottomSpacer: 0 };
8176
- }
8177
- const startIndex = clamp3(Math.floor(scrollTop / rowHeight) - overscan, 0, itemCount);
8178
- const endIndex = clamp3(Math.ceil((scrollTop + viewportHeight) / rowHeight) + overscan, 0, itemCount);
8179
- const topSpacer = startIndex * rowHeight;
8180
- const bottomSpacer = (itemCount - endIndex) * rowHeight;
8181
- return { startIndex, endIndex, topSpacer, bottomSpacer };
8182
- }, [enabled, itemCount, overscan, rowHeight, scrollTop, viewportHeight]);
8280
+ return { startIndex: 0, endIndex: itemCount, topSpacer: 0, bottomSpacer: 0, totalHeight: prefix[itemCount] ?? 0 };
8281
+ }
8282
+ const total = prefix[itemCount] ?? 0;
8283
+ const startPos = Math.max(0, Math.min(scrollTop, total));
8284
+ const endPos = Math.max(0, Math.min(scrollTop + viewportHeight, total));
8285
+ let startIndex = Math.max(0, lowerBound(prefix, startPos) - 1);
8286
+ let endIndex = Math.min(itemCount, lowerBound(prefix, endPos) + overscan);
8287
+ startIndex = clamp3(startIndex - overscan, 0, itemCount);
8288
+ const topSpacer = prefix[startIndex] ?? 0;
8289
+ const bottomSpacer = total - (prefix[endIndex] ?? total);
8290
+ return { startIndex, endIndex, topSpacer, bottomSpacer, totalHeight: total };
8291
+ }, [enabled, itemCount, overscan, prefix, scrollTop, viewportHeight]);
8183
8292
  }
8184
8293
 
8185
8294
  // ../../components/ui/CalendarTimeline/CalendarTimeline.tsx
@@ -8236,7 +8345,19 @@ function CalendarTimeline({
8236
8345
  defaultGroupCollapsed,
8237
8346
  onGroupCollapsedChange,
8238
8347
  resourceColumnWidth,
8348
+ defaultResourceColumnWidth,
8349
+ onResourceColumnWidthChange,
8350
+ minResourceColumnWidth,
8351
+ maxResourceColumnWidth,
8239
8352
  rowHeight,
8353
+ defaultRowHeight,
8354
+ onRowHeightChange,
8355
+ minRowHeight,
8356
+ maxRowHeight,
8357
+ rowHeights,
8358
+ defaultRowHeights,
8359
+ onRowHeightsChange,
8360
+ enableLayoutResize,
8240
8361
  slotMinWidth,
8241
8362
  dayTimeStepMinutes = 60,
8242
8363
  maxLanesPerRow = 3,
@@ -8335,9 +8456,42 @@ function CalendarTimeline({
8335
8456
  };
8336
8457
  return cfgBySize[size];
8337
8458
  }, [size]);
8338
- const effectiveResourceColumnWidth = resourceColumnWidth ?? sizeConfig.resourceColumnWidth;
8339
- const effectiveRowHeight = rowHeight ?? sizeConfig.rowHeight;
8459
+ const canResizeColumn = React28.useMemo(() => {
8460
+ const cfg = enableLayoutResize;
8461
+ if (!cfg) return false;
8462
+ if (cfg === true) return true;
8463
+ return cfg.column !== false;
8464
+ }, [enableLayoutResize]);
8465
+ const canResizeRow = React28.useMemo(() => {
8466
+ const cfg = enableLayoutResize;
8467
+ if (!cfg) return false;
8468
+ if (cfg === true) return true;
8469
+ return cfg.row !== false;
8470
+ }, [enableLayoutResize]);
8471
+ const isControlledResourceColumnWidth = resourceColumnWidth !== void 0;
8472
+ const [internalResourceColumnWidth, setInternalResourceColumnWidth] = React28.useState(() => {
8473
+ const init = defaultResourceColumnWidth ?? sizeConfig.resourceColumnWidth;
8474
+ return typeof init === "number" ? init : sizeConfig.resourceColumnWidth;
8475
+ });
8476
+ React28.useEffect(() => {
8477
+ if (isControlledResourceColumnWidth) return;
8478
+ if (defaultResourceColumnWidth == null) return;
8479
+ setInternalResourceColumnWidth(defaultResourceColumnWidth);
8480
+ }, [defaultResourceColumnWidth, isControlledResourceColumnWidth]);
8481
+ const effectiveResourceColumnWidth = isControlledResourceColumnWidth ? resourceColumnWidth : internalResourceColumnWidth;
8482
+ const isControlledRowHeight = rowHeight !== void 0;
8483
+ const [internalRowHeight, setInternalRowHeight] = React28.useState(() => defaultRowHeight ?? sizeConfig.rowHeight);
8484
+ React28.useEffect(() => {
8485
+ if (isControlledRowHeight) return;
8486
+ if (defaultRowHeight == null) return;
8487
+ setInternalRowHeight(defaultRowHeight);
8488
+ }, [defaultRowHeight, isControlledRowHeight]);
8489
+ const effectiveRowHeight = isControlledRowHeight ? rowHeight : internalRowHeight;
8340
8490
  const effectiveSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
8491
+ const colMin = minResourceColumnWidth ?? 160;
8492
+ const colMax = maxResourceColumnWidth ?? 520;
8493
+ const rowMin = minRowHeight ?? 36;
8494
+ const rowMax = maxRowHeight ?? 120;
8341
8495
  const isControlledView = view !== void 0;
8342
8496
  const [internalView, setInternalView] = React28.useState(defaultView);
8343
8497
  const activeView = isControlledView ? view : internalView;
@@ -8527,6 +8681,135 @@ function CalendarTimeline({
8527
8681
  const bodyRef = React28.useRef(null);
8528
8682
  const headerRef = React28.useRef(null);
8529
8683
  useHorizontalScrollSync({ bodyRef, headerRef, leftRef });
8684
+ const virt = virtualization?.enabled;
8685
+ const overscan = virtualization?.overscan ?? 8;
8686
+ const isControlledRowHeights = rowHeights !== void 0;
8687
+ const [internalRowHeights, setInternalRowHeights] = React28.useState(() => defaultRowHeights ?? {});
8688
+ React28.useEffect(() => {
8689
+ if (isControlledRowHeights) return;
8690
+ if (!defaultRowHeights) return;
8691
+ setInternalRowHeights(defaultRowHeights);
8692
+ }, [defaultRowHeights, isControlledRowHeights]);
8693
+ const activeRowHeights = isControlledRowHeights ? rowHeights : internalRowHeights;
8694
+ const getResourceRowHeight = React28.useCallback(
8695
+ (resourceId) => {
8696
+ const h = activeRowHeights[resourceId];
8697
+ if (typeof h === "number" && Number.isFinite(h) && h > 0) return h;
8698
+ return effectiveRowHeight;
8699
+ },
8700
+ [activeRowHeights, effectiveRowHeight]
8701
+ );
8702
+ const setRowHeightForResource = React28.useCallback(
8703
+ (resourceId, height) => {
8704
+ const clamped = clamp3(Math.round(height), rowMin, rowMax);
8705
+ onRowHeightChange?.(clamped);
8706
+ if (isControlledRowHeights) {
8707
+ const next = { ...activeRowHeights ?? {}, [resourceId]: clamped };
8708
+ onRowHeightsChange?.(next);
8709
+ return;
8710
+ }
8711
+ setInternalRowHeights((prev) => {
8712
+ const next = { ...prev, [resourceId]: clamped };
8713
+ onRowHeightsChange?.(next);
8714
+ return next;
8715
+ });
8716
+ },
8717
+ [activeRowHeights, isControlledRowHeights, onRowHeightChange, onRowHeightsChange, rowMax, rowMin]
8718
+ );
8719
+ const rowHeightsArray = React28.useMemo(() => {
8720
+ return rows.map((r) => {
8721
+ if (r.kind === "resource") return getResourceRowHeight(r.resource.id);
8722
+ return effectiveRowHeight;
8723
+ });
8724
+ }, [effectiveRowHeight, getResourceRowHeight, rows]);
8725
+ const virtualResult = useVirtualVariableRows({
8726
+ enabled: virt,
8727
+ overscan,
8728
+ rowHeights: rowHeightsArray,
8729
+ scrollRef: bodyRef
8730
+ });
8731
+ const startRow = virt ? virtualResult.startIndex : 0;
8732
+ const endRow = virt ? virtualResult.endIndex : rows.length;
8733
+ const topSpacer = virt ? virtualResult.topSpacer : 0;
8734
+ const bottomSpacer = virt ? virtualResult.bottomSpacer : 0;
8735
+ const resizeRef = React28.useRef(null);
8736
+ const setResourceColumnWidth = React28.useCallback(
8737
+ (next) => {
8738
+ const clamped = clamp3(Math.round(next), colMin, colMax);
8739
+ if (!isControlledResourceColumnWidth) setInternalResourceColumnWidth(clamped);
8740
+ onResourceColumnWidthChange?.(clamped);
8741
+ },
8742
+ [colMax, colMin, isControlledResourceColumnWidth, onResourceColumnWidthChange]
8743
+ );
8744
+ const startResize = React28.useCallback(
8745
+ (mode, e, args) => {
8746
+ resizeRef.current = {
8747
+ mode,
8748
+ pointerId: e.pointerId,
8749
+ startX: e.clientX,
8750
+ startY: e.clientY,
8751
+ startWidth: args.startWidth,
8752
+ startHeight: args.startHeight,
8753
+ resourceId: args.resourceId
8754
+ };
8755
+ document.body.style.cursor = mode === "column" ? "col-resize" : "row-resize";
8756
+ document.body.style.userSelect = "none";
8757
+ const onMove = (ev) => {
8758
+ const st = resizeRef.current;
8759
+ if (!st) return;
8760
+ if (ev.pointerId !== st.pointerId) return;
8761
+ if (st.mode === "column") {
8762
+ setResourceColumnWidth(st.startWidth + (ev.clientX - st.startX));
8763
+ } else {
8764
+ if (!st.resourceId) return;
8765
+ setRowHeightForResource(st.resourceId, st.startHeight + (ev.clientY - st.startY));
8766
+ }
8767
+ };
8768
+ const onUp = (ev) => {
8769
+ const st = resizeRef.current;
8770
+ if (!st) return;
8771
+ if (ev.pointerId !== st.pointerId) return;
8772
+ resizeRef.current = null;
8773
+ document.body.style.cursor = "";
8774
+ document.body.style.userSelect = "";
8775
+ window.removeEventListener("pointermove", onMove);
8776
+ window.removeEventListener("pointerup", onUp);
8777
+ };
8778
+ window.addEventListener("pointermove", onMove);
8779
+ window.addEventListener("pointerup", onUp);
8780
+ e.currentTarget.setPointerCapture?.(e.pointerId);
8781
+ e.preventDefault();
8782
+ e.stopPropagation();
8783
+ },
8784
+ [setResourceColumnWidth, setRowHeightForResource]
8785
+ );
8786
+ React28.useEffect(() => {
8787
+ return () => {
8788
+ if (!resizeRef.current) return;
8789
+ resizeRef.current = null;
8790
+ document.body.style.cursor = "";
8791
+ document.body.style.userSelect = "";
8792
+ };
8793
+ }, []);
8794
+ const beginResizeColumn = React28.useCallback(
8795
+ (e) => {
8796
+ if (!canResizeColumn) return;
8797
+ if (typeof effectiveResourceColumnWidth !== "number") return;
8798
+ startResize("column", e, { startWidth: effectiveResourceColumnWidth, startHeight: effectiveRowHeight });
8799
+ },
8800
+ [canResizeColumn, effectiveResourceColumnWidth, effectiveRowHeight, startResize]
8801
+ );
8802
+ const beginResizeResourceRow = React28.useCallback(
8803
+ (resourceId) => (e) => {
8804
+ if (!canResizeRow) return;
8805
+ startResize("row", e, {
8806
+ startWidth: typeof effectiveResourceColumnWidth === "number" ? effectiveResourceColumnWidth : 0,
8807
+ startHeight: getResourceRowHeight(resourceId),
8808
+ resourceId
8809
+ });
8810
+ },
8811
+ [canResizeRow, effectiveResourceColumnWidth, getResourceRowHeight, startResize]
8812
+ );
8530
8813
  const title = React28.useMemo(() => {
8531
8814
  if (activeView === "month") {
8532
8815
  return formatters?.monthTitle?.(activeDate, { locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultMonthTitle(activeDate, resolvedLocale, resolvedTimeZone);
@@ -8550,20 +8833,6 @@ function CalendarTimeline({
8550
8833
  const eventHeight = sizeConfig.eventHeight;
8551
8834
  const laneGap = sizeConfig.laneGap;
8552
8835
  const lanePaddingY = sizeConfig.lanePaddingY;
8553
- const virt = virtualization?.enabled;
8554
- const overscan = virtualization?.overscan ?? 8;
8555
- const {
8556
- startIndex: startRow,
8557
- endIndex: endRow,
8558
- topSpacer,
8559
- bottomSpacer
8560
- } = useVirtualRows({
8561
- enabled: virt,
8562
- overscan,
8563
- rowHeight: effectiveRowHeight,
8564
- itemCount: rows.length,
8565
- scrollRef: bodyRef
8566
- });
8567
8836
  const dragRef = React28.useRef(null);
8568
8837
  const [preview, setPreview] = React28.useState(null);
8569
8838
  const getPointerContext = React28.useCallback(
@@ -8824,12 +9093,34 @@ function CalendarTimeline({
8824
9093
  )) })
8825
9094
  ] }),
8826
9095
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex border-t border-border/20", children: [
8827
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
9096
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
8828
9097
  "div",
8829
9098
  {
8830
- className: "shrink-0 border-r border-border/30 bg-muted/20 flex items-center justify-center",
9099
+ className: "shrink-0 border-r border-border/30 bg-muted/20 flex items-center justify-center relative group/uv-ct-top-left",
8831
9100
  style: { width: effectiveResourceColumnWidth, minWidth: effectiveResourceColumnWidth },
8832
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-xs font-medium text-muted-foreground/70 uppercase tracking-wider", children: t("resourcesHeader") })
9101
+ children: [
9102
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-xs font-medium text-muted-foreground/70 uppercase tracking-wider", children: t("resourcesHeader") }),
9103
+ canResizeColumn && typeof effectiveResourceColumnWidth === "number" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
9104
+ "div",
9105
+ {
9106
+ role: "separator",
9107
+ "aria-orientation": "vertical",
9108
+ "aria-label": "Resize resource column",
9109
+ className: cn(
9110
+ "absolute right-0 top-0 h-full w-3 cursor-col-resize z-20",
9111
+ "bg-transparent hover:bg-primary/10 active:bg-primary/15",
9112
+ "transition-all",
9113
+ "opacity-0 pointer-events-none",
9114
+ "group-hover/uv-ct-top-left:opacity-100 group-hover/uv-ct-top-left:pointer-events-auto"
9115
+ ),
9116
+ onPointerDown: beginResizeColumn,
9117
+ children: [
9118
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute inset-y-2 left-1/2 w-px -translate-x-1/2 bg-border/70" }),
9119
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-70", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react18.GripVertical, { className: "h-4 w-4 text-muted-foreground" }) })
9120
+ ]
9121
+ }
9122
+ ) : null
9123
+ ]
8833
9124
  }
8834
9125
  ),
8835
9126
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: headerRef, className: "overflow-x-auto overflow-y-hidden scrollbar-none", children: slotHeaderNodes })
@@ -8839,13 +9130,33 @@ function CalendarTimeline({
8839
9130
  "div",
8840
9131
  {
8841
9132
  className: cn(
8842
- "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-background to-background/95",
9133
+ "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-background to-background/95 relative",
8843
9134
  sizeConfig.resourceRowClass,
8844
- "hover:from-muted/30 hover:to-muted/10 transition-all duration-200 group"
9135
+ "hover:from-muted/30 hover:to-muted/10 transition-all duration-200 group/uv-ct-row-header"
8845
9136
  ),
8846
9137
  children: [
8847
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "shrink-0 opacity-0 group-hover:opacity-60 transition-opacity cursor-grab", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react18.GripVertical, { className: "h-4 w-4 text-muted-foreground" }) }),
8848
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: cn("flex-1 min-w-0", r.disabled && "opacity-50"), children: renderResource ? renderResource(r) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "font-medium text-sm truncate block", children: r.label }) })
9138
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "shrink-0 opacity-0 group-hover/uv-ct-row-header:opacity-60 transition-opacity cursor-grab", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react18.GripVertical, { className: "h-4 w-4 text-muted-foreground" }) }),
9139
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: cn("flex-1 min-w-0", r.disabled && "opacity-50"), children: renderResource ? renderResource(r) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "font-medium text-sm truncate block", children: r.label }) }),
9140
+ canResizeRow ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
9141
+ "div",
9142
+ {
9143
+ role: "separator",
9144
+ "aria-orientation": "horizontal",
9145
+ "aria-label": "Resize row height",
9146
+ className: cn(
9147
+ "absolute left-0 bottom-0 w-full h-3 cursor-row-resize z-20",
9148
+ "bg-transparent hover:bg-primary/10 active:bg-primary/15",
9149
+ "transition-all",
9150
+ "opacity-0 pointer-events-none",
9151
+ "group-hover/uv-ct-row-header:opacity-100 group-hover/uv-ct-row-header:pointer-events-auto"
9152
+ ),
9153
+ onPointerDown: beginResizeResourceRow(r.id),
9154
+ children: [
9155
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute inset-x-3 top-1/2 h-px -translate-y-1/2 bg-border/70" }),
9156
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute top-1/2 right-3 -translate-y-1/2 opacity-70", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_lucide_react18.GripVertical, { className: "h-4 w-4 text-muted-foreground rotate-90" }) })
9157
+ ]
9158
+ }
9159
+ ) : null
8849
9160
  ]
8850
9161
  }
8851
9162
  );
@@ -8898,11 +9209,12 @@ function CalendarTimeline({
8898
9209
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: topSpacer } }),
8899
9210
  rows.slice(startRow, endRow).map((row, idx) => {
8900
9211
  const rowIndex = startRow + idx;
9212
+ const h = rowHeightsArray[rowIndex] ?? effectiveRowHeight;
8901
9213
  if (row.kind === "group") {
8902
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: effectiveRowHeight }, children: renderGroupRow(row.group) }, `lg_${row.group.id}_${rowIndex}`);
9214
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: h }, children: renderGroupRow(row.group) }, `lg_${row.group.id}_${rowIndex}`);
8903
9215
  }
8904
9216
  const r = row.resource;
8905
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: effectiveRowHeight }, children: ResourceCell(r) }, `lr_${r.id}_${rowIndex}`);
9217
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: h }, children: ResourceCell(r) }, `lr_${r.id}_${rowIndex}`);
8906
9218
  }),
8907
9219
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: bottomSpacer } })
8908
9220
  ]
@@ -8919,8 +9231,9 @@ function CalendarTimeline({
8919
9231
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { height: topSpacer } }),
8920
9232
  rows.slice(startRow, endRow).map((row, idx) => {
8921
9233
  const rowIndex = startRow + idx;
9234
+ const h = rowHeightsArray[rowIndex] ?? effectiveRowHeight;
8922
9235
  if (row.kind === "group") {
8923
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex", style: { height: effectiveRowHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "border-b border-border/30 bg-linear-to-r from-muted/15 to-muted/5", style: { width: gridWidth, minWidth: gridWidth } }) }, `rg_${row.group.id}_${rowIndex}`);
9236
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex", style: { height: h }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "border-b border-border/30 bg-linear-to-r from-muted/15 to-muted/5", style: { width: gridWidth, minWidth: gridWidth } }) }, `rg_${row.group.id}_${rowIndex}`);
8924
9237
  }
8925
9238
  const r = row.resource;
8926
9239
  const layout = layoutsByResource.get(r.id) ?? { visible: [], hidden: [] };
@@ -8929,7 +9242,7 @@ function CalendarTimeline({
8929
9242
  "div",
8930
9243
  {
8931
9244
  className: "group/row hover:bg-muted/5 transition-colors duration-150",
8932
- style: { height: effectiveRowHeight },
9245
+ style: { height: h },
8933
9246
  "data-uv-ct-row": r.id,
8934
9247
  children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "relative shrink-0", style: { width: gridWidth, minWidth: gridWidth, height: "100%" }, children: [
8935
9248
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute inset-0", onPointerDown: onPointerDownCell, "data-uv-ct-timeline": true, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute inset-0 flex", children: slots.map((s, i2) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(