@underverse-ui/underverse 0.2.99 → 0.2.101

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
@@ -8759,6 +8759,7 @@ function CalendarTimeline({
8759
8759
  rowHeights,
8760
8760
  defaultRowHeights,
8761
8761
  onRowHeightsChange,
8762
+ autoRowHeight,
8762
8763
  enableLayoutResize,
8763
8764
  slotMinWidth,
8764
8765
  dayTimeStepMinutes = 60,
@@ -8811,6 +8812,10 @@ function CalendarTimeline({
8811
8812
  [isControlledEventSheetOpen, onEventSheetOpenChange, setSelectedEventId]
8812
8813
  );
8813
8814
  const sizeConfig = React28.useMemo(() => getSizeConfig(size), [size]);
8815
+ const densityClass = sizeConfig.densityClass;
8816
+ const eventHeight = sizeConfig.eventHeight;
8817
+ const laneGap = sizeConfig.laneGap;
8818
+ const lanePaddingY = sizeConfig.lanePaddingY;
8814
8819
  const canResizeColumn = React28.useMemo(() => {
8815
8820
  const cfg = enableLayoutResize;
8816
8821
  if (!cfg) return false;
@@ -8998,13 +9003,43 @@ function CalendarTimeline({
8998
9003
  setInternalRowHeights(defaultRowHeights);
8999
9004
  }, [defaultRowHeights, isControlledRowHeights]);
9000
9005
  const activeRowHeights = isControlledRowHeights ? rowHeights : internalRowHeights;
9006
+ const autoRowHeightCfg = React28.useMemo(() => {
9007
+ if (!autoRowHeight) return null;
9008
+ return autoRowHeight === true ? {} : autoRowHeight;
9009
+ }, [autoRowHeight]);
9010
+ const effectiveMaxLanesPerRow = React28.useMemo(() => {
9011
+ if (!autoRowHeightCfg) return maxLanesPerRow;
9012
+ const maxLanes = autoRowHeightCfg.maxLanesPerRow;
9013
+ if (typeof maxLanes === "number" && Number.isFinite(maxLanes) && maxLanes > 0) return Math.floor(maxLanes);
9014
+ return Number.POSITIVE_INFINITY;
9015
+ }, [autoRowHeightCfg, maxLanesPerRow]);
9016
+ const autoRowHeightsByResource = React28.useMemo(() => {
9017
+ if (!autoRowHeightCfg) return null;
9018
+ const maxRowHeight2 = autoRowHeightCfg.maxRowHeight;
9019
+ const out = /* @__PURE__ */ new Map();
9020
+ for (const [resourceId, list] of eventsByResource.entries()) {
9021
+ const mapped = list.map((ev) => {
9022
+ const startIdx = binarySearchLastLE(slotStarts, ev._start);
9023
+ const endIdx = clamp3(binarySearchFirstGE(slotStarts, ev._end), startIdx + 1, slots.length);
9024
+ return { startIdx, endIdx };
9025
+ });
9026
+ const { laneCount } = intervalPack(mapped);
9027
+ const lanesToFit = Number.isFinite(effectiveMaxLanesPerRow) ? Math.max(1, Math.min(laneCount, effectiveMaxLanesPerRow)) : Math.max(1, laneCount);
9028
+ const needed = lanePaddingY * 2 + lanesToFit * eventHeight + laneGap * Math.max(0, lanesToFit - 1);
9029
+ const next = typeof maxRowHeight2 === "number" && Number.isFinite(maxRowHeight2) && maxRowHeight2 > 0 ? Math.min(needed, maxRowHeight2) : needed;
9030
+ out.set(resourceId, next);
9031
+ }
9032
+ return out;
9033
+ }, [autoRowHeightCfg, eventHeight, eventsByResource, laneGap, lanePaddingY, slotStarts, slots.length, effectiveMaxLanesPerRow]);
9001
9034
  const getResourceRowHeight = React28.useCallback(
9002
9035
  (resourceId) => {
9003
9036
  const h = activeRowHeights[resourceId];
9004
- if (typeof h === "number" && Number.isFinite(h) && h > 0) return h;
9005
- return effectiveRowHeight;
9037
+ const base = typeof h === "number" && Number.isFinite(h) && h > 0 ? h : effectiveRowHeight;
9038
+ const auto = autoRowHeightsByResource?.get(resourceId);
9039
+ if (typeof auto === "number" && Number.isFinite(auto) && auto > 0) return Math.max(base, auto);
9040
+ return base;
9006
9041
  },
9007
- [activeRowHeights, effectiveRowHeight]
9042
+ [activeRowHeights, autoRowHeightsByResource, effectiveRowHeight]
9008
9043
  );
9009
9044
  const setRowHeightForResource = React28.useCallback(
9010
9045
  (resourceId, height) => {
@@ -9137,10 +9172,6 @@ function CalendarTimeline({
9137
9172
  const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
9138
9173
  return fmt.format(range.start);
9139
9174
  }, [activeDate, activeView, formatters, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone]);
9140
- const densityClass = sizeConfig.densityClass;
9141
- const eventHeight = sizeConfig.eventHeight;
9142
- const laneGap = sizeConfig.laneGap;
9143
- const lanePaddingY = sizeConfig.lanePaddingY;
9144
9175
  const createMode = interactions?.createMode ?? "drag";
9145
9176
  const canCreate = !isViewOnly && (interactions?.creatable ?? false) && !!onCreateEvent;
9146
9177
  const [createOpen, setCreateOpen] = React28.useState(false);
@@ -9207,6 +9238,7 @@ function CalendarTimeline({
9207
9238
  const dragRef = React28.useRef(null);
9208
9239
  const [preview, setPreview] = React28.useState(null);
9209
9240
  const suppressNextEventClickRef = React28.useRef(false);
9241
+ const [hoverCell, setHoverCell] = React28.useState(null);
9210
9242
  const autoScrollStateRef = React28.useRef({
9211
9243
  dir: 0,
9212
9244
  speed: 0,
@@ -9416,7 +9448,32 @@ function CalendarTimeline({
9416
9448
  };
9417
9449
  const onPointerMove = (e) => {
9418
9450
  const drag = dragRef.current;
9419
- if (!drag || drag.pointerId !== e.pointerId) return;
9451
+ if (!drag) {
9452
+ if (isViewOnly) return;
9453
+ if (!(interactions?.creatable ?? false)) return;
9454
+ const target = e.target;
9455
+ if (target?.closest?.("[data-uv-ct-event]")) {
9456
+ if (hoverCell) setHoverCell(null);
9457
+ return;
9458
+ }
9459
+ const ctx = getPointerContext(e.clientX, e.clientY);
9460
+ if (!ctx) {
9461
+ if (hoverCell) setHoverCell(null);
9462
+ return;
9463
+ }
9464
+ const rowEl = target?.closest?.("[data-uv-ct-row]");
9465
+ if (!rowEl) {
9466
+ if (hoverCell) setHoverCell(null);
9467
+ return;
9468
+ }
9469
+ const rect = rowEl.getBoundingClientRect();
9470
+ const y = clamp3(e.clientY - rect.top, 0, rect.height);
9471
+ if (!hoverCell || hoverCell.resourceId !== ctx.resourceId || hoverCell.slotIdx !== ctx.slotIdx || Math.abs(hoverCell.y - y) > 0.5) {
9472
+ setHoverCell({ resourceId: ctx.resourceId, slotIdx: ctx.slotIdx, y });
9473
+ }
9474
+ return;
9475
+ }
9476
+ if (drag.pointerId !== e.pointerId) return;
9420
9477
  updateAutoScrollFromPointer(e.clientX, e.clientY);
9421
9478
  updateDragPreview(e.clientX, e.clientY);
9422
9479
  };
@@ -9425,6 +9482,7 @@ function CalendarTimeline({
9425
9482
  if (!drag || drag.pointerId !== e.pointerId) return;
9426
9483
  dragRef.current = null;
9427
9484
  stopAutoScroll();
9485
+ setHoverCell(null);
9428
9486
  if (!preview) {
9429
9487
  setPreview(null);
9430
9488
  return;
@@ -9525,10 +9583,10 @@ function CalendarTimeline({
9525
9583
  return { ev: { ...ev, _start: s, _end: e }, startIdx, endIdx };
9526
9584
  });
9527
9585
  const { packed, laneCount } = intervalPack(mapped);
9528
- const visible = packed.filter((p) => p.lane < maxLanesPerRow);
9529
- const hidden = packed.filter((p) => p.lane >= maxLanesPerRow);
9586
+ const visible = packed.filter((p) => p.lane < effectiveMaxLanesPerRow);
9587
+ const hidden = packed.filter((p) => p.lane >= effectiveMaxLanesPerRow);
9530
9588
  const rowHeightPx = getResourceRowHeight(resourceId);
9531
- const visibleLaneCount = Math.max(1, Math.min(laneCount, maxLanesPerRow));
9589
+ const visibleLaneCount = Math.max(1, Math.min(laneCount, effectiveMaxLanesPerRow));
9532
9590
  const available = Math.max(0, rowHeightPx - lanePaddingY * 2 - laneGap * Math.max(0, visibleLaneCount - 1));
9533
9591
  const fitPerLane = visibleLaneCount > 0 ? Math.floor(available / visibleLaneCount) : eventHeight;
9534
9592
  const perLaneHeight = Math.max(9, Math.min(eventHeight, fitPerLane || eventHeight));
@@ -9546,7 +9604,7 @@ function CalendarTimeline({
9546
9604
  });
9547
9605
  }
9548
9606
  return map;
9549
- }, [eventsByResource, getResourceRowHeight, laneGap, lanePaddingY, slotStarts, slots.length, slotWidth, maxLanesPerRow, preview, eventHeight]);
9607
+ }, [eventsByResource, getResourceRowHeight, laneGap, lanePaddingY, slotStarts, slots.length, slotWidth, effectiveMaxLanesPerRow, preview, eventHeight]);
9550
9608
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
9551
9609
  "div",
9552
9610
  {
@@ -9597,6 +9655,7 @@ function CalendarTimeline({
9597
9655
  className: "relative flex-1 overflow-auto scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent",
9598
9656
  onPointerMove,
9599
9657
  onPointerUp,
9658
+ onPointerLeave: () => setHoverCell(null),
9600
9659
  children: [
9601
9660
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { style: { height: topSpacer } }),
9602
9661
  rows.slice(startRow, endRow).map((row, idx) => {
@@ -9608,6 +9667,7 @@ function CalendarTimeline({
9608
9667
  const r = row.resource;
9609
9668
  const layout = layoutsByResource.get(r.id) ?? { visible: [], hidden: [], baseTop: lanePaddingY, eventHeight };
9610
9669
  const canMore = layout.hidden.length > 0 && !!onMoreClick;
9670
+ const showCreateHint = !isViewOnly && (interactions?.creatable ?? false) && !preview && hoverCell?.resourceId === r.id;
9611
9671
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9612
9672
  "div",
9613
9673
  {
@@ -9680,6 +9740,7 @@ function CalendarTimeline({
9680
9740
  ev.className,
9681
9741
  isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-background scale-[1.02] z-10"
9682
9742
  ),
9743
+ "data-uv-ct-event": true,
9683
9744
  style: {
9684
9745
  left,
9685
9746
  top,
@@ -9750,6 +9811,24 @@ function CalendarTimeline({
9750
9811
  }
9751
9812
  );
9752
9813
  })() : null,
9814
+ showCreateHint ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9815
+ "div",
9816
+ {
9817
+ className: cn(
9818
+ "pointer-events-none absolute z-20",
9819
+ "h-5 w-5 rounded-full",
9820
+ "bg-background/80 backdrop-blur-sm",
9821
+ "border border-border/60 shadow-xs",
9822
+ "flex items-center justify-center"
9823
+ ),
9824
+ style: {
9825
+ left: hoverCell.slotIdx * slotWidth + slotWidth / 2 - 10,
9826
+ top: clamp3(Math.round(hoverCell.y - 10), 6, Math.max(6, h - 26))
9827
+ },
9828
+ "aria-hidden": true,
9829
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react20.Plus, { className: "h-3.5 w-3.5 text-muted-foreground" })
9830
+ }
9831
+ ) : null,
9753
9832
  canMore ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
9754
9833
  "button",
9755
9834
  {