@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
687 lines (686 loc) • 32.1 kB
JavaScript
import {} from "../+types.js";
import { useRef } from "react";
import { makeColumnView } from "./helpers/column-view.js";
import { computeBounds, computeColumnPositions, computeRowPositions, makeLayoutState, updateFull, } from "@1771technologies/lytenyte-shared";
import { computed, effect, makeAtom, signal } from "@1771technologies/lytenyte-core/yinternal";
import { makeRowLayout } from "./helpers/row-layout/row-layout.js";
import { equal, rangesOverlap } from "@1771technologies/lytenyte-shared";
import { makeColumnLayout } from "./helpers/column-layout.js";
import { emptyRowDataSource } from "./helpers/empty-row-data-source.js";
import { getFullWidthCallback } from "./helpers/get-full-width-callback.js";
import { getSpanFn } from "./helpers/get-span-callback.js";
import { makeColumnField } from "./api/column-field.js";
import { makeSortForColumn } from "./api/sort-for-column.js";
import { columnAddRowGroup } from "./helpers/column-add-row-group.js";
import { makeEventListeners } from "./api/event-listeners.js";
import { makeScrollIntoView } from "./api/scroll-into-view.js";
import { makeColumnByIndex } from "./api/column-from-index.js";
import { makeColumnIndex } from "./api/column-index.js";
import { makeRowGroupColumnIndex } from "./api/row-group-column-index.js";
import { makeRowGroupIsExpanded } from "./api/row-group-is-expanded.js";
import { makeRowGroupToggle } from "./api/row-group-toggle.js";
import { makeRowGroupApplyExpansions } from "./api/row-group-apply-expansions.js";
import { makeEditBegin } from "./api/edit-begin.js";
import { makeEditIsCellActive } from "./api/edit-is-cell-active.js";
import { makeEditEnd } from "./api/edit-end.js";
import { makeEditUpdate } from "./api/edit-update.js";
import { makeRowById } from "./api/row-by-id.js";
import { makeRowByIndex } from "./api/row-by-index.js";
import { makeRowDetailIsExpanded } from "./api/row-detail-is-expanded.js";
import { makeRowDetailToggle } from "./api/row-detail-toggle.js";
import { makeRowDetailRenderedHeight } from "./api/row-detail-rendered-height.js";
import { columnHandleMarker } from "./helpers/column-marker.js";
import { makeRowSelect } from "./api/row-select.js";
import { makeRowSelectAll } from "./api/row-select-all.js";
import { makeRowSelected } from "./api/row-selected.js";
import { makeRowHandleSelect } from "./api/row-handle-select.js";
import { makeUseRowDrag } from "./api/use-row-drag.js";
import { makeColumnById } from "./api/column-by-id.js";
import { makeColumnResize } from "./api/column-resize.js";
import { makeColumnUpdate } from "./api/column-update.js";
import { makeColumnMove } from "./api/column-move.js";
import { makeColumnGroupToggle } from "./api/column-group-toggle.js";
import { makeColumnAutosize } from "./api/column-autosize.js";
import { makeExportCsv, makeExportCsvFile } from "./api/export-csv.js";
import { makeExportDataRect } from "./api/export-data-rect.js";
import { makeDialogFrameClose, makeDialogFrameOpen } from "./api/dialog-frame.js";
import { makePopoverFrameClose, makePopoverFrameOpen } from "./api/popover-frame.js";
import { makePositionFromElement } from "./api/position-from-element.js";
import { splitCellSelectionRect, } from "../cell-selection/split-cell-selection-rect.js";
import { boundSelectionRect } from "../cell-selection/bound-selection-rect.js";
import { makeCellRoot } from "./api/cell-root.js";
import { splitOnPivot } from "../cell-selection/split-on-pivot.js";
import { makeRowStore } from "@1771technologies/lytenyte-core/yinternal";
const EMPTY_POSITION_ARRAY = new Uint32Array();
const DEFAULT_HEADER_HEIGHT = 40;
const COLUMN_GROUP_JOIN_DELIMITER = "-->";
export function makeLyteNyte(p) {
/**
* Primitive ATOMS and State
*/
const rtl = signal(p.rtl ?? false);
const columns = signal(p.columns ?? []);
const base = signal(p.columnBase ?? {});
const gridId = signal(p.gridId);
const headerHeight = signal(p.headerHeight ?? DEFAULT_HEADER_HEIGHT);
const headerGroupHeight = signal(p.headerGroupHeight ?? DEFAULT_HEADER_HEIGHT);
const columnSizeToFit = signal(p.columnSizeToFit ?? false);
const viewport = signal(null);
const viewportHeightInner = signal(0);
const viewportHeightOuter = signal(0);
const viewportWidthInner = signal(0);
const viewportWidthOuter = signal(0);
const columnGroupJoinDelimiter = signal(p.columnGroupJoinDelimiter ?? COLUMN_GROUP_JOIN_DELIMITER);
const columnGroupExpansions = signal(p.columnGroupExpansions ?? {});
const columnGroupDefaultExpansion = signal(p.columnGroupDefaultExpansion ?? true);
const rowDataSource = signal(emptyRowDataSource);
const rowHeight = signal(40);
if (typeof p.rowHeight === "function")
rowHeight.set(() => p.rowHeight);
else
rowHeight.set(p.rowHeight ?? 40);
const rowAutoHeightGuess = signal(p.rowAutoHeightGuess ?? 40);
const rowScanDistance = signal(p.rowScanDistance ?? 100);
const colScanDistance = signal(p.colScanDistance ?? 100);
const rowOverscanTop = signal(p.rowOverscanTop ?? 10);
const rowOverscanBottom = signal(p.rowOverscanBottom ?? 10);
const colOverScanStart = signal(p.colOverscanStart ?? 2);
const colOverscanEnd = signal(p.colOverscanEnd ?? 2);
const xScroll = signal(0);
const yScroll = signal(0);
const cellRenderers = signal(p.cellRenderers ?? {});
const rowFullWidthPredicate = signal({ fn: p.rowFullWidthPredicate ?? null });
const rowFullWidthRenderer = signal({ fn: p.rowFullWidthRenderer ?? (() => "Not defined") });
const sortModel = signal(p.sortModel ?? []);
const filterModel = signal(p.filterModel ?? {});
const filterInModel = signal(p.filterInModel ?? {});
const rowGroupModel = signal(p.rowGroupModel ?? []);
const aggModel = signal(p.aggModel ?? {});
const rowGroupDisplayMode = signal(p.rowGroupDisplayMode ?? "single-column");
const rowGroupDefaultExpansion = signal(p.rowGroupDefaultExpansion ?? false);
const rowGroupExpansions = signal(p.rowGroupExpansions ?? {});
const rowGroupColumn = signal(p.rowGroupColumn ?? {});
const headerCellRenderers = signal(p.headerCellRenderers ?? {});
const floatingCellRenderers = signal(p.floatingCellRenderers ?? {});
const floatingRowEnabled = signal(p.floatingRowEnabled ?? false);
const floatingRowHeight = signal(p.floatingRowHeight ?? 40);
const editRenderers = signal(p.editRenderers ?? {});
const editRowValidatorFn = signal({ fn: p.editRowValidatorFn ?? (() => true) });
const editClickActivator = signal(p.editClickActivator ?? "single");
const editCellMode = signal(p.editCellMode ?? "readonly");
const columnMarker = signal(p.columnMarker ?? {});
const columnMarkerEnabled = signal(p.columnMarkerEnabled ?? false);
const columnDoubleClickToAutosize = signal(p.columnDoubleClickToAutosize ?? true);
const rowDetailHeight = signal(p.rowDetailHeight ?? 300);
const rowDetailAutoHeightGuess = signal(p.rowDetailAutoHeightGuess ?? 300);
const rowDetailExpansions = signal(p.rowDetailExpansions ?? new Set());
const rowDetailRenderer = signal({ fn: p.rowDetailRenderer ?? (() => "Not defined") });
const rowSelectedIds = signal(p.rowSelectedIds ?? new Set());
const rowSelectionMode = signal(p.rowSelectionMode ?? "none");
const rowSelectChildren = signal(p.rowSelectChildren ?? false);
const rowSelectionActivator = signal(p.rowSelectionActivator ?? "single-click");
const virtualizeRows = signal(p.virtualizeRows ?? true);
const virtualizeCols = signal(p.virtualizeCols ?? true);
const quickSearch = signal(p.quickSearch ?? null);
const quickSearchSensitivity = signal(p.quickSearchSensitivity ?? "case-insensitive");
const columnPivotMode = signal(p.columnPivotMode ?? false);
const columnPivotModel = signal(p.columnPivotModel ??
{
columns: [],
filters: {},
filtersIn: {},
rows: [],
sorts: [],
values: [],
});
const columnPivotColumns = signal([]);
const columnPivotColumnGroupExpansions = signal({});
const columnPivotRowGroupExpansions = signal({});
const cellSelections = signal(p.cellSelections ?? []);
const cellSelectionMode = signal(p.cellSelectionMode ?? "none");
const internal__rowGroupColumnState = signal({});
const internal_cellSelectionPivot = signal(null);
const internal_cellSelectionAdditive = signal(null);
const internal_cellSelectionSplits = computed(() => {
const selections = cellSelections();
// @ts-expect-error the $() is defined but not typed.
const topCount = rowDataStore.rowTopCount.$();
// @ts-expect-error the $() is defined but not typed.
const centerCount = rowDataStore.rowCenterCount.$();
const meta = columnMeta();
const p = internal_cellSelectionPivot();
const splits = selections.flatMap((rect) => {
return splitCellSelectionRect({
rect,
rowTopCount: topCount,
rowCenterCount: centerCount,
colStartCount: meta.columnVisibleStartCount,
colCenterCount: meta.columnVisibleCenterCount,
});
});
const firstWithinPivot = splits.findIndex((c) => p &&
p.rowStart >= c.rowStart &&
p.rowEnd <= c.rowEnd &&
p.columnStart >= c.columnStart &&
p.columnEnd <= c.columnEnd);
if (firstWithinPivot !== -1) {
const pivotSplits = splitOnPivot(splits[firstWithinPivot], p);
if (pivotSplits)
splits.splice(firstWithinPivot, 1, ...pivotSplits);
else
splits.splice(firstWithinPivot, 1);
}
return splits;
});
const dialogFrame = signal(p.dialogFrames ?? {});
const popoverFrame = signal(p.popoverFrames ?? {});
const internal_rowSelectionPivot = signal(null);
const internal_rowSelectionLastWasDeselect = signal(false);
const internal_draggingHeader = signal(null);
const internal_rowAutoHeightCache = signal({});
const internal_rowDetailHeightCache = signal({});
const internal_focusActive = signal(null);
const internal_focusPrevCol = signal(null);
const internal_focusPrevRow = signal(null);
const internal_editActivePosition = signal(null);
const internal_editData = signal(null);
const internal_editValidation = signal(true);
const internal_dialogFrames = signal({});
const internal_popoverFrames = signal({});
const columnCount$ = computed(() => {
const view = columnView();
return view.visibleColumns.length;
});
const layoutState = makeLayoutState(0);
const layoutState$ = computed(() => {
// @ts-expect-error the $() is defined but not typed.
rowDataStore.rowBottomCount.$();
// @ts-expect-error the $() is defined but not typed.
rowDataStore.rowCenterCount.$();
// @ts-expect-error the $() is defined but not typed.
rowDataStore.rowTopCount.$();
rowDataSource();
rowDetailExpansions();
rowGroupModel();
sortModel();
filterModel();
aggModel();
filterInModel();
columnPivotModel();
columns();
columnPivotColumns();
// @ts-expect-error the $() is defined but not typed.
const rowCount = rowDataStore.rowCount.$();
const columnCount = columnCount$();
if (rowCount > layoutState.computed.length || columnCount != layoutState.base.length) {
Object.assign(layoutState, makeLayoutState(columnCount, rowCount + 2000));
}
else {
layoutState.computed.fill(0);
layoutState.special.fill(0);
layoutState.lookup.clear();
}
return { layout: { ...layoutState }, cache: new Map() };
});
/**
* VIEW BOUNDS
*/
const bounds = computed(() => {
const vpWidth = viewportWidthInner();
const vpHeight = viewportHeightInner();
const scrollTop = yScroll();
const scrollLeft = xScroll();
const xPos = xPositions();
const yPos = yPositions();
// @ts-expect-error the $() is defined but not typed.
const topCount = rowDataStore.rowTopCount.$();
// @ts-expect-error the $() is defined but not typed.
const bottomCount = rowDataStore.rowBottomCount.$();
const start = startCount();
const end = endCount();
const bounds = computeBounds({
viewportWidth: vpWidth,
viewportHeight: vpHeight,
scrollTop,
scrollLeft,
xPositions: xPos,
yPositions: yPos,
topCount,
bottomCount,
startCount: start,
endCount: end,
rowOverscanTop: rowOverscanTop(),
rowOverscanBottom: rowOverscanBottom(),
colOverscanStart: colOverScanStart(),
colOverscanEnd: colOverscanEnd(),
});
return bounds;
}, { dirty: (prev, next) => !equal(prev, next) });
const internal_colBounds = computed(() => {
const b = bounds();
return [b.colCenterStart, b.colCenterEnd];
}, { dirty: (l, r) => !equal(l, r) });
/**
* COLUMN VIEW
* Compute the column layout. This impacts both the row layout and header layout. Column layout
* is impacted by the column definitions, the group expansions, the row group display mode.
*/
const columnView = computed(() => {
const groupState = internal__rowGroupColumnState();
const pivotMode = columnPivotMode();
let cols;
if (pivotMode) {
const model = columnPivotModel();
cols = columnAddRowGroup({
columns: columnPivotColumns(),
rowGroupDisplayMode: "single-column",
rowGroupModel: model.rows.filter((c) => c.active ?? true).map((c) => c.field),
rowGroupTemplate: rowGroupColumn(),
rowGroupColumnState: groupState,
});
}
else {
cols = columnAddRowGroup({
columns: columns(),
rowGroupDisplayMode: rowGroupDisplayMode(),
rowGroupModel: rowGroupModel(),
rowGroupTemplate: rowGroupColumn(),
rowGroupColumnState: groupState,
});
}
const colsWithMarker = columnHandleMarker({
columns: cols,
marker: columnMarker(),
markerEnabled: columnMarkerEnabled(),
});
const view = makeColumnView({
columns: colsWithMarker,
base: base(),
groupExpansionDefault: columnGroupDefaultExpansion(),
groupExpansions: columnGroupExpansions(),
groupJoinDelimiter: columnGroupJoinDelimiter(),
});
return view;
});
/**
* COMPUTE ATOMS
*/
const headerLayoutFull = computed(() => {
const view = columnView();
const layout = makeColumnLayout(view, floatingRowEnabled());
return { maxCol: view.maxCol, maxRow: view.maxRow, layout };
});
const headerPosition$ = computed(() => {
const focus = internal_focusActive();
return focus?.kind === "header-cell" ||
focus?.kind === "floating-cell" ||
focus?.kind === "header-group-cell"
? focus
: null;
});
const headerLayout = computed(() => {
const view = columnView();
const layout = headerLayoutFull();
const spanBounds = bounds();
const headerPos = headerPosition$();
const filtered = layout.layout.map((row) => {
return row.filter((col) => {
if (col.colPin)
return true;
if (headerPos) {
if (rangesOverlap(col.colStart, col.colEnd, headerPos.colIndex, headerPos.colIndex + 1))
return true;
}
return col.colStart >= spanBounds.colCenterStart && col.colStart < spanBounds.colCenterEnd;
});
});
const dragged = internal_draggingHeader();
// When dragging a group column, we may end up removing that group column when joining
// the group with other columns in the same group (if they are separated). We want to ensure
// the group remains mounted until after the drag. Hence we check if there would've been a merge
// and add the dragged element to the layout. Once the drag ends the element will be removed. The
// isHiddenMove is important for this.
if (dragged) {
const row = dragged.rowStart;
const has = filtered[row].findIndex((c) => c.kind === "group" && c.idOccurrence === dragged.idOccurrence);
if (has === -1) {
filtered[row].push({ ...dragged, isHiddenMove: true });
}
}
return { maxCol: view.maxCol, maxRow: view.maxRow, layout: filtered };
});
const columnGroupMeta = computed(() => columnView().meta);
const columnMeta = computed(() => {
const view = columnView();
return {
columnLookup: view.lookup,
columnsVisible: view.visibleColumns,
columnVisibleCenterCount: view.centerCount,
columnVisibleEndCount: view.endCount,
columnVisibleStartCount: view.startCount,
};
});
const xPositions = computed(() => {
if (!viewport())
return EMPTY_POSITION_ARRAY;
const view = columnView();
return computeColumnPositions(view.visibleColumns, base(), viewportWidthInner(), columnSizeToFit());
});
const widthTotal = computed(() => xPositions().at(-1));
const headerHeightTotal = computed(() => {
const groupHHeight = headerGroupHeight();
const hHeight = headerHeight();
const view = columnView();
const floating = floatingRowEnabled();
const floatingHeight = floatingRowHeight();
return (view.maxRow - 1) * groupHHeight + hHeight + (floating ? floatingHeight : 0);
});
const startCount = computed(() => columnView().startCount);
const endCount = computed(() => columnView().endCount);
/** ROWS */
const rowDataStore = makeRowStore({
getRow: (r) => {
return rowDataSource().rowByIndex(r);
},
});
const yPositions = computed(() => {
if (!viewport())
return EMPTY_POSITION_ARRAY;
// These can change the positions
sortModel();
filterModel();
rowGroupModel();
columnPivotModel();
// @ts-expect-error the $() is defined but not typed.
const rowCount = rowDataStore.rowCount.$();
const innerHeight = viewportHeightInner();
const detailExpansions = rowDetailExpansions();
internal_rowDetailHeightCache();
const rds = rowDataSource();
const rows = Object.fromEntries([...detailExpansions]
.map((x) => rds.rowToIndex(x))
.filter((x) => x != null)
.map((x) => [x, api.rowByIndex(x)]));
const headerHeight = headerHeightTotal();
const x = computeRowPositions(rowCount, rowHeight(), rowAutoHeightGuess(), internal_rowAutoHeightCache(), (i) => {
const row = rows[i];
if (!row || !api.rowDetailIsExpanded(row))
return 0;
return api.rowDetailRenderedHeight(row);
}, innerHeight - headerHeight);
return x;
});
const heightTotal = computed(() => yPositions().at(-1));
const hasSpans = computed(() => {
const visible = columnMeta().columnsVisible;
return cellSelectionMode() !== "none" || !visible.every((c) => !(c.colSpan || c.rowSpan));
});
const rowView = computed(() => {
if (!viewport())
return {
bottom: [],
center: [],
rowBottomTotalHeight: 0,
rowCenterTotalHeight: 0,
rowFirstCenter: 0,
rowFocusedIndex: 0,
rowTopTotalHeight: 0,
top: [],
};
let n = bounds();
if (!virtualizeRows()) {
n = { ...n, rowCenterStart: n.rowTopEnd, rowCenterEnd: n.rowCenterLast };
}
if (!virtualizeCols()) {
n = { ...n, colCenterStart: n.colStartEnd, colCenterEnd: n.colCenterLast };
}
const rowScan = rowScanDistance();
const columns = columnMeta().columnsVisible;
const rds = rowDataSource();
const { layout, cache } = layoutState$();
// @ts-expect-error the $() is defined but not typed.
const topCount = rowDataStore.rowTopCount.$();
// @ts-expect-error the $() is defined but not typed.
const botCount = rowDataStore.rowBottomCount.$();
// @ts-expect-error the $() is defined but not typed.
const rowCount = rowDataStore.rowCount.$();
const expansions = rowDetailExpansions();
const fullWidthPredicate = rowFullWidthPredicate().fn;
updateFull({
topCount,
botCount,
startCount: n.colStartEnd,
endCount: n.colEndEnd - n.colEndStart,
centerCount: n.colCenterLast - n.colStartEnd,
computeColSpan: getSpanFn(rds, grid, columns, "col"),
computeRowSpan: getSpanFn(rds, grid, columns, "row"),
isFullWidth: fullWidthPredicate ? getFullWidthCallback(rds, fullWidthPredicate, grid) : null,
isRowCutoff: (r) => {
const row = rds.rowByIndex(r);
return !row || row.kind === "branch" || expansions.has(row.id);
},
rowScanDistance: rowScan,
rowStart: n.rowCenterStart,
rowEnd: n.rowCenterEnd,
rowMax: n.rowCenterLast,
...layout,
});
const yPos = yPositions();
const botStart = rowCount - botCount;
const focus = internal_focusActive();
const view = makeRowLayout({
view: n,
viewCache: cache,
layout,
rds: rowDataStore,
columns,
focus: internal_focusActive(),
});
const topHeight = yPos[topCount];
const botHeight = yPos.at(-1) - yPos[botStart];
const centerHeight = yPos.at(-1) - topHeight - botHeight;
return {
...view,
rowFocusedIndex: focus?.kind === "cell" || focus?.kind === "full-width"
? focus.rowIndex < topCount || focus.rowIndex >= rowCount - botCount
? null
: focus.rowIndex
: null,
rowTopTotalHeight: topHeight,
rowBottomTotalHeight: botHeight,
rowCenterTotalHeight: centerHeight,
rowFirstCenter: n.rowCenterStart,
};
});
/**
* STATE VIEW AND API
*/
const gridView = computed(() => {
return {
header: headerLayout(),
rows: rowView(),
};
});
const state = {
rtl: makeAtom(rtl),
columns: makeAtom(columns),
columnMeta: makeAtom(columnMeta),
columnBase: makeAtom(base),
columnGroupExpansions: makeAtom(columnGroupExpansions),
columnGroupDefaultExpansion: makeAtom(columnGroupDefaultExpansion),
columnGroupJoinDelimiter: makeAtom(columnGroupJoinDelimiter),
columnGroupMeta: makeAtom(columnGroupMeta),
columnSizeToFit: makeAtom(columnSizeToFit),
gridId: makeAtom(gridId),
widthTotal: makeAtom(widthTotal),
heightTotal: makeAtom(heightTotal),
headerHeight: makeAtom(headerHeight),
headerGroupHeight: makeAtom(headerGroupHeight),
viewport: makeAtom(viewport),
viewportHeightInner: makeAtom(viewportHeightInner),
viewportHeightOuter: makeAtom(viewportHeightOuter),
viewportWidthInner: makeAtom(viewportWidthInner),
viewportWidthOuter: makeAtom(viewportWidthOuter),
xPositions: makeAtom(xPositions),
yPositions: makeAtom(yPositions),
rowDataStore,
rowDataSource: makeAtom(rowDataSource),
rowAutoHeightGuess: makeAtom(rowAutoHeightGuess),
rowHeight: makeAtom(rowHeight),
rowScanDistance: makeAtom(rowScanDistance),
colScanDistance: makeAtom(colScanDistance),
colOverscanStart: makeAtom(colOverScanStart),
colOverscanEnd: makeAtom(colOverscanEnd),
rowOverscanTop: makeAtom(rowOverscanTop),
rowOverscanBottom: makeAtom(rowOverscanBottom),
rowFullWidthPredicate: makeAtom(rowFullWidthPredicate),
rowFullWidthRenderer: makeAtom(rowFullWidthRenderer),
cellRenderers: makeAtom(cellRenderers),
sortModel: makeAtom(sortModel),
filterModel: makeAtom(filterModel),
filterInModel: makeAtom(filterInModel),
rowGroupModel: makeAtom(rowGroupModel),
aggModel: makeAtom(aggModel),
rowGroupColumn: makeAtom(rowGroupColumn),
rowGroupDefaultExpansion: makeAtom(rowGroupDefaultExpansion),
rowGroupDisplayMode: makeAtom(rowGroupDisplayMode),
rowGroupExpansions: makeAtom(rowGroupExpansions),
headerCellRenderers: makeAtom(headerCellRenderers),
floatingCellRenderers: makeAtom(floatingCellRenderers),
floatingRowEnabled: makeAtom(floatingRowEnabled),
floatingRowHeight: makeAtom(floatingRowHeight),
editRenderers: makeAtom(editRenderers),
editCellMode: makeAtom(editCellMode),
editClickActivator: makeAtom(editClickActivator),
editRowValidatorFn: makeAtom(editRowValidatorFn),
editActivePosition: makeAtom(computed(() => internal_editActivePosition())),
columnMarker: makeAtom(columnMarker),
columnMarkerEnabled: makeAtom(columnMarkerEnabled),
columnDoubleClickToAutosize: makeAtom(columnDoubleClickToAutosize),
rowDetailExpansions: makeAtom(rowDetailExpansions),
rowDetailHeight: makeAtom(rowDetailHeight),
rowDetailRenderer: makeAtom(rowDetailRenderer),
rowDetailAutoHeightGuess: makeAtom(rowDetailAutoHeightGuess),
rowSelectedIds: makeAtom(rowSelectedIds),
rowSelectionMode: makeAtom(rowSelectionMode),
rowSelectionPivot: makeAtom(internal_rowSelectionPivot),
rowSelectionActivator: makeAtom(rowSelectionActivator),
rowSelectChildren: makeAtom(rowSelectChildren),
viewBounds: makeAtom(bounds),
virtualizeRows: makeAtom(virtualizeRows),
virtualizeCols: makeAtom(virtualizeCols),
quickSearch: makeAtom(quickSearch),
quickSearchSensitivity: makeAtom(quickSearchSensitivity),
columnPivotMode: makeAtom(columnPivotMode),
columnPivotColumns: makeAtom(columnPivotColumns),
columnPivotModel: makeAtom(columnPivotModel),
columnPivotColumnGroupExpansions: makeAtom(columnPivotColumnGroupExpansions),
columnPivotRowGroupExpansions: makeAtom(columnPivotRowGroupExpansions),
dialogFrames: makeAtom(dialogFrame),
popoverFrames: makeAtom(popoverFrame),
cellSelections: makeAtom(cellSelections),
cellSelectionMode: makeAtom(cellSelectionMode),
};
const api = {};
const grid = { state, view: makeAtom(gridView), api };
const listeners = makeEventListeners();
Object.assign(api, {
cellRoot: makeCellRoot(grid),
columnField: makeColumnField(grid),
columnByIndex: makeColumnByIndex(grid),
columnIndex: makeColumnIndex(grid),
columnById: makeColumnById(grid),
columnResize: makeColumnResize(grid),
columnUpdate: makeColumnUpdate(grid),
columnMove: makeColumnMove(grid),
sortForColumn: makeSortForColumn(grid),
rowIsGroup: (r) => r.kind === "branch",
rowIsLeaf: (r) => r.kind === "leaf",
eventAddListener: listeners.eventAddListener,
eventRemoveListener: listeners.eventRemoveListener,
eventFire: listeners.eventFire,
rowGroupColumnIndex: makeRowGroupColumnIndex(grid),
rowGroupIsExpanded: makeRowGroupIsExpanded(grid),
rowGroupToggle: makeRowGroupToggle(grid),
rowGroupApplyExpansions: makeRowGroupApplyExpansions(grid),
scrollIntoView: makeScrollIntoView(grid),
editBegin: makeEditBegin(grid),
editEnd: makeEditEnd(grid),
editIsCellActive: makeEditIsCellActive(grid),
editUpdate: makeEditUpdate(grid),
rowById: makeRowById(grid),
rowByIndex: makeRowByIndex(grid),
rowDetailIsExpanded: makeRowDetailIsExpanded(grid),
rowDetailRenderedHeight: makeRowDetailRenderedHeight(grid),
rowDetailToggle: makeRowDetailToggle(grid),
rowSelect: makeRowSelect(grid),
rowSelectAll: makeRowSelectAll(grid),
rowSelected: makeRowSelected(grid),
rowHandleSelect: makeRowHandleSelect(grid),
columnToggleGroup: makeColumnGroupToggle(grid),
columnAutosize: makeColumnAutosize(grid),
useRowDrag: makeUseRowDrag(grid),
exportCsv: makeExportCsv(grid),
exportCsvFile: makeExportCsvFile(grid),
exportDataRect: makeExportDataRect(grid),
dialogFrameClose: makeDialogFrameClose(grid),
dialogFrameOpen: makeDialogFrameOpen(grid),
popoverFrameClose: makePopoverFrameClose(grid),
popoverFrameOpen: makePopoverFrameOpen(grid),
positionFromElement: makePositionFromElement(grid),
});
Object.assign(grid, {
internal: {
headerCols: makeAtom(computed(() => columnView().maxCol)),
headerRows: makeAtom(computed(() => columnView().maxRow)),
headerHeightTotal: makeAtom(headerHeightTotal),
xScroll: makeAtom(xScroll),
yScroll: makeAtom(yScroll),
layout: layoutState,
colBounds: makeAtom(internal_colBounds),
hasSpans: makeAtom(hasSpans),
focusActive: makeAtom(internal_focusActive),
focusPrevColIndex: makeAtom(internal_focusPrevCol),
focusPrevRowIndex: makeAtom(internal_focusPrevRow),
editActivePos: makeAtom(internal_editActivePosition),
editData: makeAtom(internal_editData),
editValidation: makeAtom(internal_editValidation),
rowAutoHeightCache: makeAtom(internal_rowAutoHeightCache),
rowDetailAutoHeightCache: makeAtom(internal_rowDetailHeightCache),
rowSelectedIds: rowSelectedIds,
rowSelectionPivot: makeAtom(internal_rowSelectionPivot),
rowSelectionLastWasDeselect: makeAtom(internal_rowSelectionLastWasDeselect),
draggingHeader: makeAtom(internal_draggingHeader),
rowGroupColumnState: makeAtom(internal__rowGroupColumnState),
dialogFrames: makeAtom(internal_dialogFrames),
popoverFrames: makeAtom(internal_popoverFrames),
cellSelectionPivot: makeAtom(internal_cellSelectionPivot),
cellSelectionAdditiveRects: makeAtom(internal_cellSelectionAdditive),
cellSelectionIsDeselect: makeAtom(signal(false)),
cellSelectionSplits: makeAtom(internal_cellSelectionSplits),
},
});
effect(() => {
rowDataSource().init(grid);
});
rowDataSource.set(p.rowDataSource ?? emptyRowDataSource);
// Ensure cell selections are bounded.
effect(() => {
// @ts-expect-error the $() is defined but not typed.
rowDataStore.rowCount.$();
columnMeta();
const selections = cellSelections();
const safeSelections = selections.map((c) => boundSelectionRect(grid, c));
if (equal(selections, safeSelections))
return;
cellSelections.set(selections);
});
return grid;
}
export function useLyteNyte(p) {
const m = useRef(null);
if (!m.current) {
m.current = makeLyteNyte(p);
}
return m.current;
}