Files
oranguru/src/Griddy/core/GriddyStore.ts
2026-02-12 22:02:39 +02:00

104 lines
3.9 KiB
TypeScript

import type { Table } from '@tanstack/react-table'
import type { ColumnFiltersState, RowSelectionState, SortingState } from '@tanstack/react-table'
import type { Virtualizer } from '@tanstack/react-virtual'
import { createSyncStore } from '@warkypublic/zustandsyncstore'
import type { DataAdapter, GriddyColumn, GriddyProps, GriddyUIState, GroupingConfig, PaginationConfig, SearchConfig, SelectionConfig } from './types'
// ─── Store State ─────────────────────────────────────────────────────────────
/**
* Full store state: UI state + synced props + internal references.
* Props from GriddyProps are synced automatically via createSyncStore's $sync.
* Fields from GriddyProps must be declared here so TypeScript can see them.
*/
export interface GriddyStoreState extends GriddyUIState {
_scrollRef: HTMLDivElement | null
// ─── Internal refs (set imperatively) ───
_table: null | Table<any>
_virtualizer: null | Virtualizer<HTMLDivElement, Element>
className?: string
columnFilters?: ColumnFiltersState
columns?: GriddyColumn<any>[]
data?: any[]
dataAdapter?: DataAdapter<any>
getRowId?: (row: any, index: number) => string
grouping?: GroupingConfig
height?: number | string
keyboardNavigation?: boolean
onColumnFiltersChange?: (filters: ColumnFiltersState) => void
onEditCommit?: (rowId: string, columnId: string, value: unknown) => Promise<void> | void
onRowSelectionChange?: (selection: RowSelectionState) => void
onSortingChange?: (sorting: SortingState) => void
overscan?: number
pagination?: PaginationConfig
persistenceKey?: string
rowHeight?: number
rowSelection?: RowSelectionState
search?: SearchConfig
selection?: SelectionConfig
setScrollRef: (el: HTMLDivElement | null) => void
// ─── Internal ref setters ───
setTable: (table: Table<any>) => void
setVirtualizer: (virtualizer: Virtualizer<HTMLDivElement, Element>) => void
sorting?: SortingState
// ─── Synced from GriddyProps (written by $sync) ───
uniqueId?: string
}
// ─── Create Store ────────────────────────────────────────────────────────────
export const { Provider: GriddyProvider, useStore: useGriddyStore } = createSyncStore<
GriddyStoreState,
GriddyProps<any>
>(
(set, get) => ({
_scrollRef: null,
// ─── Internal Refs ───
_table: null,
_virtualizer: null,
focusedColumnId: null,
// ─── Focus State ───
focusedRowIndex: null,
// ─── Mode State ───
isEditing: false,
isSearchOpen: false,
isSelecting: false,
moveFocus: (direction, amount) => {
const { focusedRowIndex, totalRows } = get()
const current = focusedRowIndex ?? 0
const delta = direction === 'down' ? amount : -amount
const next = Math.max(0, Math.min(current + delta, totalRows - 1))
set({ focusedRowIndex: next })
},
moveFocusToEnd: () => {
const { totalRows } = get()
set({ focusedRowIndex: Math.max(0, totalRows - 1) })
},
moveFocusToStart: () => set({ focusedRowIndex: 0 }),
setEditing: (editing) => set({ isEditing: editing }),
setFocusedColumn: (id) => set({ focusedColumnId: id }),
// ─── Actions ───
setFocusedRow: (index) => set({ focusedRowIndex: index }),
setScrollRef: (el) => set({ _scrollRef: el }),
setSearchOpen: (open) => set({ isSearchOpen: open }),
setSelecting: (selecting) => set({ isSelecting: selecting }),
// ─── Internal Ref Setters ───
setTable: (table) => set({ _table: table }),
setTotalRows: (count) => set({ totalRows: count }),
setVirtualizer: (virtualizer) => set({ _virtualizer: virtualizer }),
// ─── Row Count ───
totalRows: 0,
}),
)