@bsol-oss/react-datatable5 13.0.1-beta.31 → 13.0.1-beta.33

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.js CHANGED
@@ -7695,7 +7695,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7695
7695
  }
7696
7696
  }
7697
7697
  };
7698
- return (jsxRuntime.jsxs(react.HStack, { justifyContent: 'start', alignItems: 'center', gap: 2, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: false, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", zIndex: 1500, children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(DatePickerContext.Provider, { value: { labels: calendarLabels }, children: jsxRuntime.jsx(Calendar, { ...calendarProps, firstDayOfWeek: 0 }) }) }) }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD", ref: dateInitialFocusEl }) }), jsxRuntime.jsxs(react.Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), initialFocusEl: () => timeInitialFocusEl.current, closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Input, { value: timeDisplayValue, onChange: handleTimeInputChange, onKeyDown: handleTimeInputKeyDown, placeholder: timePickerLabels?.placeholder ??
7698
+ return (jsxRuntime.jsxs(react.HStack, { justifyContent: 'start', alignItems: 'center', gap: 2, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: false, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Portal, { disabled: portalled, children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", zIndex: 1500, children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(DatePickerContext.Provider, { value: { labels: calendarLabels }, children: jsxRuntime.jsx(Calendar, { ...calendarProps, firstDayOfWeek: 0 }) }) }) }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD", ref: dateInitialFocusEl }) }), jsxRuntime.jsxs(react.Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), initialFocusEl: () => timeInitialFocusEl.current, closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Input, { value: timeDisplayValue, onChange: handleTimeInputChange, onKeyDown: handleTimeInputKeyDown, placeholder: timePickerLabels?.placeholder ??
7699
7699
  (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), ref: timeInitialFocusEl }) }) }), jsxRuntime.jsx(react.Portal, { disabled: portalled, children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { p: 0, minH: "200px", maxH: "70vh", width: "fit-content", children: collection.items.length === 0 ? (jsxRuntime.jsx(react.Text, { px: 3, py: 2, color: "gray.500", fontSize: "sm", children: timePickerLabels?.emptyMessage ?? 'No time found' })) : (collection.items.map((item) => {
7700
7700
  const option = item;
7701
7701
  const isSelected = option.value === currentTimeValue;
@@ -7987,6 +7987,591 @@ const getMultiDates = ({ selected, selectedDate, selectedDates, selectable, }) =
7987
7987
  }
7988
7988
  };
7989
7989
 
7990
+ const VIEWPORT_TRANSITION_EASING = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)';
7991
+ const VIEWPORT_TRANSITION_DURATION_MS = 220;
7992
+ const VIEWPORT_TRANSITION = `transform ${VIEWPORT_TRANSITION_DURATION_MS}ms ${VIEWPORT_TRANSITION_EASING}, opacity ${VIEWPORT_TRANSITION_DURATION_MS}ms ${VIEWPORT_TRANSITION_EASING}`;
7993
+ const TimeViewportContext = React.createContext(null);
7994
+ const useResolvedViewport = (viewportStart, viewportEnd) => {
7995
+ const context = React.useContext(TimeViewportContext);
7996
+ const resolvedStart = viewportStart ?? context?.viewportStart;
7997
+ const resolvedEnd = viewportEnd ?? context?.viewportEnd;
7998
+ if (resolvedStart === undefined || resolvedEnd === undefined)
7999
+ return null;
8000
+ return {
8001
+ viewportStart: resolvedStart,
8002
+ viewportEnd: resolvedEnd,
8003
+ };
8004
+ };
8005
+ function TimeViewportRoot({ viewportStart, viewportEnd, children, onViewportChange, enableDragPan = false, enableCtrlWheelZoom = false, wheelZoomFactor = 1.2, minDurationMs = 60 * 1000, maxDurationMs = 365 * 24 * 60 * 60 * 1000, }) {
8006
+ const containerRef = React.useRef(null);
8007
+ const [isDragging, setIsDragging] = React.useState(false);
8008
+ const dragRef = React.useRef(null);
8009
+ const parseViewport = React.useCallback(() => {
8010
+ const start = parseTimeInput(viewportStart);
8011
+ const end = parseTimeInput(viewportEnd);
8012
+ if (!start || !end || !end.isAfter(start))
8013
+ return null;
8014
+ return {
8015
+ startMs: start.valueOf(),
8016
+ endMs: end.valueOf(),
8017
+ };
8018
+ }, [viewportEnd, viewportStart]);
8019
+ const handlePointerDown = (e) => {
8020
+ if (!enableDragPan || !onViewportChange)
8021
+ return;
8022
+ if (e.button !== 0)
8023
+ return;
8024
+ const parsed = parseViewport();
8025
+ if (!parsed)
8026
+ return;
8027
+ dragRef.current = {
8028
+ pointerId: e.pointerId,
8029
+ startX: e.clientX,
8030
+ viewportStartMs: parsed.startMs,
8031
+ viewportEndMs: parsed.endMs,
8032
+ };
8033
+ setIsDragging(true);
8034
+ e.currentTarget.setPointerCapture(e.pointerId);
8035
+ };
8036
+ const handlePointerMove = (e) => {
8037
+ if (!enableDragPan || !onViewportChange)
8038
+ return;
8039
+ const dragging = dragRef.current;
8040
+ if (!dragging || dragging.pointerId !== e.pointerId)
8041
+ return;
8042
+ const width = containerRef.current?.clientWidth ?? 0;
8043
+ if (width <= 0)
8044
+ return;
8045
+ const deltaX = e.clientX - dragging.startX;
8046
+ const durationMs = dragging.viewportEndMs - dragging.viewportStartMs;
8047
+ const shiftMs = (-deltaX / width) * durationMs;
8048
+ onViewportChange({
8049
+ start: dayjs(dragging.viewportStartMs + shiftMs).toDate(),
8050
+ end: dayjs(dragging.viewportEndMs + shiftMs).toDate(),
8051
+ });
8052
+ };
8053
+ const stopDragging = (e) => {
8054
+ const dragging = dragRef.current;
8055
+ if (!dragging || dragging.pointerId !== e.pointerId)
8056
+ return;
8057
+ dragRef.current = null;
8058
+ setIsDragging(false);
8059
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
8060
+ e.currentTarget.releasePointerCapture(e.pointerId);
8061
+ }
8062
+ };
8063
+ const handleWheel = (e) => {
8064
+ if (!e.ctrlKey)
8065
+ return;
8066
+ // Prevent browser-level Ctrl/Cmd + wheel page zoom while interacting
8067
+ // with the timeline surface.
8068
+ e.preventDefault();
8069
+ if (!enableCtrlWheelZoom || !onViewportChange)
8070
+ return;
8071
+ const parsed = parseViewport();
8072
+ if (!parsed)
8073
+ return;
8074
+ const width = containerRef.current?.clientWidth ?? 0;
8075
+ if (width <= 0)
8076
+ return;
8077
+ const safeFactor = wheelZoomFactor > 1 ? wheelZoomFactor : 1.2;
8078
+ const durationMs = parsed.endMs - parsed.startMs;
8079
+ // Exponential zoom curve: each wheel "step" compounds by safeFactor.
8080
+ // This keeps zooming smooth on trackpads and predictable on mouse wheels.
8081
+ const wheelStep = e.deltaY / 100;
8082
+ const zoomMultiplier = Math.pow(safeFactor, wheelStep);
8083
+ const nextDuration = clampNumber(durationMs * (Number.isFinite(zoomMultiplier) ? zoomMultiplier : 1), minDurationMs, maxDurationMs);
8084
+ const rect = containerRef.current?.getBoundingClientRect();
8085
+ const x = rect ? e.clientX - rect.left : width / 2;
8086
+ const ratio = clampNumber(x / width, 0, 1);
8087
+ const anchorMs = parsed.startMs + durationMs * ratio;
8088
+ const nextStartMs = anchorMs - nextDuration * ratio;
8089
+ const nextEndMs = anchorMs + nextDuration * (1 - ratio);
8090
+ onViewportChange({
8091
+ start: dayjs(nextStartMs).toDate(),
8092
+ end: dayjs(nextEndMs).toDate(),
8093
+ });
8094
+ };
8095
+ return (jsxRuntime.jsx(TimeViewportContext.Provider, { value: { viewportStart, viewportEnd }, children: jsxRuntime.jsx(react.Box, { ref: containerRef, position: "relative", width: "100%", cursor: enableDragPan ? (isDragging ? 'grabbing' : 'grab') : 'default', userSelect: enableDragPan ? 'none' : undefined, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: stopDragging, onPointerCancel: stopDragging, onWheel: handleWheel, children: children }) }));
8096
+ }
8097
+ function TimeViewportTrackRow({ trackKey, blocks, resolvedHeight, prefix, suffix, renderBlockNode, }) {
8098
+ return (jsxRuntime.jsxs(react.HStack, { align: "stretch", gap: 2, children: [prefix ? (jsxRuntime.jsx(react.Box, { minW: "fit-content", display: "flex", alignItems: "center", children: prefix })) : null, jsxRuntime.jsx(react.Box, { position: "relative", width: "100%", height: resolvedHeight, children: blocks.map((item, index) => renderBlockNode(item, index)) }), suffix ? (jsxRuntime.jsx(react.Box, { minW: "fit-content", display: "flex", alignItems: "center", children: suffix })) : null] }, trackKey));
8099
+ }
8100
+ const defaultLabels = {
8101
+ zoomIn: 'Zoom in',
8102
+ zoomOut: 'Zoom out',
8103
+ reset: 'Reset',
8104
+ visibleRange: 'Visible range',
8105
+ duration: 'Duration',
8106
+ daysShort: 'd',
8107
+ hoursShort: 'h',
8108
+ minutesShort: 'm',
8109
+ secondsShort: 's',
8110
+ invalidRange: 'Invalid range',
8111
+ dateTimeFormat: 'YYYY-MM-DD HH:mm:ss',
8112
+ };
8113
+ const DEFAULT_MIN_DURATION_MS = 60 * 1000;
8114
+ const DEFAULT_MAX_DURATION_MS = 365 * 24 * 60 * 60 * 1000;
8115
+ const DEFAULT_ZOOM_FACTOR = 2;
8116
+ const clampNumber = (value, min, max) => Math.min(Math.max(value, min), max);
8117
+ const toValidDayjs = (value, fallback) => {
8118
+ const parsed = dayjs(value);
8119
+ return parsed.isValid() ? parsed : fallback;
8120
+ };
8121
+ const parseTimeInput = (value) => {
8122
+ const parsed = dayjs(value);
8123
+ return parsed.isValid() ? parsed : null;
8124
+ };
8125
+ const flattenTrackBlocks = (blocks, inheritedTrack) => {
8126
+ const flattened = [];
8127
+ blocks.forEach((block) => {
8128
+ const resolvedTrack = block.track ?? inheritedTrack;
8129
+ if (block.children && block.children.length > 0) {
8130
+ flattened.push(...flattenTrackBlocks(block.children, resolvedTrack));
8131
+ }
8132
+ if (block.start !== undefined && block.end !== undefined) {
8133
+ flattened.push({
8134
+ ...block,
8135
+ track: resolvedTrack,
8136
+ });
8137
+ }
8138
+ });
8139
+ return flattened;
8140
+ };
8141
+ function useTimeViewport(viewportStart, viewportEnd, format) {
8142
+ const viewport = useResolvedViewport(viewportStart, viewportEnd);
8143
+ const parsedViewport = React.useMemo(() => {
8144
+ if (!viewport)
8145
+ return null;
8146
+ const start = parseTimeInput(viewport.viewportStart);
8147
+ const end = parseTimeInput(viewport.viewportEnd);
8148
+ if (!start || !end || !end.isAfter(start))
8149
+ return null;
8150
+ const viewportStartMs = start.valueOf();
8151
+ const viewportEndMs = end.valueOf();
8152
+ return {
8153
+ start,
8154
+ end,
8155
+ formatString: format ?? getDefaultHeaderFormat(start, end),
8156
+ viewportStartMs,
8157
+ viewportEndMs,
8158
+ viewportDurationMs: viewportEndMs - viewportStartMs,
8159
+ };
8160
+ }, [format, viewport]);
8161
+ const toTimeMs = React.useCallback((value) => {
8162
+ if (value === undefined)
8163
+ return null;
8164
+ const parsed = parseTimeInput(value);
8165
+ return parsed ? parsed.valueOf() : null;
8166
+ }, []);
8167
+ const getGeometry = React.useCallback((start, end) => {
8168
+ if (!parsedViewport || start === undefined || end === undefined) {
8169
+ return { valid: false, leftPercent: 0, widthPercent: 0 };
8170
+ }
8171
+ const blockStartMs = toTimeMs(start);
8172
+ const blockEndMs = toTimeMs(end);
8173
+ if (blockStartMs === null ||
8174
+ blockEndMs === null ||
8175
+ blockEndMs <= blockStartMs) {
8176
+ return { valid: false, leftPercent: 0, widthPercent: 0 };
8177
+ }
8178
+ const visibleStartMs = Math.max(blockStartMs, parsedViewport.viewportStartMs);
8179
+ const visibleEndMs = Math.min(blockEndMs, parsedViewport.viewportEndMs);
8180
+ if (visibleEndMs <= visibleStartMs) {
8181
+ return { valid: true, leftPercent: 0, widthPercent: 0 };
8182
+ }
8183
+ const leftMs = visibleStartMs - parsedViewport.viewportStartMs;
8184
+ const widthMs = visibleEndMs - visibleStartMs;
8185
+ return {
8186
+ valid: true,
8187
+ leftPercent: clampNumber((leftMs / parsedViewport.viewportDurationMs) * 100, 0, 100),
8188
+ widthPercent: clampNumber((widthMs / parsedViewport.viewportDurationMs) * 100, 0, 100),
8189
+ };
8190
+ }, [parsedViewport, toTimeMs]);
8191
+ const getTimestampPercent = React.useCallback((timestamp) => {
8192
+ if (!parsedViewport || timestamp === undefined) {
8193
+ return { valid: false, percent: 0, inView: false };
8194
+ }
8195
+ const timestampMs = toTimeMs(timestamp);
8196
+ if (timestampMs === null)
8197
+ return { valid: false, percent: 0, inView: false };
8198
+ const rawPercent = ((timestampMs - parsedViewport.viewportStartMs) /
8199
+ parsedViewport.viewportDurationMs) *
8200
+ 100;
8201
+ return {
8202
+ valid: true,
8203
+ percent: clampNumber(rawPercent, 0, 100),
8204
+ inView: rawPercent >= 0 && rawPercent <= 100,
8205
+ };
8206
+ }, [parsedViewport, toTimeMs]);
8207
+ const getTicksByCount = React.useCallback((tickCount = 7) => {
8208
+ if (!parsedViewport)
8209
+ return [];
8210
+ const safeTickCount = Math.max(2, tickCount);
8211
+ return Array.from({ length: safeTickCount }, (_, index) => {
8212
+ const ratio = index / (safeTickCount - 1);
8213
+ const tickTime = parsedViewport.start.add(parsedViewport.viewportDurationMs * ratio, 'millisecond');
8214
+ return {
8215
+ index,
8216
+ percent: ratio * 100,
8217
+ label: tickTime.format(parsedViewport.formatString),
8218
+ };
8219
+ });
8220
+ }, [parsedViewport]);
8221
+ const getTicksByTimeUnit = React.useCallback((tickUnit = 'hour', tickStep = 1) => {
8222
+ if (!parsedViewport)
8223
+ return [];
8224
+ const safeTickStep = Math.max(1, Math.floor(tickStep));
8225
+ const candidateTimes = [parsedViewport.start];
8226
+ let cursor = parsedViewport.start.startOf(tickUnit);
8227
+ while (cursor.isBefore(parsedViewport.start)) {
8228
+ cursor = cursor.add(safeTickStep, tickUnit);
8229
+ }
8230
+ while (cursor.isBefore(parsedViewport.end) ||
8231
+ cursor.isSame(parsedViewport.end)) {
8232
+ candidateTimes.push(cursor);
8233
+ cursor = cursor.add(safeTickStep, tickUnit);
8234
+ }
8235
+ candidateTimes.push(parsedViewport.end);
8236
+ const uniqueSortedTicks = Array.from(new Map(candidateTimes.map((time) => [time.valueOf(), time])).values()).sort((a, b) => a.valueOf() - b.valueOf());
8237
+ return uniqueSortedTicks.map((tickTime, index) => {
8238
+ const ratio = tickTime.diff(parsedViewport.start, 'millisecond') /
8239
+ parsedViewport.viewportDurationMs;
8240
+ return {
8241
+ index,
8242
+ percent: clampNumber(ratio * 100, 0, 100),
8243
+ label: tickTime.format(parsedViewport.formatString),
8244
+ };
8245
+ });
8246
+ }, [parsedViewport]);
8247
+ const getTicks = React.useCallback((options) => {
8248
+ const strategy = options?.tickStrategy ?? 'count';
8249
+ if (strategy === 'timeUnit') {
8250
+ return getTicksByTimeUnit(options?.tickUnit, options?.tickStep);
8251
+ }
8252
+ return getTicksByCount(options?.tickCount);
8253
+ }, [getTicksByCount, getTicksByTimeUnit]);
8254
+ return {
8255
+ isValidViewport: Boolean(parsedViewport),
8256
+ toTimeMs,
8257
+ getGeometry,
8258
+ getTimestampPercent,
8259
+ getTicksByCount,
8260
+ getTicksByTimeUnit,
8261
+ getTicks,
8262
+ };
8263
+ }
8264
+ function useTimeViewportBlockGeometry(viewportStart, viewportEnd) {
8265
+ const { isValidViewport, getGeometry, toTimeMs } = useTimeViewport(viewportStart, viewportEnd);
8266
+ return {
8267
+ hasValidViewport: isValidViewport,
8268
+ getGeometry,
8269
+ toTimeMs,
8270
+ };
8271
+ }
8272
+ const getDefaultHeaderFormat = (start, end) => {
8273
+ const durationHours = end.diff(start, 'hour', true);
8274
+ if (durationHours <= 24)
8275
+ return 'HH:mm';
8276
+ if (durationHours <= 24 * 7)
8277
+ return 'ddd HH:mm';
8278
+ return 'MMM D';
8279
+ };
8280
+ function useTimeViewportTicks({ viewportStart, viewportEnd, format, }) {
8281
+ const { isValidViewport, getTicksByCount, getTicksByTimeUnit, getTicks } = useTimeViewport(viewportStart, viewportEnd, format);
8282
+ return {
8283
+ isValidViewport,
8284
+ getTicksByCount,
8285
+ getTicksByTimeUnit,
8286
+ getTicks,
8287
+ };
8288
+ }
8289
+ const useTimeViewportHeader = useTimeViewportTicks;
8290
+ const formatDuration = (durationMs, labels) => {
8291
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
8292
+ const days = Math.floor(totalSeconds / 86400);
8293
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
8294
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
8295
+ const seconds = totalSeconds % 60;
8296
+ const parts = [];
8297
+ if (days > 0)
8298
+ parts.push(`${days}${labels.daysShort}`);
8299
+ if (hours > 0)
8300
+ parts.push(`${hours}${labels.hoursShort}`);
8301
+ if (minutes > 0)
8302
+ parts.push(`${minutes}${labels.minutesShort}`);
8303
+ if (seconds > 0 || parts.length === 0)
8304
+ parts.push(`${seconds}${labels.secondsShort}`);
8305
+ return parts.join(' ');
8306
+ };
8307
+ /**
8308
+ * A resizable timeline block based on block time range and viewport time range.
8309
+ * Width and offset are automatically derived from datetime overlap.
8310
+ */
8311
+ function TimeViewportBlock({ start, end, viewportStart, viewportEnd, height = '28px', minWidthPx = 2, borderRadius = 'sm', colorPalette = 'blue', background, label, showLabel = true, hideWhenOutOfView = true, onClick, }) {
8312
+ const { getGeometry } = useTimeViewportBlockGeometry(viewportStart, viewportEnd);
8313
+ const geometry = React.useMemo(() => {
8314
+ return getGeometry(start, end);
8315
+ }, [end, getGeometry, start]);
8316
+ if (!geometry.valid)
8317
+ return null;
8318
+ if (hideWhenOutOfView && geometry.widthPercent <= 0)
8319
+ return null;
8320
+ return (jsxRuntime.jsx(react.Box, { position: "relative", width: "100%", height: height, children: jsxRuntime.jsx(react.Box, { position: "absolute", inset: 0, pointerEvents: "none", children: jsxRuntime.jsx(react.Box, { width: "100%", height: "100%", transform: `translateX(${geometry.leftPercent}%)`, transition: VIEWPORT_TRANSITION, children: jsxRuntime.jsx(react.Box, { width: `max(${geometry.widthPercent}%, ${minWidthPx}px)`, height: "100%", borderRadius: borderRadius, bg: background ?? `${colorPalette}.500`, _dark: {
8321
+ bg: background ?? `${colorPalette}.900`,
8322
+ }, display: "flex", alignItems: "center", justifyContent: "center", px: 2, overflow: "hidden", pointerEvents: "auto", onClick: onClick, cursor: onClick ? 'pointer' : 'default', children: showLabel && label ? (jsxRuntime.jsx(react.Text, { fontSize: "xs", lineClamp: 1, color: "white", _dark: { color: 'gray.100' }, children: label })) : null }) }) }) }));
8323
+ }
8324
+ /**
8325
+ * Vertical marker line for a timestamp in the current viewport.
8326
+ */
8327
+ function TimeViewportMarkerLine({ timestamp, viewportStart, viewportEnd, height = '100%', colorPalette = 'red', color, lineWidthPx = 2, label, showLabel = true, hideWhenOutOfView = true, }) {
8328
+ const { getTimestampPercent } = useTimeViewport(viewportStart, viewportEnd);
8329
+ const marker = React.useMemo(() => {
8330
+ return getTimestampPercent(timestamp);
8331
+ }, [getTimestampPercent, timestamp]);
8332
+ if (!marker.valid)
8333
+ return null;
8334
+ if (hideWhenOutOfView && !marker.inView)
8335
+ return null;
8336
+ return (jsxRuntime.jsx(react.Box, { position: "absolute", insetInlineStart: 0, insetInlineEnd: 0, top: 0, bottom: 0, pointerEvents: "none", zIndex: 100, height: height, children: jsxRuntime.jsxs(react.Box, { width: "100%", height: "100%", transform: `translateX(${marker.percent}%)`, transition: VIEWPORT_TRANSITION, transformOrigin: "left center", children: [jsxRuntime.jsx(react.Box, { width: `${lineWidthPx}px`, height: "100%", bg: color ?? `${colorPalette}.500`, _dark: { bg: color ?? `${colorPalette}.300` }, transform: "translateX(-50%)" }), showLabel && label ? (jsxRuntime.jsx(react.Text, { position: "absolute", insetInlineStart: 0, top: "100%", mt: 1, display: "inline-block", fontSize: "xs", whiteSpace: "nowrap", color: color ?? `${colorPalette}.700`, _dark: { color: color ?? `${colorPalette}.200` }, transform: "translateX(-50%)", children: label })) : null] }) }));
8337
+ }
8338
+ /**
8339
+ * Header labels for timeline viewport time scale.
8340
+ */
8341
+ function TimeViewportHeader({ viewportStart, viewportEnd, tickCount = 7, tickStrategy = 'count', tickUnit = 'hour', tickStep = 1, format, height = '28px', color = 'gray.600', borderColor = 'gray.200', showBottomBorder = true, animationDurationMs = VIEWPORT_TRANSITION_DURATION_MS, animationEasing = VIEWPORT_TRANSITION_EASING, }) {
8342
+ const { isValidViewport, getTicks } = useTimeViewport(viewportStart, viewportEnd, format);
8343
+ const ticks = getTicks({ tickStrategy, tickCount, tickUnit, tickStep });
8344
+ const safeTickCount = ticks.length;
8345
+ const transitionValue = animationDurationMs > 0
8346
+ ? `transform ${animationDurationMs}ms ${animationEasing}, opacity ${animationDurationMs}ms ${animationEasing}`
8347
+ : undefined;
8348
+ if (!isValidViewport || safeTickCount < 2)
8349
+ return null;
8350
+ return (jsxRuntime.jsx(react.Box, { position: "relative", width: "100%", height: height, borderBottomWidth: showBottomBorder ? '1px' : '0px', borderColor: borderColor, _dark: { borderColor: 'gray.700' }, mb: 2, children: ticks.map((tick) => (jsxRuntime.jsx(react.Box, { position: "absolute", inset: 0, transform: `translateX(${tick.percent}%)`, transition: transitionValue, children: jsxRuntime.jsx(react.Text, { position: "absolute", insetInlineStart: 0, top: "50%", translate: "0 -50%", transform: tick.index === 0
8351
+ ? 'translateX(0%)'
8352
+ : tick.index === safeTickCount - 1
8353
+ ? 'translateX(-100%)'
8354
+ : 'translateX(-50%)', fontSize: "xs", color: color, _dark: { color: 'gray.300' }, whiteSpace: "nowrap", children: tick.label }) }, `tick-wrap-${tick.index}`))) }));
8355
+ }
8356
+ /**
8357
+ * Vertical grid lines for measuring block positions in the viewport.
8358
+ * Render inside a relative container that also contains blocks.
8359
+ */
8360
+ function TimeViewportGrid({ viewportStart, viewportEnd, tickCount = 8, minorDivisions = 2, majorLineColor = 'gray.300', minorLineColor = 'gray.200', showMinorLines = true, zIndex = 0, animationDurationMs = VIEWPORT_TRANSITION_DURATION_MS, animationEasing = VIEWPORT_TRANSITION_EASING, }) {
8361
+ const viewport = useResolvedViewport(viewportStart, viewportEnd);
8362
+ const start = viewport ? parseTimeInput(viewport.viewportStart) : null;
8363
+ const end = viewport ? parseTimeInput(viewport.viewportEnd) : null;
8364
+ if (!start || !end || !end.isAfter(start))
8365
+ return null;
8366
+ const safeTickCount = Math.max(2, tickCount);
8367
+ const majorTicks = Array.from({ length: safeTickCount }, (_, index) => ({
8368
+ index,
8369
+ percent: (index / (safeTickCount - 1)) * 100,
8370
+ }));
8371
+ const safeMinorDivisions = Math.max(1, minorDivisions);
8372
+ const transitionValue = animationDurationMs > 0
8373
+ ? `transform ${animationDurationMs}ms ${animationEasing}, opacity ${animationDurationMs}ms ${animationEasing}`
8374
+ : undefined;
8375
+ const minorTicks = showMinorLines
8376
+ ? Array.from({ length: safeTickCount - 1 }, (_, segmentIndex) => {
8377
+ const base = (segmentIndex / (safeTickCount - 1)) * 100;
8378
+ const next = ((segmentIndex + 1) / (safeTickCount - 1)) * 100;
8379
+ const segment = [];
8380
+ for (let step = 1; step < safeMinorDivisions; step += 1) {
8381
+ segment.push(base + ((next - base) * step) / safeMinorDivisions);
8382
+ }
8383
+ return segment;
8384
+ }).flat()
8385
+ : [];
8386
+ return (jsxRuntime.jsxs(react.Box, { position: "absolute", inset: 0, pointerEvents: "none", zIndex: zIndex, children: [minorTicks.map((percent, index) => (jsxRuntime.jsx(react.Box, { position: "absolute", inset: 0, transform: `translateX(${percent}%)`, transition: transitionValue, children: jsxRuntime.jsx(react.Box, { position: "absolute", insetInlineStart: 0, top: 0, bottom: 0, width: "1px", bg: minorLineColor, _dark: { bg: 'gray.700' } }) }, `minor-grid-${index}`))), majorTicks.map((tick) => (jsxRuntime.jsx(react.Box, { position: "absolute", inset: 0, transform: `translateX(${tick.percent}%)`, transition: transitionValue, children: jsxRuntime.jsx(react.Box, { position: "absolute", insetInlineStart: 0, top: 0, bottom: 0, width: "1px", bg: majorLineColor, _dark: { bg: 'gray.600' } }) }, `major-grid-${tick.index}`)))] }));
8387
+ }
8388
+ function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height = '28px', minWidthPx = 2, borderRadius = 'sm', defaultColorPalette = 'blue', showLabel = true, hideWhenOutOfView = true, hideEmptyTracks = true, gap = 2, allowOverlap = false, overlapOpacity = 0.9, renderTrackPrefix, renderTrackSuffix, onBlockClick, }) {
8389
+ const { getGeometry, toTimeMs } = useTimeViewportBlockGeometry(viewportStart, viewportEnd);
8390
+ const resolvedHeight = typeof height === 'number' ? `${height}px` : height;
8391
+ const expandedBlocks = flattenTrackBlocks(blocks);
8392
+ const parsedBlocks = expandedBlocks
8393
+ .map((block, index) => {
8394
+ if (block.start === undefined || block.end === undefined) {
8395
+ return {
8396
+ block,
8397
+ index,
8398
+ geometry: { valid: false, leftPercent: 0, widthPercent: 0 },
8399
+ startMs: Number.NaN,
8400
+ endMs: Number.NaN,
8401
+ };
8402
+ }
8403
+ const geometry = getGeometry(block.start, block.end);
8404
+ const startMs = toTimeMs(block.start);
8405
+ const endMs = toTimeMs(block.end);
8406
+ return {
8407
+ block,
8408
+ index,
8409
+ geometry,
8410
+ startMs: startMs ?? Number.NaN,
8411
+ endMs: endMs ?? Number.NaN,
8412
+ };
8413
+ })
8414
+ .filter(({ geometry }) => geometry.valid)
8415
+ .filter(({ geometry }) => !hideWhenOutOfView || geometry.widthPercent > 0);
8416
+ const renderBlockNode = (blockItem, indexInLayer) => {
8417
+ const { block, geometry } = blockItem;
8418
+ const handleBlockClick = () => {
8419
+ block.onClick?.(block);
8420
+ onBlockClick?.(block);
8421
+ };
8422
+ const isBlockClickable = Boolean(block.onClick || onBlockClick);
8423
+ return (jsxRuntime.jsx(react.Box, { position: "absolute", inset: 0, pointerEvents: "none", children: jsxRuntime.jsx(react.Box, { width: "100%", height: "100%", transform: `translateX(${geometry.leftPercent}%)`, transition: VIEWPORT_TRANSITION, children: jsxRuntime.jsx(react.Box, { width: `max(${geometry.widthPercent}%, ${minWidthPx}px)`, height: "100%", borderRadius: borderRadius, bg: block.background ??
8424
+ `${block.colorPalette ?? defaultColorPalette}.500`, _dark: {
8425
+ bg: block.background ??
8426
+ `${block.colorPalette ?? defaultColorPalette}.900`,
8427
+ }, display: "flex", alignItems: "center", justifyContent: "center", px: 2, overflow: "hidden", opacity: allowOverlap ? overlapOpacity : 1, zIndex: indexInLayer + 1, pointerEvents: "auto", onClick: isBlockClickable ? handleBlockClick : undefined, cursor: isBlockClickable ? 'pointer' : 'default', children: showLabel && block.label ? (jsxRuntime.jsx(react.Text, { fontSize: "xs", lineClamp: 1, color: "white", _dark: { color: 'gray.100' }, children: block.label })) : null }) }) }, block.id));
8428
+ };
8429
+ const explicitTrackKeys = Array.from(new Set(expandedBlocks
8430
+ .map((item) => item.track)
8431
+ .filter((track) => track !== undefined)));
8432
+ if (explicitTrackKeys.length > 0) {
8433
+ const tracks = explicitTrackKeys
8434
+ .map((trackKey) => ({
8435
+ trackKey,
8436
+ blocks: parsedBlocks.filter((item) => item.block.track === trackKey),
8437
+ }))
8438
+ .filter((track) => !hideEmptyTracks || track.blocks.length > 0);
8439
+ return (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: gap, children: tracks.map((track, trackIndex) => {
8440
+ const trackBlocks = track.blocks.map((item) => item.block);
8441
+ const prefix = renderTrackPrefix?.({
8442
+ trackIndex,
8443
+ trackBlocks,
8444
+ trackKey: track.trackKey,
8445
+ });
8446
+ const suffix = renderTrackSuffix?.({
8447
+ trackIndex,
8448
+ trackBlocks,
8449
+ trackKey: track.trackKey,
8450
+ });
8451
+ return (jsxRuntime.jsx(TimeViewportTrackRow, { trackKey: `track-keyed-${String(track.trackKey)}`, blocks: track.blocks, resolvedHeight: resolvedHeight, prefix: prefix, suffix: suffix, renderBlockNode: renderBlockNode }));
8452
+ }) }));
8453
+ }
8454
+ const autoPackedTracks = (() => {
8455
+ const sortedBlocks = [...parsedBlocks]
8456
+ .filter(({ startMs, endMs }) => Number.isFinite(startMs) && Number.isFinite(endMs))
8457
+ .sort((a, b) => {
8458
+ if (a.startMs === b.startMs)
8459
+ return a.endMs - b.endMs;
8460
+ return a.startMs - b.startMs;
8461
+ });
8462
+ const trackLastEndTimes = [];
8463
+ const tracks = [];
8464
+ sortedBlocks.forEach((item) => {
8465
+ const trackIndex = trackLastEndTimes.findIndex((endMs) => item.startMs >= endMs);
8466
+ if (trackIndex === -1) {
8467
+ trackLastEndTimes.push(item.endMs);
8468
+ tracks.push([item]);
8469
+ }
8470
+ else {
8471
+ trackLastEndTimes[trackIndex] = item.endMs;
8472
+ tracks[trackIndex].push(item);
8473
+ }
8474
+ });
8475
+ return tracks;
8476
+ })();
8477
+ const tracks = allowOverlap ? [parsedBlocks] : autoPackedTracks;
8478
+ return (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: gap, children: tracks.map((track, trackIndex) => {
8479
+ const trackBlocks = track.map((item) => item.block);
8480
+ const prefix = renderTrackPrefix?.({
8481
+ trackIndex,
8482
+ trackBlocks,
8483
+ trackKey: undefined,
8484
+ });
8485
+ const suffix = renderTrackSuffix?.({
8486
+ trackIndex,
8487
+ trackBlocks,
8488
+ trackKey: undefined,
8489
+ });
8490
+ return (jsxRuntime.jsx(TimeViewportTrackRow, { trackKey: `track-row-${trackIndex}`, blocks: track, resolvedHeight: resolvedHeight, prefix: prefix, suffix: suffix, renderBlockNode: renderBlockNode }));
8491
+ }) }));
8492
+ }
8493
+ function TimeRangeZoom({ range, onRangeChange, minDurationMs = DEFAULT_MIN_DURATION_MS, maxDurationMs = DEFAULT_MAX_DURATION_MS, zoomFactor = DEFAULT_ZOOM_FACTOR, resetDurationMs, showResetButton = true, disabled = false, labels, }) {
8494
+ const { labels: mergedLabels, canZoomIn, canZoomOut, visibleRangeText, durationText, zoomIn, zoomOut, reset, } = useTimeRangeZoom({
8495
+ range,
8496
+ onRangeChange,
8497
+ minDurationMs,
8498
+ maxDurationMs,
8499
+ zoomFactor,
8500
+ resetDurationMs,
8501
+ disabled,
8502
+ labels,
8503
+ });
8504
+ return (jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, p: 3, children: [jsxRuntime.jsxs(react.HStack, { justify: "space-between", gap: 2, children: [jsxRuntime.jsxs(react.HStack, { gap: 2, children: [jsxRuntime.jsx(react.IconButton, { "aria-label": mergedLabels.zoomOut, onClick: zoomOut, disabled: !canZoomOut, size: "sm", variant: "outline", children: jsxRuntime.jsx(lu.LuZoomOut, {}) }), jsxRuntime.jsx(react.IconButton, { "aria-label": mergedLabels.zoomIn, onClick: zoomIn, disabled: !canZoomIn, size: "sm", variant: "outline", children: jsxRuntime.jsx(lu.LuZoomIn, {}) })] }), showResetButton ? (jsxRuntime.jsx(react.Button, { size: "sm", variant: "ghost", onClick: reset, disabled: disabled, colorPalette: "blue", children: mergedLabels.reset })) : null] }), jsxRuntime.jsxs(react.Text, { fontSize: "sm", color: "gray.700", _dark: { color: 'gray.200' }, children: [mergedLabels.visibleRange, ": ", visibleRangeText] }), jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "gray.600", _dark: { color: 'gray.400' }, children: [mergedLabels.duration, ": ", durationText] })] }));
8505
+ }
8506
+ function useTimeRangeZoom({ range, onRangeChange, minDurationMs = DEFAULT_MIN_DURATION_MS, maxDurationMs = DEFAULT_MAX_DURATION_MS, zoomFactor = DEFAULT_ZOOM_FACTOR, resetDurationMs, disabled = false, labels, }) {
8507
+ const mergedLabels = React.useMemo(() => ({
8508
+ ...defaultLabels,
8509
+ ...(labels ?? {}),
8510
+ }), [labels]);
8511
+ const now = dayjs();
8512
+ const start = toValidDayjs(range.start, now.subtract(1, 'hour'));
8513
+ const end = toValidDayjs(range.end, now);
8514
+ const safeMinDurationMs = Math.max(1000, minDurationMs);
8515
+ const safeMaxDurationMs = Math.max(safeMinDurationMs, maxDurationMs);
8516
+ const safeZoomFactor = zoomFactor > 1 ? zoomFactor : DEFAULT_ZOOM_FACTOR;
8517
+ const durationMs = React.useMemo(() => {
8518
+ const diff = end.diff(start, 'millisecond');
8519
+ return clampNumber(diff > 0 ? diff : safeMinDurationMs, safeMinDurationMs, safeMaxDurationMs);
8520
+ }, [end, start, safeMaxDurationMs, safeMinDurationMs]);
8521
+ const initialDurationRef = React.useRef(clampNumber(resetDurationMs ?? durationMs, safeMinDurationMs, safeMaxDurationMs));
8522
+ const hasValidDisplayRange = end.isAfter(start);
8523
+ const applyDurationAroundCenter = React.useCallback((nextDurationMs) => {
8524
+ const centerMs = start.valueOf() + durationMs / 2;
8525
+ const half = nextDurationMs / 2;
8526
+ const nextStart = dayjs(centerMs - half).toDate();
8527
+ const nextEnd = dayjs(centerMs + half).toDate();
8528
+ onRangeChange({ start: nextStart, end: nextEnd });
8529
+ }, [durationMs, onRangeChange, start]);
8530
+ const zoomIn = React.useCallback(() => {
8531
+ const nextDuration = clampNumber(durationMs / safeZoomFactor, safeMinDurationMs, safeMaxDurationMs);
8532
+ applyDurationAroundCenter(nextDuration);
8533
+ }, [
8534
+ applyDurationAroundCenter,
8535
+ durationMs,
8536
+ safeMaxDurationMs,
8537
+ safeMinDurationMs,
8538
+ safeZoomFactor,
8539
+ ]);
8540
+ const zoomOut = React.useCallback(() => {
8541
+ const nextDuration = clampNumber(durationMs * safeZoomFactor, safeMinDurationMs, safeMaxDurationMs);
8542
+ applyDurationAroundCenter(nextDuration);
8543
+ }, [
8544
+ applyDurationAroundCenter,
8545
+ durationMs,
8546
+ safeMaxDurationMs,
8547
+ safeMinDurationMs,
8548
+ safeZoomFactor,
8549
+ ]);
8550
+ const reset = React.useCallback(() => {
8551
+ applyDurationAroundCenter(initialDurationRef.current);
8552
+ }, [applyDurationAroundCenter]);
8553
+ const canZoomIn = !disabled && durationMs > safeMinDurationMs;
8554
+ const canZoomOut = !disabled && durationMs < safeMaxDurationMs;
8555
+ const visibleRangeText = hasValidDisplayRange
8556
+ ? `${start.format(mergedLabels.dateTimeFormat)} - ${end.format(mergedLabels.dateTimeFormat)}`
8557
+ : mergedLabels.invalidRange;
8558
+ const durationText = formatDuration(durationMs, mergedLabels);
8559
+ return {
8560
+ labels: mergedLabels,
8561
+ start,
8562
+ end,
8563
+ durationMs,
8564
+ canZoomIn,
8565
+ canZoomOut,
8566
+ hasValidDisplayRange,
8567
+ visibleRangeText,
8568
+ durationText,
8569
+ zoomIn,
8570
+ zoomOut,
8571
+ reset,
8572
+ };
8573
+ }
8574
+
7990
8575
  const snakeToLabel = (str) => {
7991
8576
  return str
7992
8577
  .split("_") // Split by underscore
@@ -8936,6 +9521,13 @@ exports.TableSelector = TableSelector;
8936
9521
  exports.TableSorter = TableSorter;
8937
9522
  exports.TableViewer = TableViewer;
8938
9523
  exports.TextCell = TextCell;
9524
+ exports.TimeRangeZoom = TimeRangeZoom;
9525
+ exports.TimeViewportBlock = TimeViewportBlock;
9526
+ exports.TimeViewportBlocks = TimeViewportBlocks;
9527
+ exports.TimeViewportGrid = TimeViewportGrid;
9528
+ exports.TimeViewportHeader = TimeViewportHeader;
9529
+ exports.TimeViewportMarkerLine = TimeViewportMarkerLine;
9530
+ exports.TimeViewportRoot = TimeViewportRoot;
8939
9531
  exports.ViewDialog = ViewDialog;
8940
9532
  exports.defaultRenderDisplay = defaultRenderDisplay;
8941
9533
  exports.getMultiDates = getMultiDates;
@@ -8944,3 +9536,8 @@ exports.useDataTable = useDataTable;
8944
9536
  exports.useDataTableContext = useDataTableContext;
8945
9537
  exports.useDataTableServer = useDataTableServer;
8946
9538
  exports.useForm = useForm;
9539
+ exports.useTimeRangeZoom = useTimeRangeZoom;
9540
+ exports.useTimeViewport = useTimeViewport;
9541
+ exports.useTimeViewportBlockGeometry = useTimeViewportBlockGeometry;
9542
+ exports.useTimeViewportHeader = useTimeViewportHeader;
9543
+ exports.useTimeViewportTicks = useTimeViewportTicks;