@underverse-ui/underverse 0.2.87 → 0.2.89

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.d.cts CHANGED
@@ -904,6 +904,7 @@ declare function Calendar({ month, defaultMonth, onMonthChange, value, defaultVa
904
904
  type CalendarTimelineView = "month" | "week" | "day";
905
905
  type CalendarTimelineDateInput = Date | string | number;
906
906
  type CalendarTimelineSize = "sm" | "md" | "xl";
907
+ type CalendarTimelineSheetSize = "sm" | "md" | "lg" | "xl" | "full";
907
908
  interface CalendarTimelineGroup {
908
909
  id: string;
909
910
  label: React$1.ReactNode;
@@ -980,6 +981,26 @@ interface CalendarTimelineProps<TResourceMeta = unknown, TEventMeta = unknown> e
980
981
  resources: CalendarTimelineResource<TResourceMeta>[];
981
982
  events: CalendarTimelineEvent<TEventMeta>[];
982
983
  size?: CalendarTimelineSize;
984
+ /**
985
+ * Enable the built-in right-side event details sheet on click.
986
+ * If `renderEventSheet` is provided, this defaults to enabled.
987
+ */
988
+ enableEventSheet?: boolean;
989
+ eventSheetSize?: CalendarTimelineSheetSize;
990
+ renderEventSheet?: (args: {
991
+ event: CalendarTimelineEvent<TEventMeta>;
992
+ resource?: CalendarTimelineResource<TResourceMeta>;
993
+ close: () => void;
994
+ locale: string;
995
+ timeZone: string;
996
+ view: CalendarTimelineView;
997
+ }) => React$1.ReactNode;
998
+ selectedEventId?: string | null;
999
+ defaultSelectedEventId?: string | null;
1000
+ onSelectedEventIdChange?: (eventId: string | null) => void;
1001
+ eventSheetOpen?: boolean;
1002
+ defaultEventSheetOpen?: boolean;
1003
+ onEventSheetOpenChange?: (open: boolean) => void;
983
1004
  view?: CalendarTimelineView;
984
1005
  defaultView?: CalendarTimelineView;
985
1006
  onViewChange?: (view: CalendarTimelineView) => void;
@@ -1041,7 +1062,7 @@ interface CalendarTimelineProps<TResourceMeta = unknown, TEventMeta = unknown> e
1041
1062
  virtualization?: CalendarTimelineVirtualization;
1042
1063
  }
1043
1064
 
1044
- declare function CalendarTimeline<TResourceMeta = unknown, TEventMeta = unknown>({ resources, events, size, view, defaultView, onViewChange, date, defaultDate, onDateChange, weekStartsOn, locale, timeZone, labels, formatters, groups, groupCollapsed, defaultGroupCollapsed, onGroupCollapsedChange, resourceColumnWidth, rowHeight, slotMinWidth, dayTimeStepMinutes, maxLanesPerRow, now, renderResource, renderGroup, renderEvent, interactions, onRangeChange, onEventClick, onEventDoubleClick, onCreateEvent, onEventMove, onEventResize, onMoreClick, virtualization, className, ...rest }: CalendarTimelineProps<TResourceMeta, TEventMeta>): react_jsx_runtime.JSX.Element;
1065
+ declare function CalendarTimeline<TResourceMeta = unknown, TEventMeta = unknown>({ resources, events, size, enableEventSheet, eventSheetSize, renderEventSheet, selectedEventId, defaultSelectedEventId, onSelectedEventIdChange, eventSheetOpen, defaultEventSheetOpen, onEventSheetOpenChange, view, defaultView, onViewChange, date, defaultDate, onDateChange, weekStartsOn, locale, timeZone, labels, formatters, groups, groupCollapsed, defaultGroupCollapsed, onGroupCollapsedChange, resourceColumnWidth, rowHeight, slotMinWidth, dayTimeStepMinutes, maxLanesPerRow, now, renderResource, renderGroup, renderEvent, interactions, onRangeChange, onEventClick, onEventDoubleClick, onCreateEvent, onEventMove, onEventResize, onMoreClick, virtualization, className, ...rest }: CalendarTimelineProps<TResourceMeta, TEventMeta>): react_jsx_runtime.JSX.Element;
1045
1066
 
1046
1067
  type ComboboxOption = string | {
1047
1068
  label: string;
package/dist/index.d.ts CHANGED
@@ -904,6 +904,7 @@ declare function Calendar({ month, defaultMonth, onMonthChange, value, defaultVa
904
904
  type CalendarTimelineView = "month" | "week" | "day";
905
905
  type CalendarTimelineDateInput = Date | string | number;
906
906
  type CalendarTimelineSize = "sm" | "md" | "xl";
907
+ type CalendarTimelineSheetSize = "sm" | "md" | "lg" | "xl" | "full";
907
908
  interface CalendarTimelineGroup {
908
909
  id: string;
909
910
  label: React$1.ReactNode;
@@ -980,6 +981,26 @@ interface CalendarTimelineProps<TResourceMeta = unknown, TEventMeta = unknown> e
980
981
  resources: CalendarTimelineResource<TResourceMeta>[];
981
982
  events: CalendarTimelineEvent<TEventMeta>[];
982
983
  size?: CalendarTimelineSize;
984
+ /**
985
+ * Enable the built-in right-side event details sheet on click.
986
+ * If `renderEventSheet` is provided, this defaults to enabled.
987
+ */
988
+ enableEventSheet?: boolean;
989
+ eventSheetSize?: CalendarTimelineSheetSize;
990
+ renderEventSheet?: (args: {
991
+ event: CalendarTimelineEvent<TEventMeta>;
992
+ resource?: CalendarTimelineResource<TResourceMeta>;
993
+ close: () => void;
994
+ locale: string;
995
+ timeZone: string;
996
+ view: CalendarTimelineView;
997
+ }) => React$1.ReactNode;
998
+ selectedEventId?: string | null;
999
+ defaultSelectedEventId?: string | null;
1000
+ onSelectedEventIdChange?: (eventId: string | null) => void;
1001
+ eventSheetOpen?: boolean;
1002
+ defaultEventSheetOpen?: boolean;
1003
+ onEventSheetOpenChange?: (open: boolean) => void;
983
1004
  view?: CalendarTimelineView;
984
1005
  defaultView?: CalendarTimelineView;
985
1006
  onViewChange?: (view: CalendarTimelineView) => void;
@@ -1041,7 +1062,7 @@ interface CalendarTimelineProps<TResourceMeta = unknown, TEventMeta = unknown> e
1041
1062
  virtualization?: CalendarTimelineVirtualization;
1042
1063
  }
1043
1064
 
1044
- declare function CalendarTimeline<TResourceMeta = unknown, TEventMeta = unknown>({ resources, events, size, view, defaultView, onViewChange, date, defaultDate, onDateChange, weekStartsOn, locale, timeZone, labels, formatters, groups, groupCollapsed, defaultGroupCollapsed, onGroupCollapsedChange, resourceColumnWidth, rowHeight, slotMinWidth, dayTimeStepMinutes, maxLanesPerRow, now, renderResource, renderGroup, renderEvent, interactions, onRangeChange, onEventClick, onEventDoubleClick, onCreateEvent, onEventMove, onEventResize, onMoreClick, virtualization, className, ...rest }: CalendarTimelineProps<TResourceMeta, TEventMeta>): react_jsx_runtime.JSX.Element;
1065
+ declare function CalendarTimeline<TResourceMeta = unknown, TEventMeta = unknown>({ resources, events, size, enableEventSheet, eventSheetSize, renderEventSheet, selectedEventId, defaultSelectedEventId, onSelectedEventIdChange, eventSheetOpen, defaultEventSheetOpen, onEventSheetOpenChange, view, defaultView, onViewChange, date, defaultDate, onDateChange, weekStartsOn, locale, timeZone, labels, formatters, groups, groupCollapsed, defaultGroupCollapsed, onGroupCollapsedChange, resourceColumnWidth, rowHeight, slotMinWidth, dayTimeStepMinutes, maxLanesPerRow, now, renderResource, renderGroup, renderEvent, interactions, onRangeChange, onEventClick, onEventDoubleClick, onCreateEvent, onEventMove, onEventResize, onMoreClick, virtualization, className, ...rest }: CalendarTimelineProps<TResourceMeta, TEventMeta>): react_jsx_runtime.JSX.Element;
1045
1066
 
1046
1067
  type ComboboxOption = string | {
1047
1068
  label: string;
package/dist/index.js CHANGED
@@ -7627,6 +7627,18 @@ function getZonedParts(date, timeZone) {
7627
7627
  second: get("second")
7628
7628
  };
7629
7629
  }
7630
+ function getIsoWeekInfo(date, timeZone) {
7631
+ const p = getZonedParts(date, timeZone);
7632
+ const target = new Date(Date.UTC(p.year, p.month - 1, p.day));
7633
+ const dayNr = (target.getUTCDay() + 6) % 7;
7634
+ target.setUTCDate(target.getUTCDate() - dayNr + 3);
7635
+ const isoYear = target.getUTCFullYear();
7636
+ const firstThursday = new Date(Date.UTC(isoYear, 0, 4));
7637
+ const firstDayNr = (firstThursday.getUTCDay() + 6) % 7;
7638
+ firstThursday.setUTCDate(firstThursday.getUTCDate() - firstDayNr + 3);
7639
+ const week = 1 + Math.round((target.getTime() - firstThursday.getTime()) / (7 * 24 * 60 * 60 * 1e3));
7640
+ return { year: isoYear, week };
7641
+ }
7630
7642
  function partsToUtcMs(p) {
7631
7643
  return Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
7632
7644
  }
@@ -7820,6 +7832,15 @@ function CalendarTimeline({
7820
7832
  resources,
7821
7833
  events,
7822
7834
  size = "md",
7835
+ enableEventSheet,
7836
+ eventSheetSize = "md",
7837
+ renderEventSheet,
7838
+ selectedEventId,
7839
+ defaultSelectedEventId,
7840
+ onSelectedEventIdChange,
7841
+ eventSheetOpen,
7842
+ defaultEventSheetOpen,
7843
+ onEventSheetOpenChange,
7823
7844
  view,
7824
7845
  defaultView = "month",
7825
7846
  onViewChange,
@@ -7860,6 +7881,28 @@ function CalendarTimeline({
7860
7881
  const detectedLocale = useLocale();
7861
7882
  const resolvedLocale = React28.useMemo(() => localeToBCP47(locale ?? detectedLocale), [locale, detectedLocale]);
7862
7883
  const resolvedTimeZone = React28.useMemo(() => timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC", [timeZone]);
7884
+ const effectiveEnableEventSheet = enableEventSheet ?? Boolean(renderEventSheet);
7885
+ const isControlledSelectedEventId = selectedEventId !== void 0;
7886
+ const [internalSelectedEventId, setInternalSelectedEventId] = React28.useState(defaultSelectedEventId ?? null);
7887
+ const activeSelectedEventId = isControlledSelectedEventId ? selectedEventId : internalSelectedEventId;
7888
+ const setSelectedEventId = React28.useCallback(
7889
+ (next) => {
7890
+ if (!isControlledSelectedEventId) setInternalSelectedEventId(next);
7891
+ onSelectedEventIdChange?.(next);
7892
+ },
7893
+ [isControlledSelectedEventId, onSelectedEventIdChange]
7894
+ );
7895
+ const isControlledEventSheetOpen = eventSheetOpen !== void 0;
7896
+ const [internalEventSheetOpen, setInternalEventSheetOpen] = React28.useState(defaultEventSheetOpen ?? false);
7897
+ const activeEventSheetOpen = isControlledEventSheetOpen ? Boolean(eventSheetOpen) : internalEventSheetOpen;
7898
+ const setEventSheetOpen = React28.useCallback(
7899
+ (next) => {
7900
+ if (!isControlledEventSheetOpen) setInternalEventSheetOpen(next);
7901
+ onEventSheetOpenChange?.(next);
7902
+ if (!next) setSelectedEventId(null);
7903
+ },
7904
+ [isControlledEventSheetOpen, onEventSheetOpenChange, setSelectedEventId]
7905
+ );
7863
7906
  const sizeConfig = React28.useMemo(() => {
7864
7907
  const cfgBySize = {
7865
7908
  sm: {
@@ -8071,13 +8114,59 @@ function CalendarTimeline({
8071
8114
  }
8072
8115
  return map;
8073
8116
  }, [normalizedEvents]);
8117
+ const resourceById = React28.useMemo(() => {
8118
+ const map = /* @__PURE__ */ new Map();
8119
+ for (const r of resources) map.set(r.id, r);
8120
+ return map;
8121
+ }, [resources]);
8122
+ const selectedEvent = React28.useMemo(() => {
8123
+ if (!activeSelectedEventId) return null;
8124
+ const found = normalizedEvents.find((e) => e.id === activeSelectedEventId);
8125
+ return found ?? null;
8126
+ }, [activeSelectedEventId, normalizedEvents]);
8127
+ const selectedResource = React28.useMemo(() => {
8128
+ if (!selectedEvent) return void 0;
8129
+ return resourceById.get(selectedEvent.resourceId);
8130
+ }, [resourceById, selectedEvent]);
8131
+ const selectedTimeText = React28.useMemo(() => {
8132
+ if (!selectedEvent) return "";
8133
+ return formatters?.eventTime?.({
8134
+ start: selectedEvent._start,
8135
+ end: selectedEvent._end,
8136
+ locale: resolvedLocale,
8137
+ timeZone: resolvedTimeZone,
8138
+ view: activeView
8139
+ }) ?? defaultEventTime({ start: selectedEvent._start, end: selectedEvent._end, locale: resolvedLocale, timeZone: resolvedTimeZone, view: activeView });
8140
+ }, [activeView, formatters, resolvedLocale, resolvedTimeZone, selectedEvent]);
8141
+ React28.useEffect(() => {
8142
+ if (!effectiveEnableEventSheet) return;
8143
+ if (activeEventSheetOpen && activeSelectedEventId && !selectedEvent) {
8144
+ setEventSheetOpen(false);
8145
+ }
8146
+ }, [activeEventSheetOpen, activeSelectedEventId, effectiveEnableEventSheet, selectedEvent, setEventSheetOpen]);
8074
8147
  const leftRef = React28.useRef(null);
8075
8148
  const bodyRef = React28.useRef(null);
8076
8149
  const headerRef = React28.useRef(null);
8077
8150
  useHorizontalScrollSync({ bodyRef, headerRef, leftRef });
8078
8151
  const title = React28.useMemo(() => {
8079
- return formatters?.monthTitle?.(activeDate, { locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultMonthTitle(activeDate, resolvedLocale, resolvedTimeZone);
8080
- }, [activeDate, formatters, resolvedLocale, resolvedTimeZone]);
8152
+ if (activeView === "month") {
8153
+ return formatters?.monthTitle?.(activeDate, { locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultMonthTitle(activeDate, resolvedLocale, resolvedTimeZone);
8154
+ }
8155
+ if (activeView === "week") {
8156
+ const { week } = getIsoWeekInfo(range.start, resolvedTimeZone);
8157
+ const fmt2 = getDtf(resolvedLocale, resolvedTimeZone, { month: "short", day: "numeric" });
8158
+ const fmtYear = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
8159
+ const endInclusive = new Date(range.end.getTime() - 1);
8160
+ const a = fmt2.format(range.start);
8161
+ const b = fmt2.format(endInclusive);
8162
+ const ya = fmtYear.format(range.start);
8163
+ const yb = fmtYear.format(endInclusive);
8164
+ const rangeText = ya === yb ? `${a} \u2013 ${b}, ${ya}` : `${a}, ${ya} \u2013 ${b}, ${yb}`;
8165
+ return `${l.week} ${week} \u2022 ${rangeText}`;
8166
+ }
8167
+ const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
8168
+ return fmt.format(range.start);
8169
+ }, [activeDate, activeView, formatters, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone]);
8081
8170
  const densityClass = sizeConfig.densityClass;
8082
8171
  const eventHeight = sizeConfig.eventHeight;
8083
8172
  const laneGap = sizeConfig.laneGap;
@@ -8103,10 +8192,9 @@ function CalendarTimeline({
8103
8192
  const body = bodyRef.current;
8104
8193
  if (!body) return null;
8105
8194
  const el = document.elementFromPoint(clientX, clientY);
8106
- const timelineEl = el?.closest?.("[data-uv-ct-timeline]");
8107
- if (!timelineEl) return null;
8108
- const timelineRect = timelineEl.getBoundingClientRect();
8109
- const x = clientX - timelineRect.left + body.scrollLeft;
8195
+ if (!el || !body.contains(el)) return null;
8196
+ const bodyRect = body.getBoundingClientRect();
8197
+ const x = clientX - bodyRect.left + body.scrollLeft;
8110
8198
  const slotIdx = clamp3(Math.floor(x / slotWidth), 0, Math.max(0, slots.length - 1));
8111
8199
  const rowEl = el?.closest?.("[data-uv-ct-row]");
8112
8200
  const rid = rowEl?.dataset?.uvCtRow ?? null;
@@ -8494,7 +8582,14 @@ function CalendarTimeline({
8494
8582
  ev.title ? /* @__PURE__ */ jsx33("span", { className: "font-semibold text-[11px] truncate leading-tight", children: ev.title }) : null,
8495
8583
  /* @__PURE__ */ jsx33("span", { className: "text-[10px] opacity-70 truncate ml-auto", children: timeText })
8496
8584
  ] });
8497
- return /* @__PURE__ */ jsxs28(
8585
+ const resource = resourceById.get(ev.resourceId);
8586
+ const tooltipTitle = ev.title || ev.id;
8587
+ const tooltipContent = /* @__PURE__ */ jsxs28("div", { className: "flex flex-col gap-0.5", children: [
8588
+ /* @__PURE__ */ jsx33("div", { className: "font-semibold", children: tooltipTitle }),
8589
+ /* @__PURE__ */ jsx33("div", { className: "text-xs opacity-80", children: timeText }),
8590
+ resource?.label ? /* @__PURE__ */ jsx33("div", { className: "text-xs opacity-70", children: resource.label }) : null
8591
+ ] });
8592
+ return /* @__PURE__ */ jsx33(Tooltip, { content: tooltipContent, placement: "top", delay: { open: 250, close: 0 }, children: /* @__PURE__ */ jsxs28(
8498
8593
  "div",
8499
8594
  {
8500
8595
  className: cn(
@@ -8517,7 +8612,13 @@ function CalendarTimeline({
8517
8612
  role: "button",
8518
8613
  tabIndex: 0,
8519
8614
  "aria-label": aria,
8520
- onClick: () => onEventClick?.(ev),
8615
+ onClick: () => {
8616
+ onEventClick?.(ev);
8617
+ if (effectiveEnableEventSheet) {
8618
+ setSelectedEventId(ev.id);
8619
+ setEventSheetOpen(true);
8620
+ }
8621
+ },
8521
8622
  onDoubleClick: () => onEventDoubleClick?.(ev),
8522
8623
  onPointerDown: (e) => onPointerDownEvent(e, ev, "move"),
8523
8624
  children: [
@@ -8539,9 +8640,8 @@ function CalendarTimeline({
8539
8640
  ] }) : null,
8540
8641
  node
8541
8642
  ]
8542
- },
8543
- ev.id
8544
- );
8643
+ }
8644
+ ) }, ev.id);
8545
8645
  }),
8546
8646
  preview && preview.resourceId === r.id && !preview.eventId ? (() => {
8547
8647
  const startIdx = binarySearchLastLE(slotStarts, preview.start);
@@ -8584,7 +8684,35 @@ function CalendarTimeline({
8584
8684
  ]
8585
8685
  }
8586
8686
  )
8587
- ] })
8687
+ ] }),
8688
+ effectiveEnableEventSheet && selectedEvent ? /* @__PURE__ */ jsx33(
8689
+ Sheet,
8690
+ {
8691
+ open: activeEventSheetOpen,
8692
+ onOpenChange: setEventSheetOpen,
8693
+ side: "right",
8694
+ size: eventSheetSize,
8695
+ title: selectedEvent.title ?? "Event",
8696
+ description: selectedTimeText || void 0,
8697
+ children: renderEventSheet ? renderEventSheet({
8698
+ event: selectedEvent,
8699
+ resource: selectedResource,
8700
+ close: () => setEventSheetOpen(false),
8701
+ locale: resolvedLocale,
8702
+ timeZone: resolvedTimeZone,
8703
+ view: activeView
8704
+ }) : /* @__PURE__ */ jsxs28("div", { className: "space-y-3", children: [
8705
+ selectedResource?.label ? /* @__PURE__ */ jsxs28("div", { children: [
8706
+ /* @__PURE__ */ jsx33("div", { className: "text-xs text-muted-foreground", children: t("resourcesHeader") }),
8707
+ /* @__PURE__ */ jsx33("div", { className: "font-medium", children: selectedResource.label })
8708
+ ] }) : null,
8709
+ /* @__PURE__ */ jsxs28("div", { children: [
8710
+ /* @__PURE__ */ jsx33("div", { className: "text-xs text-muted-foreground", children: "ID" }),
8711
+ /* @__PURE__ */ jsx33("div", { className: "font-mono text-xs break-all", children: selectedEvent.id })
8712
+ ] })
8713
+ ] })
8714
+ }
8715
+ ) : null
8588
8716
  ]
8589
8717
  }
8590
8718
  );