@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.cjs CHANGED
@@ -7802,6 +7802,18 @@ function getZonedParts(date, timeZone) {
7802
7802
  second: get("second")
7803
7803
  };
7804
7804
  }
7805
+ function getIsoWeekInfo(date, timeZone) {
7806
+ const p = getZonedParts(date, timeZone);
7807
+ const target = new Date(Date.UTC(p.year, p.month - 1, p.day));
7808
+ const dayNr = (target.getUTCDay() + 6) % 7;
7809
+ target.setUTCDate(target.getUTCDate() - dayNr + 3);
7810
+ const isoYear = target.getUTCFullYear();
7811
+ const firstThursday = new Date(Date.UTC(isoYear, 0, 4));
7812
+ const firstDayNr = (firstThursday.getUTCDay() + 6) % 7;
7813
+ firstThursday.setUTCDate(firstThursday.getUTCDate() - firstDayNr + 3);
7814
+ const week = 1 + Math.round((target.getTime() - firstThursday.getTime()) / (7 * 24 * 60 * 60 * 1e3));
7815
+ return { year: isoYear, week };
7816
+ }
7805
7817
  function partsToUtcMs(p) {
7806
7818
  return Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
7807
7819
  }
@@ -7995,6 +8007,15 @@ function CalendarTimeline({
7995
8007
  resources,
7996
8008
  events,
7997
8009
  size = "md",
8010
+ enableEventSheet,
8011
+ eventSheetSize = "md",
8012
+ renderEventSheet,
8013
+ selectedEventId,
8014
+ defaultSelectedEventId,
8015
+ onSelectedEventIdChange,
8016
+ eventSheetOpen,
8017
+ defaultEventSheetOpen,
8018
+ onEventSheetOpenChange,
7998
8019
  view,
7999
8020
  defaultView = "month",
8000
8021
  onViewChange,
@@ -8035,6 +8056,28 @@ function CalendarTimeline({
8035
8056
  const detectedLocale = useLocale();
8036
8057
  const resolvedLocale = React28.useMemo(() => localeToBCP47(locale ?? detectedLocale), [locale, detectedLocale]);
8037
8058
  const resolvedTimeZone = React28.useMemo(() => timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC", [timeZone]);
8059
+ const effectiveEnableEventSheet = enableEventSheet ?? Boolean(renderEventSheet);
8060
+ const isControlledSelectedEventId = selectedEventId !== void 0;
8061
+ const [internalSelectedEventId, setInternalSelectedEventId] = React28.useState(defaultSelectedEventId ?? null);
8062
+ const activeSelectedEventId = isControlledSelectedEventId ? selectedEventId : internalSelectedEventId;
8063
+ const setSelectedEventId = React28.useCallback(
8064
+ (next) => {
8065
+ if (!isControlledSelectedEventId) setInternalSelectedEventId(next);
8066
+ onSelectedEventIdChange?.(next);
8067
+ },
8068
+ [isControlledSelectedEventId, onSelectedEventIdChange]
8069
+ );
8070
+ const isControlledEventSheetOpen = eventSheetOpen !== void 0;
8071
+ const [internalEventSheetOpen, setInternalEventSheetOpen] = React28.useState(defaultEventSheetOpen ?? false);
8072
+ const activeEventSheetOpen = isControlledEventSheetOpen ? Boolean(eventSheetOpen) : internalEventSheetOpen;
8073
+ const setEventSheetOpen = React28.useCallback(
8074
+ (next) => {
8075
+ if (!isControlledEventSheetOpen) setInternalEventSheetOpen(next);
8076
+ onEventSheetOpenChange?.(next);
8077
+ if (!next) setSelectedEventId(null);
8078
+ },
8079
+ [isControlledEventSheetOpen, onEventSheetOpenChange, setSelectedEventId]
8080
+ );
8038
8081
  const sizeConfig = React28.useMemo(() => {
8039
8082
  const cfgBySize = {
8040
8083
  sm: {
@@ -8246,13 +8289,59 @@ function CalendarTimeline({
8246
8289
  }
8247
8290
  return map;
8248
8291
  }, [normalizedEvents]);
8292
+ const resourceById = React28.useMemo(() => {
8293
+ const map = /* @__PURE__ */ new Map();
8294
+ for (const r of resources) map.set(r.id, r);
8295
+ return map;
8296
+ }, [resources]);
8297
+ const selectedEvent = React28.useMemo(() => {
8298
+ if (!activeSelectedEventId) return null;
8299
+ const found = normalizedEvents.find((e) => e.id === activeSelectedEventId);
8300
+ return found ?? null;
8301
+ }, [activeSelectedEventId, normalizedEvents]);
8302
+ const selectedResource = React28.useMemo(() => {
8303
+ if (!selectedEvent) return void 0;
8304
+ return resourceById.get(selectedEvent.resourceId);
8305
+ }, [resourceById, selectedEvent]);
8306
+ const selectedTimeText = React28.useMemo(() => {
8307
+ if (!selectedEvent) return "";
8308
+ return formatters?.eventTime?.({
8309
+ start: selectedEvent._start,
8310
+ end: selectedEvent._end,
8311
+ locale: resolvedLocale,
8312
+ timeZone: resolvedTimeZone,
8313
+ view: activeView
8314
+ }) ?? defaultEventTime({ start: selectedEvent._start, end: selectedEvent._end, locale: resolvedLocale, timeZone: resolvedTimeZone, view: activeView });
8315
+ }, [activeView, formatters, resolvedLocale, resolvedTimeZone, selectedEvent]);
8316
+ React28.useEffect(() => {
8317
+ if (!effectiveEnableEventSheet) return;
8318
+ if (activeEventSheetOpen && activeSelectedEventId && !selectedEvent) {
8319
+ setEventSheetOpen(false);
8320
+ }
8321
+ }, [activeEventSheetOpen, activeSelectedEventId, effectiveEnableEventSheet, selectedEvent, setEventSheetOpen]);
8249
8322
  const leftRef = React28.useRef(null);
8250
8323
  const bodyRef = React28.useRef(null);
8251
8324
  const headerRef = React28.useRef(null);
8252
8325
  useHorizontalScrollSync({ bodyRef, headerRef, leftRef });
8253
8326
  const title = React28.useMemo(() => {
8254
- return formatters?.monthTitle?.(activeDate, { locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultMonthTitle(activeDate, resolvedLocale, resolvedTimeZone);
8255
- }, [activeDate, formatters, resolvedLocale, resolvedTimeZone]);
8327
+ if (activeView === "month") {
8328
+ return formatters?.monthTitle?.(activeDate, { locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultMonthTitle(activeDate, resolvedLocale, resolvedTimeZone);
8329
+ }
8330
+ if (activeView === "week") {
8331
+ const { week } = getIsoWeekInfo(range.start, resolvedTimeZone);
8332
+ const fmt2 = getDtf(resolvedLocale, resolvedTimeZone, { month: "short", day: "numeric" });
8333
+ const fmtYear = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
8334
+ const endInclusive = new Date(range.end.getTime() - 1);
8335
+ const a = fmt2.format(range.start);
8336
+ const b = fmt2.format(endInclusive);
8337
+ const ya = fmtYear.format(range.start);
8338
+ const yb = fmtYear.format(endInclusive);
8339
+ const rangeText = ya === yb ? `${a} \u2013 ${b}, ${ya}` : `${a}, ${ya} \u2013 ${b}, ${yb}`;
8340
+ return `${l.week} ${week} \u2022 ${rangeText}`;
8341
+ }
8342
+ const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
8343
+ return fmt.format(range.start);
8344
+ }, [activeDate, activeView, formatters, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone]);
8256
8345
  const densityClass = sizeConfig.densityClass;
8257
8346
  const eventHeight = sizeConfig.eventHeight;
8258
8347
  const laneGap = sizeConfig.laneGap;
@@ -8278,10 +8367,9 @@ function CalendarTimeline({
8278
8367
  const body = bodyRef.current;
8279
8368
  if (!body) return null;
8280
8369
  const el = document.elementFromPoint(clientX, clientY);
8281
- const timelineEl = el?.closest?.("[data-uv-ct-timeline]");
8282
- if (!timelineEl) return null;
8283
- const timelineRect = timelineEl.getBoundingClientRect();
8284
- const x = clientX - timelineRect.left + body.scrollLeft;
8370
+ if (!el || !body.contains(el)) return null;
8371
+ const bodyRect = body.getBoundingClientRect();
8372
+ const x = clientX - bodyRect.left + body.scrollLeft;
8285
8373
  const slotIdx = clamp3(Math.floor(x / slotWidth), 0, Math.max(0, slots.length - 1));
8286
8374
  const rowEl = el?.closest?.("[data-uv-ct-row]");
8287
8375
  const rid = rowEl?.dataset?.uvCtRow ?? null;
@@ -8669,7 +8757,14 @@ function CalendarTimeline({
8669
8757
  ev.title ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "font-semibold text-[11px] truncate leading-tight", children: ev.title }) : null,
8670
8758
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-[10px] opacity-70 truncate ml-auto", children: timeText })
8671
8759
  ] });
8672
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
8760
+ const resource = resourceById.get(ev.resourceId);
8761
+ const tooltipTitle = ev.title || ev.id;
8762
+ const tooltipContent = /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-col gap-0.5", children: [
8763
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "font-semibold", children: tooltipTitle }),
8764
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "text-xs opacity-80", children: timeText }),
8765
+ resource?.label ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "text-xs opacity-70", children: resource.label }) : null
8766
+ ] });
8767
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Tooltip, { content: tooltipContent, placement: "top", delay: { open: 250, close: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
8673
8768
  "div",
8674
8769
  {
8675
8770
  className: cn(
@@ -8692,7 +8787,13 @@ function CalendarTimeline({
8692
8787
  role: "button",
8693
8788
  tabIndex: 0,
8694
8789
  "aria-label": aria,
8695
- onClick: () => onEventClick?.(ev),
8790
+ onClick: () => {
8791
+ onEventClick?.(ev);
8792
+ if (effectiveEnableEventSheet) {
8793
+ setSelectedEventId(ev.id);
8794
+ setEventSheetOpen(true);
8795
+ }
8796
+ },
8696
8797
  onDoubleClick: () => onEventDoubleClick?.(ev),
8697
8798
  onPointerDown: (e) => onPointerDownEvent(e, ev, "move"),
8698
8799
  children: [
@@ -8714,9 +8815,8 @@ function CalendarTimeline({
8714
8815
  ] }) : null,
8715
8816
  node
8716
8817
  ]
8717
- },
8718
- ev.id
8719
- );
8818
+ }
8819
+ ) }, ev.id);
8720
8820
  }),
