/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-refresh/only-export-components */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { type CellArray, type CellClickedEventArgs, CompactSelection, type DataEditorProps, type DataEditorRef, type EditableGridCell, type GetCellsThunk, type GridCell, GridCellKind, type GridColumn, type GridMouseEventArgs, type GridSelection, type HeaderClickedEventArgs, type Item, type Rectangle, } from '@glideapps/glide-data-grid'; import { IconGrid4x4 } from '@tabler/icons-react'; import { getUUID } from '@warkypublic/artemis-kit'; import { getNestedValue } from '@warkypublic/artemis-kit/object'; import { createSyncStore } from '@warkypublic/zustandsyncstore'; import { produce } from 'immer'; import { type PropsWithChildren, type ReactNode, useEffect } from 'react'; import { type MantineBetterMenuInstance, type MantineBetterMenuInstanceItem, useMantineBetterMenus, } from '../../MantineBetterMenu'; import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column'; import { SortDownSprite } from './sprites/SortDown'; import { SortUpSprite } from './sprites/SortUp'; import { SpriteImage } from './sprites/SpriteImage'; export type FilterOption = { datatype?: 'array' | 'boolean' | 'date' | 'function' | 'number' | 'object' | 'string'; id: string; operator: FilterOptionOperator; value: string; value2?: string; }; export type FilterOptionOperator = | 'between' | 'contains' | 'endswith' | 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'neq' | 'startswith'; export interface GridlerProps extends PropsWithChildren { allowMultiSelect?: boolean; columns?: GridlerColumns; defaultSort?: Array; enableOddEvenRowColor?: boolean; getMenuItems?: ( id: string, storeState: GridlerState, row?: any, col?: GridlerColumn, defaultItems?: Array ) => Array; glideProps?: Partial; headerHeight?: number; height?: number | string; hideMenu?: (id: string) => void; keyField?: string; maxConcurrency?: number; onChange?: (values: Array>) => void; onMounted?: (getState: GridlerState['getState'], setState: GridlerState['setState']) => void; onUnMounted?: () => void; pageSize?: number; progressiveScroll?: boolean; RenderCell?: >( row: TRowType, colindex: number, colid: string, value: any, store: GridlerState ) => GridCell; rowHeight?: number; scrollToRowKey?: number; searchFields?: Array; searchStr?: string; sections?: { bottom?: React.ReactNode; left?: React.ReactNode; right?: React.ReactNode; rightElementDisabled?: boolean; rightElementEnd?: React.ReactNode; rightElementStart?: React.ReactNode; top?: React.ReactNode; }; selectedRowKey?: number; selectFirstRowOnMount?: boolean; selectMode?: 'cell' | 'row'; showMenu?: (id: string, options?: Partial) => void; title?: string; tooltipBarProps?: React.HTMLAttributes; total_rows?: number; uniqueid: string; values?: Array>; width?: number | string; } export interface GridlerRef { getGlideRef: () => DataEditorRef | undefined; getState: GridlerState['getState']; isEmpty: () => boolean; refresh: (parms?: any) => Promise; reload: (parms?: any) => Promise; reloadRow: (key: number | string) => Promise; scrollToRow: (key: number | string) => Promise; selectRow: (key: number | string) => Promise; setStateFN: GridlerState['setStateFN']; } export interface GridlerState { _active_requests?: Array<{ controller: AbortController; page: number }>; _activeTooltip?: ReactNode; _events: EventTarget; _glideref?: DataEditorRef; _gridSelection?: GridSelection; _gridSelectionRows?: GridSelection['rows']; _loadingList: CompactSelection; _page_data: Record>; _refresh: () => Promise; _scrollTimeout?: any | number; _visibleArea: Rectangle; _visiblePages: Rectangle; addError: (err: string, ...args: Array) => void; askAPIRowNumber?: (key: string) => Promise; colFilters?: Array; colOrder?: Record; colSize?: Record; colSort?: Array; data?: Array; errors: Array; focused?: boolean; get: () => GridlerState; getCellContent: (cell: Item) => GridCell; getCellsForSelection: ( selection: Rectangle, abortSignal: AbortSignal ) => CellArray | GetCellsThunk; getGridSelectedRows: () => Array; getRowBuffer: (row: number) => Record; getRowIndexByKey: (key: number | string) => Promise; getState: (key: K) => GridlerStoreState[K]; hasLocalData: boolean; isEmpty: boolean; isValuesInPages: () => boolean loadingData?: boolean; loadPage: (page: number, clearMode?: 'all' | 'page') => Promise; mounted: boolean; onCellActivated: (cell: Item) => void; onCellClicked: (cell: Item, event: CellClickedEventArgs) => void; onCellEdited: (cell: Item, newVal: EditableGridCell) => void; onColumnMoved: (from: number, to: number) => void; onColumnProposeMove: (startIndex: number, endIndex: number) => boolean; onColumnResize: ( column: GridColumn, newSize: number, colIndex: number, newSizeWithGrow: number ) => void; onContextClick: (area: string, event: CellClickedEventArgs, col?: number, row?: number) => void; onHeaderClicked: (colIndex: number, event: HeaderClickedEventArgs) => void; onHeaderMenuClick: (col: number, screenPosition: Rectangle) => void; onItemHovered: (args: GridMouseEventArgs) => void; onVisibleRegionChanged: ( r: Rectangle, tx: number, ty: number, extras: { freezeRegion?: Rectangle; freezeRegions?: readonly Rectangle[]; selected?: Item; } ) => void; pageSize: number; ready: boolean; refreshCells: (fromRow?: number, toRow?: number, col?: number) => void; reload?: () => Promise; renderColumns?: GridlerColumns; setState: ( key: K, value: GridlerStoreState[K] ) => void; setStateFN: ( key: K, value: (current: GridlerStoreState[K]) => Partial ) => Promise; toCell: >(row: TRowType, col: number) => GridCell; useAPIQuery?: (index: number) => Promise>>; } export type GridlerStoreState = GridlerProps & GridlerState; export type SortOption = { direction: 'asc' | 'desc'; id: string; order?: number }; const { Provider, useStore: useGridlerStore } = createSyncStore( (set, get) => ({ _events: new EventTarget(), _loadingList: CompactSelection.empty(), _page_data: {}, _refresh: async () => { const s = get(); await s.loadPage(0, 'all'); await s.refreshCells(); await s.reload?.(); }, _visibleArea: { height: 10000, width: 1000, x: 0, y: 0 }, _visiblePages: { height: 0, width: 0, x: 0, y: 0 }, addError: (err: string, ...args: Array) => { const s = get(); console.log('Gridler Error', s.uniqueid, err, args); set( produce((state: GridlerStoreState) => { state.errors = [...state.errors, err]; }) ); }, errors: [], get: () => get(), getCellContent: (cell: Item) => { const state = get(); const [col, row] = cell; const buffer = state.getRowBuffer(row) as Record | undefined; if (buffer !== undefined) { return state.toCell(buffer, col); } return { allowOverlay: false, kind: GridCellKind.Loading, }; }, getCellsForSelection: (selection: Rectangle, _abortSignal: AbortSignal) => { return async () => { const state = get(); //const firstPage = Math.max(0, Math.floor(selection.y / state.pageSize)); //const lastPage = Math.floor((selection.y + selection.height) / state.pageSize); await state.setStateFN('_visibleArea', (_cv) => { //if (r.x === cv.x && r.y === cv.y && r.width === cv.width && r.height === cv.height) // return cv; return selection; }); const result: GridCell[][] = []; for (let y = selection.y; y < selection.y + selection.height; y++) { const row: GridCell[] = []; for (let x = selection.x; x < selection.x + selection.width; x++) { row.push(state.getCellContent([x, y])); } result.push(row); } //console.log('Gridler:Debug:getCellsForSelection', selection, result); return result as CellArray; }; }, getGridSelectedRows: () => { const state = get(); const buffers: Array = []; const page_data = state._page_data; const pageSize = state.pageSize; if (state._gridSelectionRows) { for (const range of state._gridSelectionRows) { let buffer = undefined; for (const p in page_data) { for (const r in page_data[p]) { const idx = Number(p) * pageSize + Number(r); if (isNaN(idx)) { continue; } if (Number(page_data[p][r]?._rownumber) === range + 1) { buffer = page_data[p][r]; //console.log('Found row', range, idx, page_data[p][r]?._rownumber); break; } else if (idx === range + 1) { buffer = page_data[p][r]; //console.log('Found row 2', range, idx, page_data[p][r]?._rownumber); break; } } } if (buffer !== undefined) { buffers.push(buffer); } } } return buffers; }, getRowBuffer: (row: number) => { const state = get(); //Handle local data if (state.data && state.data.length > 0) { if (state.data[row] === undefined) { return { allowOverlay: false, kind: GridCellKind.Loading, }; } return state.data[row]; } //Handle remote paged data const firstPage = Math.max(0, Math.floor(row / state.pageSize)); const upperPage = state.pageSize * firstPage; const index = row - upperPage; const rowData = state._page_data?.[firstPage]?.[index]; return rowData; }, getRowIndexByKey: async (key: number | string) => { const state = get(); let rowIndex = -1; if (state.ready) { const page_data = state._page_data; const pageSize = state.pageSize; const keyField = state.keyField ?? 'id'; for (const p in page_data) { for (const r in page_data[p]) { const idx = Number(p) * pageSize + Number(r); //console.log('Found row', idx, page_data[p][r]?.[keyField], scrollToRowKey); if (String(page_data[p][r]?.[keyField]) === String(key)) { rowIndex = page_data[p][r]?._rownumber > 0 ? page_data[p][r]?._rownumber : idx > 0 ? idx : -1; break; } } if (rowIndex > 0) { console.log('Local row index', rowIndex, key); return rowIndex; } } if (rowIndex > 0) { return rowIndex; } else if (typeof state.askAPIRowNumber === 'function') { const rn = await state.askAPIRowNumber(String(key)); if (rn && rn >= 0) { console.log('Remote row index', rowIndex, key); return rn; } } } return undefined; }, getState: (key) => { return get()[key]; }, hasLocalData: false, isEmpty: true, isValuesInPages: () => { const state = get(); if (state.values && Object.keys(state._page_data).length > 0) { let found = false; for (const page in state._page_data) { const pageData = state._page_data[Number(page)]; for (const row of pageData) { const keyField = state.keyField ?? 'id'; const rowKey = row?.[keyField]; if (rowKey !== undefined) { const match = state.values.find((v) => String(v?.[keyField]) === String(rowKey)); if (match) { found = true; break; } } } if (found) { return true; } } } return false }, keyField: 'id', loadPage: async (pPage: number, clearMode?: 'all' | 'page') => { const state = get(); const page = pPage < 0 ? 0 : pPage; const result = state._events.dispatchEvent( new CustomEvent('before_loadPage', { detail: { clearMode, page: pPage, state }, }) ); if (!result) { return; } const damageList: { cell: [number, number] }[] = []; const colLen = Object.keys(state.renderColumns ?? [1, 2, 3]).length; const upperPage = state.pageSize * page; const hasPage = state._page_data?.[page]?.length > 0; //console.log('loadPage', page, clearMode, { upperPage, hasPage, pz: state.pageSize, colLen }); if (clearMode === 'all') { state._active_requests?.forEach((r) => { r.controller?.abort?.(); }); state.setState('_page_data', {}); state.setState('_active_requests', []); state.loadPage(page); return; } if (!state.useAPIQuery) { console.warn('No useAPIQuery function defined, cannot load page', page); return; } if (!hasPage && clearMode !== 'page') { state .useAPIQuery?.(page) .then((data) => { state.setStateFN('_page_data', (cv) => { return { ...cv, [page]: data }; }); for (let row = page; row <= upperPage + state.pageSize; row++) { for (let col = 0; col <= colLen; col++) { damageList.push({ cell: [col, row], }); } } state._glideref?.updateCells(damageList); state._events.dispatchEvent( new CustomEvent('loadPage', { detail: { clearMode, data, page: pPage, state }, }) ); }) .catch((e) => { console.error('loadPage Error: ', page, e); state._events.dispatchEvent( new CustomEvent('loadPage_error', { detail: { clearMode, error: e, page: pPage, state }, }) ); }); } }, maxConcurrency: 1, mounted: false, onCellActivated: (cell: Item): void => { const state = get(); const [col, row] = cell; state._events.dispatchEvent( new CustomEvent('onCellActivated', { detail: { cell, col, row, state }, }) ); state.glideProps?.onCellActivated?.(cell); }, onCellClicked: (cell: Item, event: CellClickedEventArgs) => { const state = get(); const [col, row] = cell; if (state.glideProps?.onCellClicked) { state.glideProps?.onCellClicked?.(cell, event); } if (state.values?.length) { if (state.onChange) { state.onChange(state.values); } } state._events.dispatchEvent( new CustomEvent('onCellClicked', { detail: { cell, col, row, state }, }) ); }, onCellEdited: (cell: Item, newVal: EditableGridCell) => { const state = get(); const [, row] = cell; //const current = state._editData?.[row]; //if (current === undefined) return; //todo: complete state._events.dispatchEvent( new CustomEvent('onCellEdited', { detail: { cell, newVal, row, state }, }) ); state.glideProps?.onCellEdited?.(cell, newVal); }, onColumnMoved: (from: number, to: number) => { const s = get(); const fromItem = s.renderColumns?.[from]; const toItem = s.renderColumns?.[to]; if (fromItem?.disableMove || toItem?.disableMove) { return; } s.setStateFN('colOrder', (cols) => { const renderCols = cols ?? s.renderColumns ?.map((col, i) => [col.id, i]) .reduce((acc, [id, i]) => ({ ...acc, [id]: i }), {}); if (!fromItem?.id || !toItem?.id) { return renderCols; } return { ...renderCols, [fromItem?.id]: to, [toItem?.id]: from }; }); }, onColumnProposeMove: (startIndex: number, endIndex: number) => { const s = get(); const fromItem = s.renderColumns?.[startIndex]; const toItem = s.renderColumns?.[endIndex]; if (fromItem?.disableMove || toItem?.disableMove) { return false; } return true; }, onColumnResize: ( column: GridColumn, newSize: number, _colIndex: number, _newSizeWithGrow: number ) => { const s = get(); const col = s.renderColumns?.find((col) => col.id === column.id); if (col?.disableResize) { return; } if (col?.maxWidth && newSize < col?.maxWidth) { return; } if (col) { s.setStateFN('colSize', (cols) => { if (cols && col && cols[col.id] === newSize) { return cols; } return { ...cols, [col.id]: newSize }; }); } }, onContextClick: (area: string, event: CellClickedEventArgs, col?: number, row?: number) => { const s = get(); const coldef = s.renderColumns?.[col ?? -1]; const items = area === 'menu' ? [{ leftSection: , title: s.title ?? 'Grid' }] : coldef ? [ { leftSection: , title: s.title ?? 'Grid' }, { items: [ { label: 'Sort Ascending', leftSection: , onClick: () => { s.setStateFN('colSort', (c) => { const cols = [...(c ?? [])]; const idx = cols.findIndex((search) => search.id === coldef.id); const dir = 'asc'; if (idx < 0) { const newSort: SortOption = { direction: dir, id: coldef.id, order: cols?.length, }; cols.push(newSort); } else if (idx >= 0) { cols[idx].direction = dir; } return cols; }); }, }, { label: 'Sort Descending', leftSection: , onClick: () => { s.setStateFN('colSort', (c) => { const cols = [...(c ?? [])]; const idx = cols.findIndex((search) => search.id === coldef.id); const dir = 'desc'; if (idx < 0) { const newSort: SortOption = { direction: dir, id: coldef.id, order: cols?.length, }; cols.push(newSort); } else if (idx >= 0) { cols[idx].direction = dir; } return cols; }); }, }, { label: `Filter ${coldef?.title ?? coldef?.id}`, }, { renderer: , }, ], label: `Column Settings for ${coldef?.title ?? coldef?.id}`, leftSection: , }, ] : []; s.hideMenu?.(area); s.showMenu?.(area, { items: coldef?.getMenuItems?.( area, s, col && row ? s.getRowBuffer(row) : undefined, coldef, items ) ?? s.getMenuItems?.(area, s, col && row ? s.getRowBuffer(row) : undefined, coldef, items) ?? items, //@ts-expect-error Check bounds x: event.clientX ?? event.bounds?.x, //@ts-expect-error Check bounds y: event.clientY ?? event.bounds?.y, }); }, onHeaderClicked: (colIndex: number, event: HeaderClickedEventArgs) => { const s = get(); event.preventDefault(); const col = s.renderColumns?.[colIndex]; if (!col) { return; } if (!col.disableSort) { s.setStateFN('colSort', (c) => { const cols = [...(c ?? [])]; const idx = cols.findIndex((search) => search.id === col.id); if (idx < 0) { const newSort: SortOption = { direction: 'asc', id: col.id, order: cols?.length }; cols.push(newSort); } else if (idx >= 0 && cols[idx].direction === 'asc') { cols[idx].direction = 'desc'; } else if (idx >= 0 && cols[idx].direction === 'desc') { cols.splice(idx, 1); } return cols; }); } }, onHeaderMenuClick: (col: number, screenPosition: Rectangle) => { const s = get(); const coldef = s.renderColumns?.[col]; if (!coldef) { return; } const sortItems = [ { label: `Sort ${coldef?.title ?? coldef?.id}`, }, { label: 'Sort Ascending', leftSection: , onClick: () => { s.setStateFN('colSort', (c) => { const cols = [...(c ?? [])]; const idx = cols.findIndex((search) => search.id === coldef.id); const dir = 'asc'; if (idx < 0) { const newSort: SortOption = { direction: dir, id: coldef.id, order: cols?.length }; cols.push(newSort); } else if (idx >= 0) { cols[idx].direction = dir; } return cols; }); }, }, { label: 'Sort Descending', leftSection: , onClick: () => { s.setStateFN('colSort', (c) => { const cols = [...(c ?? [])]; const idx = cols.findIndex((search) => search.id === coldef.id); const dir = 'desc'; if (idx < 0) { const newSort: SortOption = { direction: dir, id: coldef.id, order: cols?.length }; cols.push(newSort); } else if (idx >= 0) { cols[idx].direction = dir; } return cols; }); }, }, { isDivider: true, }, ]; const items = [ ...(coldef.disableSort ? [] : sortItems), { label: `Filter ${coldef?.title ?? coldef?.id}`, }, { renderer: , }, { isDivider: true, }, { id: 'refesh', label: `Refresh`, onClick: () => { const s = get(); s._refresh?.(); }, }, ]; s.hideMenu?.('header-menu'); s.showMenu?.('header-menu', { items: coldef?.getMenuItems?.('header-menu', s, undefined, coldef, items) ?? s.getMenuItems?.('header-menu', s, undefined, coldef, items), x: screenPosition.x, y: screenPosition.y, }); }, onItemHovered: (args: GridMouseEventArgs) => { const s = get(); s.setState('_activeTooltip', undefined); if (args.kind === 'cell') { //_activeTooltip const coldef = s.renderColumns?.[args.location[0]]; if (coldef?.tooltip && typeof coldef?.tooltip === 'string') { s.setState('_activeTooltip', coldef?.tooltip); } else if (coldef?.tooltip && typeof coldef?.tooltip === 'function') { const buffer = s.getRowBuffer(args.location[1]); s.setState('_activeTooltip', coldef?.tooltip(buffer, args.location[1], args.location[0])); } } }, onVisibleRegionChanged: ( region: Rectangle, _tx: number, _ty: number, _extras: { freezeRegion?: Rectangle; freezeRegions?: readonly Rectangle[]; selected?: Item; } ) => { const state = get(); if (state._scrollTimeout) { clearTimeout(state._scrollTimeout); } const onVisibleRegionChangedDebounced = () => { //console.log('Gridler:Debug:VisibleRegionChanged', r); const state = get(); const firstPage = Math.max(0, Math.floor(region.y / state.pageSize)); // const lastPage = Math.floor((region.y + region.height) / state.pageSize); // const upperPage = state.pageSize * firstPage; const previousPage = Math.max(0, Math.floor(state._visiblePages.y / state.pageSize)); const pageDif = firstPage - previousPage; // console.log( // 'Page dif', // pageDif, // { previousPage, firstPage, lastPage, onder: pageDif - 1 === lastPage }, // region // ); // if (pageDif - 1 === lastPage) { // console.log('Is onder', lastPage, pageDif); // } if (state.progressiveScroll && pageDif > 1) { const state = get(); const newpos = (previousPage + 2) * state.pageSize * (state.rowHeight ?? 22); // console.log('Pending scroll exec', newpos, { // pageSize: state.pageSize, // rowHeight: state.rowHeight // }); state._glideref?.scrollTo( 0, { amount: newpos, unit: 'px', }, 'vertical' ); return; } //console.log('VIsi change', { firstPage, lastPage }, state?._page_data[lastPage]); // if (state._active_requests && state._active_requests.filter((f)=>f.page > firstPage - 1)) { // } //console.log('Gridler:Debug:VisibleRegionChanged', region, firstPage, lastPage); state.setState('_visiblePages', region); }; state.setState( '_scrollTimeout', setTimeout(() => { onVisibleRegionChangedDebounced(); }, 100) ); }, pageSize: 50, ready: false, refreshCells: (fromRow?: number, toRow?: number, col?: number) => { const state = get(); const damageList: { cell: [number, number] }[] = []; const colLen = Object.keys(state.renderColumns ?? [1, 2, 3]).length; const from = fromRow && fromRow > 0 ? fromRow : 0; const to = toRow && toRow >= from ? toRow : from + state.pageSize; for (let row = from; row <= to; row++) { if (col && col > 0) { damageList.push({ cell: [col, row], }); } else { for (let c = 0; c <= colLen; c++) { damageList.push({ cell: [c, row], }); } } } state._glideref?.updateCells(damageList); }, setState: (key, value) => { set( produce((state) => { state[key] = value; }) ); }, setStateFN: (key, value) => { const p = new Promise((resolve, reject) => { set( produce((state) => { if (typeof value === 'function') { state[key] = (value as (value: unknown) => unknown)(state[key]); } else { reject(new Error(`Not a function ${value}`)); throw Error(`Not a function ${value}`); } }) ); resolve(); }); return p; }, toCell: >(row: T, col: number): GridCell => { const s = get(); const columns = s.renderColumns; const coldef: GridlerColumn | undefined = columns?.[col]; const ref: string = coldef?.id ?? coldef?.title ?? String(col); if (ref === undefined || ref === null || row === undefined || row === null) { if (coldef?.Cell) { return coldef?.Cell(row, col, ref, undefined, s) as GridCell; } if (s.RenderCell) { return s.RenderCell(row, col, ref, undefined, s); } return { allowOverlay: false, kind: GridCellKind.Loading, }; } try { const val = String(ref).includes('.') ? (getNestedValue(ref, row) ?? '') : row?.[ref]; if (coldef?.Cell) { return { kind: GridCellKind.Text, ...coldef?.Cell(row, col, ref, val, s) } as GridCell; } if (s.RenderCell) { return s.RenderCell(row, col, ref, val, s); } return { allowOverlay: true, data: val ?? '', displayData: String(val ?? ''), kind: GridCellKind.Text, }; } catch (e) { if (s.RenderCell) { return s.RenderCell(row, col, ref, row?.[ref ?? ''], s); } return { allowOverlay: false, kind: GridCellKind.Loading, skeletonWidthVariability: 50, }; } }, total_rows: 1000, uniqueid: getUUID() }), (props) => { const [setState, getState] = props.useStore((s) => [s.setState, s.getState]); const menus = useMantineBetterMenus(); useEffect(() => { const onMounted = getState('onMounted'); if (typeof onMounted === 'function') { onMounted(getState, setState); } setState('mounted', true); if (window && window.document) { const portalElement = window.document.getElementById('portal'); if (!portalElement) { const div = window.document.createElement('div'); div.id = 'portal'; div.setAttribute('data-gridler-portal', props.uniqueid); window.document.body.appendChild(div); } } getState('_events').dispatchEvent( new CustomEvent('mounted', { detail: {}, }) ); return () => { const onUnMounted = getState('onUnMounted'); setState('mounted', false); getState('_events').dispatchEvent( new CustomEvent('unmounted', { detail: {}, }) ); if (typeof onUnMounted === 'function') { onUnMounted(); } }; }, [setState, getState]); getState('_events').addEventListener('reload', (_e: Event) => { getState('_refresh')?.(); }); return { ...props, colSort: props.defaultSort ?? getState('colSort') ?? [], hideMenu: props.hideMenu ?? menus.hide, scrollToRowKey: props.scrollToRowKey ?? props.selectedRowKey ?? getState('scrollToRowKey'), showMenu: props.showMenu ?? menus.show, total_rows: getState('total_rows') ?? props.total_rows, }; } ); export type useStoreReturnType = ReturnType; export { Provider, useGridlerStore };