@bsol-oss/react-datatable5 13.0.1-beta.33 → 13.0.1-beta.34
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.ts +12 -2
- package/dist/index.js +91 -71
- package/dist/index.mjs +91 -71
- package/dist/types/components/DatePicker/TimeRangeZoom.d.ts +12 -2
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -1057,6 +1057,10 @@ interface TimeViewportGridProps {
|
|
|
1057
1057
|
viewportStart?: TimeInput;
|
|
1058
1058
|
viewportEnd?: TimeInput;
|
|
1059
1059
|
tickCount?: number;
|
|
1060
|
+
tickStrategy?: 'count' | 'timeUnit';
|
|
1061
|
+
tickUnit?: 'minute' | 'hour' | 'day';
|
|
1062
|
+
tickStep?: number;
|
|
1063
|
+
format?: string;
|
|
1060
1064
|
minorDivisions?: number;
|
|
1061
1065
|
majorLineColor?: string;
|
|
1062
1066
|
minorLineColor?: string;
|
|
@@ -1090,6 +1094,12 @@ interface TimeViewportBlocksProps {
|
|
|
1090
1094
|
trackKey?: string | number;
|
|
1091
1095
|
}) => ReactNode;
|
|
1092
1096
|
onBlockClick?: (block: TimeViewportBlockItem) => void;
|
|
1097
|
+
/** Enable virtual scrolling for large track lists. */
|
|
1098
|
+
virtualize?: boolean;
|
|
1099
|
+
/** Fixed pixel height of the scroll container when virtualize is true. Defaults to 400. */
|
|
1100
|
+
virtualHeight?: number;
|
|
1101
|
+
/** Number of off-screen rows to render above/below the visible area. Defaults to 5. */
|
|
1102
|
+
overscan?: number;
|
|
1093
1103
|
}
|
|
1094
1104
|
interface TimeViewportRootProps {
|
|
1095
1105
|
viewportStart: TimeInput;
|
|
@@ -1172,8 +1182,8 @@ declare function TimeViewportHeader({ viewportStart, viewportEnd, tickCount, tic
|
|
|
1172
1182
|
* Vertical grid lines for measuring block positions in the viewport.
|
|
1173
1183
|
* Render inside a relative container that also contains blocks.
|
|
1174
1184
|
*/
|
|
1175
|
-
declare function TimeViewportGrid({ viewportStart, viewportEnd, tickCount, minorDivisions, majorLineColor, minorLineColor, showMinorLines, zIndex, animationDurationMs, animationEasing, }: TimeViewportGridProps): react_jsx_runtime.JSX.Element | null;
|
|
1176
|
-
declare function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height, minWidthPx, borderRadius, defaultColorPalette, showLabel, hideWhenOutOfView, hideEmptyTracks, gap, allowOverlap, overlapOpacity, renderTrackPrefix, renderTrackSuffix, onBlockClick, }: TimeViewportBlocksProps): react_jsx_runtime.JSX.Element;
|
|
1185
|
+
declare function TimeViewportGrid({ viewportStart, viewportEnd, tickCount, tickStrategy, tickUnit, tickStep, format, minorDivisions, majorLineColor, minorLineColor, showMinorLines, zIndex, animationDurationMs, animationEasing, }: TimeViewportGridProps): react_jsx_runtime.JSX.Element | null;
|
|
1186
|
+
declare function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height, minWidthPx, borderRadius, defaultColorPalette, showLabel, hideWhenOutOfView, hideEmptyTracks, gap, allowOverlap, overlapOpacity, renderTrackPrefix, renderTrackSuffix, onBlockClick, virtualize, virtualHeight, overscan, }: TimeViewportBlocksProps): react_jsx_runtime.JSX.Element;
|
|
1177
1187
|
declare function TimeRangeZoom({ range, onRangeChange, minDurationMs, maxDurationMs, zoomFactor, resetDurationMs, showResetButton, disabled, labels, }: TimeRangeZoomProps): react_jsx_runtime.JSX.Element;
|
|
1178
1188
|
declare function useTimeRangeZoom({ range, onRangeChange, minDurationMs, maxDurationMs, zoomFactor, resetDurationMs, disabled, labels, }: TimeRangeZoomProps): UseTimeRangeZoomResult;
|
|
1179
1189
|
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var timezone = require('dayjs/plugin/timezone');
|
|
|
32
32
|
var utc = require('dayjs/plugin/utc');
|
|
33
33
|
var ti = require('react-icons/ti');
|
|
34
34
|
var ajv = require('@hookform/resolvers/ajv');
|
|
35
|
+
var reactVirtual = require('@tanstack/react-virtual');
|
|
35
36
|
var matchSorterUtils = require('@tanstack/match-sorter-utils');
|
|
36
37
|
|
|
37
38
|
function _interopNamespaceDefault(e) {
|
|
@@ -8095,7 +8096,7 @@ function TimeViewportRoot({ viewportStart, viewportEnd, children, onViewportChan
|
|
|
8095
8096
|
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
|
}
|
|
8097
8098
|
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
|
+
return (jsxRuntime.jsxs(react.HStack, { width: "100%", overflowX: 'hidden', 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
|
}
|
|
8100
8101
|
const defaultLabels = {
|
|
8101
8102
|
zoomIn: 'Zoom in',
|
|
@@ -8357,35 +8358,55 @@ function TimeViewportHeader({ viewportStart, viewportEnd, tickCount = 7, tickStr
|
|
|
8357
8358
|
* Vertical grid lines for measuring block positions in the viewport.
|
|
8358
8359
|
* Render inside a relative container that also contains blocks.
|
|
8359
8360
|
*/
|
|
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
|
|
8362
|
-
const
|
|
8363
|
-
|
|
8364
|
-
if (!start || !end || !end.isAfter(start))
|
|
8361
|
+
function TimeViewportGrid({ viewportStart, viewportEnd, tickCount = 8, tickStrategy = 'count', tickUnit = 'hour', tickStep = 1, format, minorDivisions = 2, majorLineColor = 'gray.300', minorLineColor = 'gray.200', showMinorLines = true, zIndex = 0, animationDurationMs = VIEWPORT_TRANSITION_DURATION_MS, animationEasing = VIEWPORT_TRANSITION_EASING, }) {
|
|
8362
|
+
const { isValidViewport, getTicks } = useTimeViewport(viewportStart, viewportEnd, format);
|
|
8363
|
+
const majorTicks = getTicks({ tickStrategy, tickCount, tickUnit, tickStep });
|
|
8364
|
+
if (!isValidViewport || majorTicks.length < 2)
|
|
8365
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
8366
|
const safeMinorDivisions = Math.max(1, minorDivisions);
|
|
8372
8367
|
const transitionValue = animationDurationMs > 0
|
|
8373
8368
|
? `transform ${animationDurationMs}ms ${animationEasing}, opacity ${animationDurationMs}ms ${animationEasing}`
|
|
8374
8369
|
: undefined;
|
|
8375
8370
|
const minorTicks = showMinorLines
|
|
8376
|
-
?
|
|
8377
|
-
const base =
|
|
8378
|
-
const next =
|
|
8371
|
+
? majorTicks.slice(0, -1).flatMap((tick, segmentIndex) => {
|
|
8372
|
+
const base = tick.percent;
|
|
8373
|
+
const next = majorTicks[segmentIndex + 1].percent;
|
|
8379
8374
|
const segment = [];
|
|
8380
8375
|
for (let step = 1; step < safeMinorDivisions; step += 1) {
|
|
8381
8376
|
segment.push(base + ((next - base) * step) / safeMinorDivisions);
|
|
8382
8377
|
}
|
|
8383
8378
|
return segment;
|
|
8384
|
-
})
|
|
8379
|
+
})
|
|
8385
8380
|
: [];
|
|
8386
8381
|
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
8382
|
}
|
|
8388
|
-
function
|
|
8383
|
+
function VirtualizedTrackList({ tracks, resolvedHeight, gap, virtualHeight, overscan, renderTrackPrefix, renderTrackSuffix, renderBlockNode, }) {
|
|
8384
|
+
const parentRef = React.useRef(null);
|
|
8385
|
+
const rowHeightPx = parseInt(resolvedHeight, 10) || 28;
|
|
8386
|
+
const gapPx = gap * 4; // Chakra spacing token to px (1 unit = 4px)
|
|
8387
|
+
const virtualizer = reactVirtual.useVirtualizer({
|
|
8388
|
+
count: tracks.length,
|
|
8389
|
+
getScrollElement: () => parentRef.current,
|
|
8390
|
+
estimateSize: () => rowHeightPx + gapPx,
|
|
8391
|
+
overscan,
|
|
8392
|
+
});
|
|
8393
|
+
return (jsxRuntime.jsx(react.Box, { ref: parentRef, overflowY: "auto", height: `${virtualHeight}px`, children: jsxRuntime.jsx(react.Box, { height: `${virtualizer.getTotalSize()}px`, position: "relative", children: virtualizer.getVirtualItems().map((virtualRow) => {
|
|
8394
|
+
const track = tracks[virtualRow.index];
|
|
8395
|
+
const trackBlocks = track.blocks.map((item) => item.block);
|
|
8396
|
+
const prefix = renderTrackPrefix?.({
|
|
8397
|
+
trackIndex: virtualRow.index,
|
|
8398
|
+
trackBlocks,
|
|
8399
|
+
trackKey: track.trackKeyRaw,
|
|
8400
|
+
});
|
|
8401
|
+
const suffix = renderTrackSuffix?.({
|
|
8402
|
+
trackIndex: virtualRow.index,
|
|
8403
|
+
trackBlocks,
|
|
8404
|
+
trackKey: track.trackKeyRaw,
|
|
8405
|
+
});
|
|
8406
|
+
return (jsxRuntime.jsx(react.Box, { position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${virtualRow.start}px)`, children: jsxRuntime.jsx(TimeViewportTrackRow, { trackKey: track.trackKey, blocks: track.blocks, resolvedHeight: resolvedHeight, prefix: prefix, suffix: suffix, renderBlockNode: renderBlockNode }) }, track.trackKey));
|
|
8407
|
+
}) }) }));
|
|
8408
|
+
}
|
|
8409
|
+
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, virtualize = false, virtualHeight = 400, overscan = 5, }) {
|
|
8389
8410
|
const { getGeometry, toTimeMs } = useTimeViewportBlockGeometry(viewportStart, viewportEnd);
|
|
8390
8411
|
const resolvedHeight = typeof height === 'number' ? `${height}px` : height;
|
|
8391
8412
|
const expandedBlocks = flattenTrackBlocks(blocks);
|
|
@@ -8420,74 +8441,73 @@ function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height = '28px
|
|
|
8420
8441
|
onBlockClick?.(block);
|
|
8421
8442
|
};
|
|
8422
8443
|
const isBlockClickable = Boolean(block.onClick || onBlockClick);
|
|
8423
|
-
return (jsxRuntime.jsx(react.Box, { position: "absolute", inset: 0, pointerEvents: "none",
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8444
|
+
return (jsxRuntime.jsx(react.Box, { height: "100%", position: "absolute", inset: 0, pointerEvents: "none", 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 ??
|
|
8445
|
+
`${block.colorPalette ?? defaultColorPalette}.500`, _dark: {
|
|
8446
|
+
bg: block.background ??
|
|
8447
|
+
`${block.colorPalette ?? defaultColorPalette}.900`,
|
|
8448
|
+
}, 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
8449
|
};
|
|
8450
|
+
// ---------- Resolve tracks ----------
|
|
8429
8451
|
const explicitTrackKeys = Array.from(new Set(expandedBlocks
|
|
8430
8452
|
.map((item) => item.track)
|
|
8431
8453
|
.filter((track) => track !== undefined)));
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8454
|
+
const resolvedTracks = React.useMemo(() => {
|
|
8455
|
+
if (explicitTrackKeys.length > 0) {
|
|
8456
|
+
return explicitTrackKeys
|
|
8457
|
+
.map((trackKey) => ({
|
|
8458
|
+
trackKey: `track-keyed-${String(trackKey)}`,
|
|
8459
|
+
trackKeyRaw: trackKey,
|
|
8460
|
+
blocks: parsedBlocks.filter((item) => item.block.track === trackKey),
|
|
8461
|
+
}))
|
|
8462
|
+
.filter((track) => !hideEmptyTracks || track.blocks.length > 0);
|
|
8463
|
+
}
|
|
8464
|
+
const autoPackedTracks = (() => {
|
|
8465
|
+
const sortedBlocks = [...parsedBlocks]
|
|
8466
|
+
.filter(({ startMs, endMs }) => Number.isFinite(startMs) && Number.isFinite(endMs))
|
|
8467
|
+
.sort((a, b) => {
|
|
8468
|
+
if (a.startMs === b.startMs)
|
|
8469
|
+
return a.endMs - b.endMs;
|
|
8470
|
+
return a.startMs - b.startMs;
|
|
8471
|
+
});
|
|
8472
|
+
const trackLastEndTimes = [];
|
|
8473
|
+
const tracks = [];
|
|
8474
|
+
sortedBlocks.forEach((item) => {
|
|
8475
|
+
const trackIndex = trackLastEndTimes.findIndex((endMs) => item.startMs >= endMs);
|
|
8476
|
+
if (trackIndex === -1) {
|
|
8477
|
+
trackLastEndTimes.push(item.endMs);
|
|
8478
|
+
tracks.push([item]);
|
|
8479
|
+
}
|
|
8480
|
+
else {
|
|
8481
|
+
trackLastEndTimes[trackIndex] = item.endMs;
|
|
8482
|
+
tracks[trackIndex].push(item);
|
|
8483
|
+
}
|
|
8484
|
+
});
|
|
8485
|
+
return tracks;
|
|
8486
|
+
})();
|
|
8487
|
+
const packed = allowOverlap ? [parsedBlocks] : autoPackedTracks;
|
|
8488
|
+
return packed.map((track, trackIndex) => ({
|
|
8489
|
+
trackKey: `track-row-${trackIndex}`,
|
|
8490
|
+
trackKeyRaw: undefined,
|
|
8491
|
+
blocks: track,
|
|
8492
|
+
}));
|
|
8493
|
+
}, [allowOverlap, explicitTrackKeys, hideEmptyTracks, parsedBlocks]);
|
|
8494
|
+
// ---------- Render ----------
|
|
8495
|
+
if (virtualize) {
|
|
8496
|
+
return (jsxRuntime.jsx(VirtualizedTrackList, { tracks: resolvedTracks, resolvedHeight: resolvedHeight, gap: gap, virtualHeight: virtualHeight, overscan: overscan, renderTrackPrefix: renderTrackPrefix, renderTrackSuffix: renderTrackSuffix, renderBlockNode: renderBlockNode }));
|
|
8453
8497
|
}
|
|
8454
|
-
|
|
8455
|
-
|
|
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);
|
|
8498
|
+
return (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: gap, children: resolvedTracks.map((track, trackIndex) => {
|
|
8499
|
+
const trackBlocks = track.blocks.map((item) => item.block);
|
|
8480
8500
|
const prefix = renderTrackPrefix?.({
|
|
8481
8501
|
trackIndex,
|
|
8482
8502
|
trackBlocks,
|
|
8483
|
-
trackKey:
|
|
8503
|
+
trackKey: track.trackKeyRaw,
|
|
8484
8504
|
});
|
|
8485
8505
|
const suffix = renderTrackSuffix?.({
|
|
8486
8506
|
trackIndex,
|
|
8487
8507
|
trackBlocks,
|
|
8488
|
-
trackKey:
|
|
8508
|
+
trackKey: track.trackKeyRaw,
|
|
8489
8509
|
});
|
|
8490
|
-
return (jsxRuntime.jsx(TimeViewportTrackRow, { trackKey:
|
|
8510
|
+
return (jsxRuntime.jsx(TimeViewportTrackRow, { trackKey: track.trackKey, blocks: track.blocks, resolvedHeight: resolvedHeight, prefix: prefix, suffix: suffix, renderBlockNode: renderBlockNode }));
|
|
8491
8511
|
}) }));
|
|
8492
8512
|
}
|
|
8493
8513
|
function TimeRangeZoom({ range, onRangeChange, minDurationMs = DEFAULT_MIN_DURATION_MS, maxDurationMs = DEFAULT_MAX_DURATION_MS, zoomFactor = DEFAULT_ZOOM_FACTOR, resetDurationMs, showResetButton = true, disabled = false, labels, }) {
|
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import timezone from 'dayjs/plugin/timezone';
|
|
|
31
31
|
import utc from 'dayjs/plugin/utc';
|
|
32
32
|
import { TiDeleteOutline } from 'react-icons/ti';
|
|
33
33
|
import { ajvResolver } from '@hookform/resolvers/ajv';
|
|
34
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
34
35
|
import { rankItem } from '@tanstack/match-sorter-utils';
|
|
35
36
|
|
|
36
37
|
const DataTableContext = createContext({
|
|
@@ -8075,7 +8076,7 @@ function TimeViewportRoot({ viewportStart, viewportEnd, children, onViewportChan
|
|
|
8075
8076
|
return (jsx(TimeViewportContext.Provider, { value: { viewportStart, viewportEnd }, children: jsx(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 }) }));
|
|
8076
8077
|
}
|
|
8077
8078
|
function TimeViewportTrackRow({ trackKey, blocks, resolvedHeight, prefix, suffix, renderBlockNode, }) {
|
|
8078
|
-
return (jsxs(HStack, { align: "stretch", gap: 2, children: [prefix ? (jsx(Box, { minW: "fit-content", display: "flex", alignItems: "center", children: prefix })) : null, jsx(Box, { position: "relative", width: "100%", height: resolvedHeight, children: blocks.map((item, index) => renderBlockNode(item, index)) }), suffix ? (jsx(Box, { minW: "fit-content", display: "flex", alignItems: "center", children: suffix })) : null] }, trackKey));
|
|
8079
|
+
return (jsxs(HStack, { width: "100%", overflowX: 'hidden', align: "stretch", gap: 2, children: [prefix ? (jsx(Box, { minW: "fit-content", display: "flex", alignItems: "center", children: prefix })) : null, jsx(Box, { position: "relative", width: "100%", height: resolvedHeight, children: blocks.map((item, index) => renderBlockNode(item, index)) }), suffix ? (jsx(Box, { minW: "fit-content", display: "flex", alignItems: "center", children: suffix })) : null] }, trackKey));
|
|
8079
8080
|
}
|
|
8080
8081
|
const defaultLabels = {
|
|
8081
8082
|
zoomIn: 'Zoom in',
|
|
@@ -8337,35 +8338,55 @@ function TimeViewportHeader({ viewportStart, viewportEnd, tickCount = 7, tickStr
|
|
|
8337
8338
|
* Vertical grid lines for measuring block positions in the viewport.
|
|
8338
8339
|
* Render inside a relative container that also contains blocks.
|
|
8339
8340
|
*/
|
|
8340
|
-
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, }) {
|
|
8341
|
-
const
|
|
8342
|
-
const
|
|
8343
|
-
|
|
8344
|
-
if (!start || !end || !end.isAfter(start))
|
|
8341
|
+
function TimeViewportGrid({ viewportStart, viewportEnd, tickCount = 8, tickStrategy = 'count', tickUnit = 'hour', tickStep = 1, format, minorDivisions = 2, majorLineColor = 'gray.300', minorLineColor = 'gray.200', showMinorLines = true, zIndex = 0, animationDurationMs = VIEWPORT_TRANSITION_DURATION_MS, animationEasing = VIEWPORT_TRANSITION_EASING, }) {
|
|
8342
|
+
const { isValidViewport, getTicks } = useTimeViewport(viewportStart, viewportEnd, format);
|
|
8343
|
+
const majorTicks = getTicks({ tickStrategy, tickCount, tickUnit, tickStep });
|
|
8344
|
+
if (!isValidViewport || majorTicks.length < 2)
|
|
8345
8345
|
return null;
|
|
8346
|
-
const safeTickCount = Math.max(2, tickCount);
|
|
8347
|
-
const majorTicks = Array.from({ length: safeTickCount }, (_, index) => ({
|
|
8348
|
-
index,
|
|
8349
|
-
percent: (index / (safeTickCount - 1)) * 100,
|
|
8350
|
-
}));
|
|
8351
8346
|
const safeMinorDivisions = Math.max(1, minorDivisions);
|
|
8352
8347
|
const transitionValue = animationDurationMs > 0
|
|
8353
8348
|
? `transform ${animationDurationMs}ms ${animationEasing}, opacity ${animationDurationMs}ms ${animationEasing}`
|
|
8354
8349
|
: undefined;
|
|
8355
8350
|
const minorTicks = showMinorLines
|
|
8356
|
-
?
|
|
8357
|
-
const base =
|
|
8358
|
-
const next =
|
|
8351
|
+
? majorTicks.slice(0, -1).flatMap((tick, segmentIndex) => {
|
|
8352
|
+
const base = tick.percent;
|
|
8353
|
+
const next = majorTicks[segmentIndex + 1].percent;
|
|
8359
8354
|
const segment = [];
|
|
8360
8355
|
for (let step = 1; step < safeMinorDivisions; step += 1) {
|
|
8361
8356
|
segment.push(base + ((next - base) * step) / safeMinorDivisions);
|
|
8362
8357
|
}
|
|
8363
8358
|
return segment;
|
|
8364
|
-
})
|
|
8359
|
+
})
|
|
8365
8360
|
: [];
|
|
8366
8361
|
return (jsxs(Box, { position: "absolute", inset: 0, pointerEvents: "none", zIndex: zIndex, children: [minorTicks.map((percent, index) => (jsx(Box, { position: "absolute", inset: 0, transform: `translateX(${percent}%)`, transition: transitionValue, children: jsx(Box, { position: "absolute", insetInlineStart: 0, top: 0, bottom: 0, width: "1px", bg: minorLineColor, _dark: { bg: 'gray.700' } }) }, `minor-grid-${index}`))), majorTicks.map((tick) => (jsx(Box, { position: "absolute", inset: 0, transform: `translateX(${tick.percent}%)`, transition: transitionValue, children: jsx(Box, { position: "absolute", insetInlineStart: 0, top: 0, bottom: 0, width: "1px", bg: majorLineColor, _dark: { bg: 'gray.600' } }) }, `major-grid-${tick.index}`)))] }));
|
|
8367
8362
|
}
|
|
8368
|
-
function
|
|
8363
|
+
function VirtualizedTrackList({ tracks, resolvedHeight, gap, virtualHeight, overscan, renderTrackPrefix, renderTrackSuffix, renderBlockNode, }) {
|
|
8364
|
+
const parentRef = useRef(null);
|
|
8365
|
+
const rowHeightPx = parseInt(resolvedHeight, 10) || 28;
|
|
8366
|
+
const gapPx = gap * 4; // Chakra spacing token to px (1 unit = 4px)
|
|
8367
|
+
const virtualizer = useVirtualizer({
|
|
8368
|
+
count: tracks.length,
|
|
8369
|
+
getScrollElement: () => parentRef.current,
|
|
8370
|
+
estimateSize: () => rowHeightPx + gapPx,
|
|
8371
|
+
overscan,
|
|
8372
|
+
});
|
|
8373
|
+
return (jsx(Box, { ref: parentRef, overflowY: "auto", height: `${virtualHeight}px`, children: jsx(Box, { height: `${virtualizer.getTotalSize()}px`, position: "relative", children: virtualizer.getVirtualItems().map((virtualRow) => {
|
|
8374
|
+
const track = tracks[virtualRow.index];
|
|
8375
|
+
const trackBlocks = track.blocks.map((item) => item.block);
|
|
8376
|
+
const prefix = renderTrackPrefix?.({
|
|
8377
|
+
trackIndex: virtualRow.index,
|
|
8378
|
+
trackBlocks,
|
|
8379
|
+
trackKey: track.trackKeyRaw,
|
|
8380
|
+
});
|
|
8381
|
+
const suffix = renderTrackSuffix?.({
|
|
8382
|
+
trackIndex: virtualRow.index,
|
|
8383
|
+
trackBlocks,
|
|
8384
|
+
trackKey: track.trackKeyRaw,
|
|
8385
|
+
});
|
|
8386
|
+
return (jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${virtualRow.start}px)`, children: jsx(TimeViewportTrackRow, { trackKey: track.trackKey, blocks: track.blocks, resolvedHeight: resolvedHeight, prefix: prefix, suffix: suffix, renderBlockNode: renderBlockNode }) }, track.trackKey));
|
|
8387
|
+
}) }) }));
|
|
8388
|
+
}
|
|
8389
|
+
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, virtualize = false, virtualHeight = 400, overscan = 5, }) {
|
|
8369
8390
|
const { getGeometry, toTimeMs } = useTimeViewportBlockGeometry(viewportStart, viewportEnd);
|
|
8370
8391
|
const resolvedHeight = typeof height === 'number' ? `${height}px` : height;
|
|
8371
8392
|
const expandedBlocks = flattenTrackBlocks(blocks);
|
|
@@ -8400,74 +8421,73 @@ function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height = '28px
|
|
|
8400
8421
|
onBlockClick?.(block);
|
|
8401
8422
|
};
|
|
8402
8423
|
const isBlockClickable = Boolean(block.onClick || onBlockClick);
|
|
8403
|
-
return (jsx(Box, { position: "absolute", inset: 0, pointerEvents: "none",
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8424
|
+
return (jsx(Box, { height: "100%", position: "absolute", inset: 0, pointerEvents: "none", transform: `translateX(${geometry.leftPercent}%)`, transition: VIEWPORT_TRANSITION, children: jsx(Box, { width: `max(${geometry.widthPercent}%, ${minWidthPx}px)`, height: "100%", borderRadius: borderRadius, bg: block.background ??
|
|
8425
|
+
`${block.colorPalette ?? defaultColorPalette}.500`, _dark: {
|
|
8426
|
+
bg: block.background ??
|
|
8427
|
+
`${block.colorPalette ?? defaultColorPalette}.900`,
|
|
8428
|
+
}, 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 ? (jsx(Text, { fontSize: "xs", lineClamp: 1, color: "white", _dark: { color: 'gray.100' }, children: block.label })) : null }) }, block.id));
|
|
8408
8429
|
};
|
|
8430
|
+
// ---------- Resolve tracks ----------
|
|
8409
8431
|
const explicitTrackKeys = Array.from(new Set(expandedBlocks
|
|
8410
8432
|
.map((item) => item.track)
|
|
8411
8433
|
.filter((track) => track !== undefined)));
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8434
|
+
const resolvedTracks = useMemo(() => {
|
|
8435
|
+
if (explicitTrackKeys.length > 0) {
|
|
8436
|
+
return explicitTrackKeys
|
|
8437
|
+
.map((trackKey) => ({
|
|
8438
|
+
trackKey: `track-keyed-${String(trackKey)}`,
|
|
8439
|
+
trackKeyRaw: trackKey,
|
|
8440
|
+
blocks: parsedBlocks.filter((item) => item.block.track === trackKey),
|
|
8441
|
+
}))
|
|
8442
|
+
.filter((track) => !hideEmptyTracks || track.blocks.length > 0);
|
|
8443
|
+
}
|
|
8444
|
+
const autoPackedTracks = (() => {
|
|
8445
|
+
const sortedBlocks = [...parsedBlocks]
|
|
8446
|
+
.filter(({ startMs, endMs }) => Number.isFinite(startMs) && Number.isFinite(endMs))
|
|
8447
|
+
.sort((a, b) => {
|
|
8448
|
+
if (a.startMs === b.startMs)
|
|
8449
|
+
return a.endMs - b.endMs;
|
|
8450
|
+
return a.startMs - b.startMs;
|
|
8451
|
+
});
|
|
8452
|
+
const trackLastEndTimes = [];
|
|
8453
|
+
const tracks = [];
|
|
8454
|
+
sortedBlocks.forEach((item) => {
|
|
8455
|
+
const trackIndex = trackLastEndTimes.findIndex((endMs) => item.startMs >= endMs);
|
|
8456
|
+
if (trackIndex === -1) {
|
|
8457
|
+
trackLastEndTimes.push(item.endMs);
|
|
8458
|
+
tracks.push([item]);
|
|
8459
|
+
}
|
|
8460
|
+
else {
|
|
8461
|
+
trackLastEndTimes[trackIndex] = item.endMs;
|
|
8462
|
+
tracks[trackIndex].push(item);
|
|
8463
|
+
}
|
|
8464
|
+
});
|
|
8465
|
+
return tracks;
|
|
8466
|
+
})();
|
|
8467
|
+
const packed = allowOverlap ? [parsedBlocks] : autoPackedTracks;
|
|
8468
|
+
return packed.map((track, trackIndex) => ({
|
|
8469
|
+
trackKey: `track-row-${trackIndex}`,
|
|
8470
|
+
trackKeyRaw: undefined,
|
|
8471
|
+
blocks: track,
|
|
8472
|
+
}));
|
|
8473
|
+
}, [allowOverlap, explicitTrackKeys, hideEmptyTracks, parsedBlocks]);
|
|
8474
|
+
// ---------- Render ----------
|
|
8475
|
+
if (virtualize) {
|
|
8476
|
+
return (jsx(VirtualizedTrackList, { tracks: resolvedTracks, resolvedHeight: resolvedHeight, gap: gap, virtualHeight: virtualHeight, overscan: overscan, renderTrackPrefix: renderTrackPrefix, renderTrackSuffix: renderTrackSuffix, renderBlockNode: renderBlockNode }));
|
|
8433
8477
|
}
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
.filter(({ startMs, endMs }) => Number.isFinite(startMs) && Number.isFinite(endMs))
|
|
8437
|
-
.sort((a, b) => {
|
|
8438
|
-
if (a.startMs === b.startMs)
|
|
8439
|
-
return a.endMs - b.endMs;
|
|
8440
|
-
return a.startMs - b.startMs;
|
|
8441
|
-
});
|
|
8442
|
-
const trackLastEndTimes = [];
|
|
8443
|
-
const tracks = [];
|
|
8444
|
-
sortedBlocks.forEach((item) => {
|
|
8445
|
-
const trackIndex = trackLastEndTimes.findIndex((endMs) => item.startMs >= endMs);
|
|
8446
|
-
if (trackIndex === -1) {
|
|
8447
|
-
trackLastEndTimes.push(item.endMs);
|
|
8448
|
-
tracks.push([item]);
|
|
8449
|
-
}
|
|
8450
|
-
else {
|
|
8451
|
-
trackLastEndTimes[trackIndex] = item.endMs;
|
|
8452
|
-
tracks[trackIndex].push(item);
|
|
8453
|
-
}
|
|
8454
|
-
});
|
|
8455
|
-
return tracks;
|
|
8456
|
-
})();
|
|
8457
|
-
const tracks = allowOverlap ? [parsedBlocks] : autoPackedTracks;
|
|
8458
|
-
return (jsx(VStack, { align: "stretch", gap: gap, children: tracks.map((track, trackIndex) => {
|
|
8459
|
-
const trackBlocks = track.map((item) => item.block);
|
|
8478
|
+
return (jsx(VStack, { align: "stretch", gap: gap, children: resolvedTracks.map((track, trackIndex) => {
|
|
8479
|
+
const trackBlocks = track.blocks.map((item) => item.block);
|
|
8460
8480
|
const prefix = renderTrackPrefix?.({
|
|
8461
8481
|
trackIndex,
|
|
8462
8482
|
trackBlocks,
|
|
8463
|
-
trackKey:
|
|
8483
|
+
trackKey: track.trackKeyRaw,
|
|
8464
8484
|
});
|
|
8465
8485
|
const suffix = renderTrackSuffix?.({
|
|
8466
8486
|
trackIndex,
|
|
8467
8487
|
trackBlocks,
|
|
8468
|
-
trackKey:
|
|
8488
|
+
trackKey: track.trackKeyRaw,
|
|
8469
8489
|
});
|
|
8470
|
-
return (jsx(TimeViewportTrackRow, { trackKey:
|
|
8490
|
+
return (jsx(TimeViewportTrackRow, { trackKey: track.trackKey, blocks: track.blocks, resolvedHeight: resolvedHeight, prefix: prefix, suffix: suffix, renderBlockNode: renderBlockNode }));
|
|
8471
8491
|
}) }));
|
|
8472
8492
|
}
|
|
8473
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, }) {
|
|
@@ -105,6 +105,10 @@ export interface TimeViewportGridProps {
|
|
|
105
105
|
viewportStart?: TimeInput;
|
|
106
106
|
viewportEnd?: TimeInput;
|
|
107
107
|
tickCount?: number;
|
|
108
|
+
tickStrategy?: 'count' | 'timeUnit';
|
|
109
|
+
tickUnit?: 'minute' | 'hour' | 'day';
|
|
110
|
+
tickStep?: number;
|
|
111
|
+
format?: string;
|
|
108
112
|
minorDivisions?: number;
|
|
109
113
|
majorLineColor?: string;
|
|
110
114
|
minorLineColor?: string;
|
|
@@ -138,6 +142,12 @@ export interface TimeViewportBlocksProps {
|
|
|
138
142
|
trackKey?: string | number;
|
|
139
143
|
}) => ReactNode;
|
|
140
144
|
onBlockClick?: (block: TimeViewportBlockItem) => void;
|
|
145
|
+
/** Enable virtual scrolling for large track lists. */
|
|
146
|
+
virtualize?: boolean;
|
|
147
|
+
/** Fixed pixel height of the scroll container when virtualize is true. Defaults to 400. */
|
|
148
|
+
virtualHeight?: number;
|
|
149
|
+
/** Number of off-screen rows to render above/below the visible area. Defaults to 5. */
|
|
150
|
+
overscan?: number;
|
|
141
151
|
}
|
|
142
152
|
export interface TimeViewportRootProps {
|
|
143
153
|
viewportStart: TimeInput;
|
|
@@ -220,8 +230,8 @@ export declare function TimeViewportHeader({ viewportStart, viewportEnd, tickCou
|
|
|
220
230
|
* Vertical grid lines for measuring block positions in the viewport.
|
|
221
231
|
* Render inside a relative container that also contains blocks.
|
|
222
232
|
*/
|
|
223
|
-
export declare function TimeViewportGrid({ viewportStart, viewportEnd, tickCount, minorDivisions, majorLineColor, minorLineColor, showMinorLines, zIndex, animationDurationMs, animationEasing, }: TimeViewportGridProps): import("react/jsx-runtime").JSX.Element | null;
|
|
224
|
-
export declare function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height, minWidthPx, borderRadius, defaultColorPalette, showLabel, hideWhenOutOfView, hideEmptyTracks, gap, allowOverlap, overlapOpacity, renderTrackPrefix, renderTrackSuffix, onBlockClick, }: TimeViewportBlocksProps): import("react/jsx-runtime").JSX.Element;
|
|
233
|
+
export declare function TimeViewportGrid({ viewportStart, viewportEnd, tickCount, tickStrategy, tickUnit, tickStep, format, minorDivisions, majorLineColor, minorLineColor, showMinorLines, zIndex, animationDurationMs, animationEasing, }: TimeViewportGridProps): import("react/jsx-runtime").JSX.Element | null;
|
|
234
|
+
export declare function TimeViewportBlocks({ blocks, viewportStart, viewportEnd, height, minWidthPx, borderRadius, defaultColorPalette, showLabel, hideWhenOutOfView, hideEmptyTracks, gap, allowOverlap, overlapOpacity, renderTrackPrefix, renderTrackSuffix, onBlockClick, virtualize, virtualHeight, overscan, }: TimeViewportBlocksProps): import("react/jsx-runtime").JSX.Element;
|
|
225
235
|
export declare function TimeRangeZoom({ range, onRangeChange, minDurationMs, maxDurationMs, zoomFactor, resetDurationMs, showResetButton, disabled, labels, }: TimeRangeZoomProps): import("react/jsx-runtime").JSX.Element;
|
|
226
236
|
export declare function useTimeRangeZoom({ range, onRangeChange, minDurationMs, maxDurationMs, zoomFactor, resetDurationMs, disabled, labels, }: TimeRangeZoomProps): UseTimeRangeZoomResult;
|
|
227
237
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsol-oss/react-datatable5",
|
|
3
|
-
"version": "13.0.1-beta.
|
|
3
|
+
"version": "13.0.1-beta.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@tanstack/match-sorter-utils": "^8.15.1",
|
|
46
46
|
"@tanstack/react-query": "^5.66.9",
|
|
47
47
|
"@tanstack/react-table": "^8.21.2",
|
|
48
|
+
"@tanstack/react-virtual": "^3.13.0",
|
|
48
49
|
"@uidotdev/usehooks": "^2.4.1",
|
|
49
50
|
"ajv": "^8.12.0",
|
|
50
51
|
"ajv-errors": "^3.0.0",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"@storybook/addon-docs": "^10.0.7",
|
|
65
66
|
"@storybook/addon-onboarding": "^10.0.7",
|
|
66
67
|
"@storybook/react-vite": "^10.0.7",
|
|
68
|
+
"@tanstack/react-virtual": "^3.13.18",
|
|
67
69
|
"@types/ajv-errors": "^2.0.0",
|
|
68
70
|
"@types/json-schema": "^7.0.15",
|
|
69
71
|
"@types/react": "19.0.2",
|