8721
8821
  preview && preview.resourceId === r.id && !preview.eventId ? (() => {
8722
8822
  const startIdx = binarySearchLastLE(slotStarts, preview.start);
@@ -8759,7 +8859,35 @@ function CalendarTimeline({
8759
8859
  ]
8760
8860
  }
8761
8861
  )
8762
- ] })
8862
+ ] }),
8863
+ effectiveEnableEventSheet && selectedEvent ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
8864
+ Sheet,
8865
+ {
8866
+ open: activeEventSheetOpen,
8867
+ onOpenChange: setEventSheetOpen,
8868
+ side: "right",
8869
+ size: eventSheetSize,
8870
+ title: selectedEvent.title ?? "Event",
8871
+ description: selectedTimeText || void 0,
8872
+ children: renderEventSheet ? renderEventSheet({
8873
+ event: selectedEvent,
8874
+ resource: selectedResource,
8875
+ close: () => setEventSheetOpen(false),
8876
+ locale: resolvedLocale,
8877
+ timeZone: resolvedTimeZone,
8878
+ view: activeView
8879
+ }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "space-y-3", children: [
8880
+ selectedResource?.label ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { children: [
8881
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "text-xs text-muted-foreground", children: t("resourcesHeader") }),
8882
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "font-medium", children: selectedResource.label })
8883
+ ] }) : null,
8884
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { children: [
8885
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "text-xs text-muted-foreground", children: "ID" }),
8886
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "font-mono text-xs break-all", children: selectedEvent.id })
8887
+ ] })
8888
+ ] })
8889
+ }
8890
+ ) : null
8763
8891
  ]
8764
8892
  }
8765
8893
  );