@coveord/plasma-mantine 55.5.1 → 55.6.0

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.
Files changed (28) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/.turbo/turbo-test.log +49 -49
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cjs/components/table/index.d.ts +1 -1
  5. package/dist/cjs/components/table/index.d.ts.map +1 -1
  6. package/dist/cjs/components/table/index.js.map +1 -1
  7. package/dist/cjs/components/table/use-table.d.ts.map +1 -1
  8. package/dist/cjs/components/table/use-table.js +177 -162
  9. package/dist/cjs/components/table/use-table.js.map +1 -1
  10. package/dist/cjs/components/table/use-url-synced-state.d.ts +8 -4
  11. package/dist/cjs/components/table/use-url-synced-state.d.ts.map +1 -1
  12. package/dist/cjs/components/table/use-url-synced-state.js +94 -67
  13. package/dist/cjs/components/table/use-url-synced-state.js.map +1 -1
  14. package/dist/esm/components/table/index.d.ts +1 -1
  15. package/dist/esm/components/table/index.d.ts.map +1 -1
  16. package/dist/esm/components/table/index.js.map +1 -1
  17. package/dist/esm/components/table/use-table.d.ts.map +1 -1
  18. package/dist/esm/components/table/use-table.js +116 -95
  19. package/dist/esm/components/table/use-table.js.map +1 -1
  20. package/dist/esm/components/table/use-url-synced-state.d.ts +8 -4
  21. package/dist/esm/components/table/use-url-synced-state.d.ts.map +1 -1
  22. package/dist/esm/components/table/use-url-synced-state.js +59 -44
  23. package/dist/esm/components/table/use-url-synced-state.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/table/__tests__/use-url-synced-state.unit.spec.ts +2 -1
  26. package/src/components/table/index.ts +1 -1
  27. package/src/components/table/use-table.ts +109 -79
  28. package/src/components/table/use-url-synced-state.ts +79 -64
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/table/use-table.ts"],"sourcesContent":["import {useDidUpdate} from '@mantine/hooks';\nimport {type ExpandedState, type PaginationState, type SortingState} from '@tanstack/table-core';\nimport defaultsDeep from 'lodash.defaultsdeep';\nimport {Dispatch, SetStateAction, useCallback, useMemo, useState} from 'react';\nimport {type DateRangePickerValue} from '../date-range-picker';\nimport {useUrlSyncedState} from './use-url-synced-state';\n\n// Create a deeply optional version of another type\ntype DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\nexport interface TableState<TData = unknown> {\n /**\n * Current pagination state\n *\n * @default { pageIndex: 0, pageSize: 50 }\n */\n pagination: PaginationState;\n /**\n * Total number of entries in the table.\n * This number is used to calculate the number of pages in the pagination.\n * When null, the number of pages is calculated using the current data length.\n *\n * @default null\n */\n totalEntries: number | null;\n /**\n * Current sorting state\n *\n * @default []\n */\n sorting: SortingState;\n /**\n * Current global filter value\n *\n * @default ''\n */\n globalFilter: string;\n\n /**\n * Current expanded state\n *\n * @default {}\n */\n expanded: ExpandedState;\n /**\n * Predicates and their current value\n *\n * @default {}\n */\n predicates: Record<string, string>;\n /**\n * Layout currently selected. When null, the first layout is used.\n *\n * @default null\n */\n layout: string | null;\n /**\n * Currently selected date range\n *\n * @default [null, null]\n */\n dateRange: DateRangePickerValue;\n /**\n * Currently selected rows\n *\n * @default {}\n */\n rowSelection: Record<string, TData>;\n /**\n * Columns that are currently visible\n *\n * @default {}\n */\n columnVisibility: Record<string, boolean>;\n}\n\nexport interface TableStore<TData = unknown> {\n /**\n * Current state of the table.\n */\n state: TableState<TData>;\n /**\n * Allows to change the pagination state.\n */\n setPagination: Dispatch<SetStateAction<TableState<TData>['pagination']>>;\n /**\n * Allows to change the total number of entries.\n */\n setTotalEntries: Dispatch<SetStateAction<TableState<TData>['totalEntries']>>;\n /**\n * Allows to change the sorting state.\n */\n setSorting: Dispatch<SetStateAction<TableState<TData>['sorting']>>;\n /**\n * Allows to change the global filter value.\n */\n setGlobalFilter: Dispatch<SetStateAction<TableState<TData>['globalFilter']>>;\n /**\n * Allows to change the rows expanded state.\n */\n setExpanded: Dispatch<SetStateAction<TableState<TData>['expanded']>>;\n /**\n * Allows to change the predicates values.\n */\n setPredicates: Dispatch<SetStateAction<TableState<TData>['predicates']>>;\n /**\n * Allows to change the selected layout.\n */\n setLayout: Dispatch<SetStateAction<TableState<TData>['layout']>>;\n /**\n * Allows to change the selected date range.\n */\n setDateRange: Dispatch<SetStateAction<TableState<TData>['dateRange']>>;\n /**\n * Allows to change the current row selection.\n */\n setRowSelection: Dispatch<SetStateAction<TableState<TData>['rowSelection']>>;\n /**\n * Allows to change the visible columns.\n */\n setColumnVisibility: Dispatch<SetStateAction<TableState<TData>['columnVisibility']>>;\n /**\n * Whether the table is currently filtered.\n */\n isFiltered: boolean;\n /**\n * Whether the table has data when unfiltered.\n *\n * This is derived from the totalEntries so make sure you set that number correctly, even if you're using a client side table.\n */\n isVacant: boolean;\n /**\n * Clear currently applied filters.\n */\n clearFilters: () => void;\n /**\n * Deselects all currently selected rows.\n */\n clearRowSelection: () => void;\n /**\n * Get currently selected rows.\n */\n getSelectedRows: () => TData[];\n /**\n * Get currently selected row\n */\n getSelectedRow: () => TData | null;\n /**\n * Whether the user can select multiple rows at the same time.\n */\n multiRowSelectionEnabled: boolean;\n /**\n * Whether rows can be selected.\n */\n rowSelectionEnabled: boolean;\n /**\n * Whether row selection is forced.\n */\n rowSelectionForced: boolean;\n}\n\nexport interface UseTableOptions<TData = unknown> {\n /**\n * Initial state of the table.\n */\n initialState?: DeepPartial<TableState<TData>>;\n /**\n * Whether rows can be selected.\n *\n * @default true\n */\n enableRowSelection?: boolean;\n /**\n * Whether multiple rows can be selected at the same time.\n *\n * @default false\n */\n enableMultiRowSelection?: boolean;\n /**\n * Forces the user to always have one row selected.\n * When activating that setting, a good practice is to have a row already selected in the initial state.\n *\n * @default false\n */\n forceSelection?: boolean;\n /**\n * Whether to sync the table state with the URL.\n *\n * @default false\n */\n syncWithUrl?: boolean;\n}\n\nconst defaultOptions: UseTableOptions = {\n enableRowSelection: true,\n enableMultiRowSelection: false,\n forceSelection: false,\n syncWithUrl: false,\n};\n\nconst defaultState: Partial<TableState> = {\n pagination: {\n pageIndex: 0,\n pageSize: 50,\n },\n totalEntries: null,\n sorting: [],\n globalFilter: '',\n predicates: {},\n layout: null,\n dateRange: [null, null],\n rowSelection: {},\n columnVisibility: {},\n};\n\nexport const useTable = <TData>(userOptions: UseTableOptions<TData> = {}): TableStore<TData> => {\n const options = defaultsDeep({}, userOptions, defaultOptions) as UseTableOptions<TData>;\n const initialState = defaultsDeep({}, options.initialState, defaultState) as TableState<TData>;\n /**\n * The `useUrlSyncedState` hook defaults to synchronize, but the table wants to default to not synchronize,\n * so always pass the sync option as a resolved boolean value.\n */\n const sync = !!options.syncWithUrl;\n\n // synced with url\n const [pagination, setPagination] = useUrlSyncedState<TableState<TData>['pagination']>({\n initialState: initialState.pagination,\n serializer: ({pageIndex, pageSize}) => [\n ['page', (pageIndex + 1).toString()],\n ['pageSize', pageSize.toString()],\n ],\n deserializer: (params) =>\n defaultsDeep(\n {\n pageIndex: params.get('page') ? parseInt(params.get('page'), 10) - 1 : undefined,\n pageSize: params.get('pageSize') ? parseInt(params.get('pageSize'), 10) : undefined,\n },\n initialState.pagination,\n ),\n sync,\n });\n const [sorting, setSorting] = useUrlSyncedState<TableState<TData>['sorting']>({\n initialState: initialState.sorting,\n serializer: (_sorting) => [\n ['sortBy', _sorting.map(({id, desc}) => `${id}.${desc ? 'desc' : 'asc'}`).join(',')],\n ],\n deserializer: (params) => {\n if (!params.has('sortBy')) {\n return initialState.sorting;\n }\n const sorts = params.get('sortBy')?.split(',') ?? [];\n return sorts.map((sort) => {\n const [id, order] = sort.split('.');\n return {id, desc: order === 'desc'};\n });\n },\n sync,\n });\n const [globalFilter, setGlobalFilter] = useUrlSyncedState<TableState<TData>['globalFilter']>({\n initialState: initialState.globalFilter,\n serializer: (filter) => [['filter', filter]],\n deserializer: (params) => params.get('filter') ?? initialState.globalFilter,\n sync,\n });\n const [predicates, setPredicates] = useUrlSyncedState<TableState<TData>['predicates']>({\n initialState: initialState.predicates,\n serializer: (_predicates) => Object.entries(_predicates).map(([key, value]) => [key, value]),\n deserializer: (params) =>\n Object.keys(initialState.predicates).reduce(\n (acc, predicateKey) => {\n acc[predicateKey] = params.get(predicateKey) ?? initialState.predicates[predicateKey];\n return acc;\n },\n {} as TableState<TData>['predicates'],\n ),\n sync,\n });\n const [layout, setLayout] = useUrlSyncedState<TableState<TData>['layout']>({\n initialState: initialState.layout,\n serializer: (_layout) => [['layout', _layout]],\n deserializer: (params) => params.get('layout') ?? initialState.layout,\n sync,\n });\n const [dateRange, setDateRange] = useUrlSyncedState<TableState<TData>['dateRange']>({\n initialState: initialState.dateRange,\n serializer: ([from, to]) => [\n ['from', from?.toISOString() ?? ''],\n ['to', to?.toISOString() ?? ''],\n ],\n deserializer: (params) => [\n params.get('from') ? new Date(params.get('from') as string) : initialState.dateRange[0],\n params.get('to') ? new Date(params.get('to') as string) : initialState.dateRange[1],\n ],\n sync,\n });\n const [columnVisibility, setColumnVisibility] = useUrlSyncedState<TableState<TData>['columnVisibility']>({\n initialState: initialState.columnVisibility,\n serializer: (columns) => [\n [\n 'show',\n Object.entries(columns)\n .filter(([, visible]) => visible === true)\n .map(([columnName]) => columnName)\n .join(','),\n ],\n [\n 'hide',\n Object.entries(columns)\n .filter(([, visible]) => visible === false)\n .map(([columnName]) => columnName)\n .join(','),\n ],\n ],\n deserializer: (params) => {\n if (!params.has('show') && !params.has('hide')) {\n return initialState.columnVisibility;\n }\n const visible = params.get('show')?.split(',') ?? [];\n const invisible = params.get('hide')?.split(',') ?? [];\n const columns = {} as TableState<TData>['columnVisibility'];\n visible.forEach((column) => {\n columns[column] = true;\n });\n invisible.forEach((column) => {\n columns[column] = false;\n });\n return columns;\n },\n sync,\n });\n\n // unsynced\n const [totalEntries, _setTotalEntries] = useState<TableState<TData>['totalEntries']>(initialState.totalEntries);\n const [unfilteredTotalEntries, setUnfilteredTotalEntries] = useState<TableState<TData>['totalEntries']>(\n initialState.totalEntries,\n );\n const [expanded, setExpanded] = useState<TableState<TData>['expanded']>(initialState.expanded);\n const [rowSelection, setRowSelection] = useState<TableState<TData>['rowSelection']>(initialState.rowSelection);\n\n const isFiltered =\n !!globalFilter ||\n Object.keys(predicates).some((predicate) => !!predicates[predicate]) ||\n !!dateRange?.[0] ||\n !!dateRange?.[1];\n\n const isVacant = unfilteredTotalEntries === 0;\n\n const setTotalEntries: typeof _setTotalEntries = useCallback(\n (updater) => {\n _setTotalEntries((old) => {\n const newTotalEntries = updater instanceof Function ? updater(old) : updater;\n if (!isFiltered) {\n setUnfilteredTotalEntries(newTotalEntries);\n }\n return newTotalEntries;\n });\n },\n [isFiltered],\n );\n\n const clearFilters = useCallback(() => {\n setPredicates(initialState.predicates);\n setGlobalFilter('');\n }, []);\n\n const clearRowSelection = useCallback(() => {\n setRowSelection({});\n }, []);\n\n const getSelectedRows = useCallback(() => Object.values(rowSelection), [rowSelection]);\n\n const getSelectedRow = () => getSelectedRows()[0] ?? null;\n\n useDidUpdate(() => {\n if (!options.enableMultiRowSelection) {\n clearRowSelection();\n }\n }, [globalFilter, pagination, sorting, dateRange, predicates]);\n\n const state = useMemo(\n () => ({\n pagination,\n totalEntries,\n sorting,\n globalFilter,\n expanded,\n predicates,\n layout,\n dateRange,\n rowSelection,\n columnVisibility,\n }),\n [\n pagination,\n totalEntries,\n sorting,\n globalFilter,\n expanded,\n predicates,\n layout,\n dateRange,\n rowSelection,\n columnVisibility,\n ],\n );\n\n return {\n state,\n setPagination,\n setTotalEntries,\n setSorting,\n setGlobalFilter,\n setExpanded,\n setPredicates,\n setLayout,\n setDateRange,\n setRowSelection,\n setColumnVisibility,\n isFiltered,\n isVacant,\n clearFilters,\n clearRowSelection,\n getSelectedRows,\n getSelectedRow,\n rowSelectionEnabled: options.enableRowSelection,\n rowSelectionForced: options.forceSelection,\n multiRowSelectionEnabled: options.enableMultiRowSelection,\n };\n};\n"],"names":["useDidUpdate","defaultsDeep","useCallback","useMemo","useState","useUrlSyncedState","defaultOptions","enableRowSelection","enableMultiRowSelection","forceSelection","syncWithUrl","defaultState","pagination","pageIndex","pageSize","totalEntries","sorting","globalFilter","predicates","layout","dateRange","rowSelection","columnVisibility","useTable","userOptions","options","initialState","sync","setPagination","serializer","toString","deserializer","params","get","parseInt","undefined","setSorting","_sorting","map","id","desc","join","has","sorts","split","sort","order","setGlobalFilter","filter","setPredicates","_predicates","Object","entries","key","value","keys","reduce","acc","predicateKey","setLayout","_layout","setDateRange","from","to","toISOString","Date","setColumnVisibility","columns","visible","columnName","invisible","forEach","column","_setTotalEntries","unfilteredTotalEntries","setUnfilteredTotalEntries","expanded","setExpanded","setRowSelection","isFiltered","some","predicate","isVacant","setTotalEntries","updater","old","newTotalEntries","Function","clearFilters","clearRowSelection","getSelectedRows","values","getSelectedRow","state","rowSelectionEnabled","rowSelectionForced","multiRowSelectionEnabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/components/table/use-table.ts"],"sourcesContent":["import {useDidUpdate} from '@mantine/hooks';\nimport {type ExpandedState, type PaginationState, type SortingState} from '@tanstack/table-core';\nimport defaultsDeep from 'lodash.defaultsdeep';\nimport {Dispatch, SetStateAction, useCallback, useMemo, useState} from 'react';\nimport {type DateRangePickerValue} from '../date-range-picker';\nimport {useUrlSyncedState, UseUrlSyncedStateOptions} from './use-url-synced-state';\n\n// Create a deeply optional version of another type\ntype DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\nexport interface TableState<TData = unknown> {\n /**\n * Current pagination state\n *\n * @default { pageIndex: 0, pageSize: 50 }\n */\n pagination: PaginationState;\n /**\n * Total number of entries in the table.\n * This number is used to calculate the number of pages in the pagination.\n * When null, the number of pages is calculated using the current data length.\n *\n * @default null\n */\n totalEntries: number | null;\n /**\n * Current sorting state\n *\n * @default []\n */\n sorting: SortingState;\n /**\n * Current global filter value\n *\n * @default ''\n */\n globalFilter: string;\n\n /**\n * Current expanded state\n *\n * @default {}\n */\n expanded: ExpandedState;\n /**\n * Predicates and their current value\n *\n * @default {}\n */\n predicates: Record<string, string>;\n /**\n * Layout currently selected. When null, the first layout is used.\n *\n * @default null\n */\n layout: string | null;\n /**\n * Currently selected date range\n *\n * @default [null, null]\n */\n dateRange: DateRangePickerValue;\n /**\n * Currently selected rows\n *\n * @default {}\n */\n rowSelection: Record<string, TData>;\n /**\n * Columns that are currently visible\n *\n * @default {}\n */\n columnVisibility: Record<string, boolean>;\n}\n\nexport interface TableStore<TData = unknown> {\n /**\n * Current state of the table.\n */\n state: TableState<TData>;\n /**\n * Allows to change the pagination state.\n */\n setPagination: Dispatch<SetStateAction<TableState<TData>['pagination']>>;\n /**\n * Allows to change the total number of entries.\n */\n setTotalEntries: Dispatch<SetStateAction<TableState<TData>['totalEntries']>>;\n /**\n * Allows to change the sorting state.\n */\n setSorting: Dispatch<SetStateAction<TableState<TData>['sorting']>>;\n /**\n * Allows to change the global filter value.\n */\n setGlobalFilter: Dispatch<SetStateAction<TableState<TData>['globalFilter']>>;\n /**\n * Allows to change the rows expanded state.\n */\n setExpanded: Dispatch<SetStateAction<TableState<TData>['expanded']>>;\n /**\n * Allows to change the predicates values.\n */\n setPredicates: Dispatch<SetStateAction<TableState<TData>['predicates']>>;\n /**\n * Allows to change the selected layout.\n */\n setLayout: Dispatch<SetStateAction<TableState<TData>['layout']>>;\n /**\n * Allows to change the selected date range.\n */\n setDateRange: Dispatch<SetStateAction<TableState<TData>['dateRange']>>;\n /**\n * Allows to change the current row selection.\n */\n setRowSelection: Dispatch<SetStateAction<TableState<TData>['rowSelection']>>;\n /**\n * Allows to change the visible columns.\n */\n setColumnVisibility: Dispatch<SetStateAction<TableState<TData>['columnVisibility']>>;\n /**\n * Whether the table is currently filtered.\n */\n isFiltered: boolean;\n /**\n * Whether the table has data when unfiltered.\n *\n * This is derived from the totalEntries so make sure you set that number correctly, even if you're using a client side table.\n */\n isVacant: boolean;\n /**\n * Clear currently applied filters.\n */\n clearFilters: () => void;\n /**\n * Deselects all currently selected rows.\n */\n clearRowSelection: () => void;\n /**\n * Get currently selected rows.\n */\n getSelectedRows: () => TData[];\n /**\n * Get currently selected row\n */\n getSelectedRow: () => TData | null;\n /**\n * Whether the user can select multiple rows at the same time.\n */\n multiRowSelectionEnabled: boolean;\n /**\n * Whether rows can be selected.\n */\n rowSelectionEnabled: boolean;\n /**\n * Whether row selection is forced.\n */\n rowSelectionForced: boolean;\n}\n\nexport interface UseTableOptions<TData = unknown> {\n /**\n * Initial state of the table.\n */\n initialState?: DeepPartial<TableState<TData>>;\n /**\n * Whether rows can be selected.\n *\n * @default true\n */\n enableRowSelection?: boolean;\n /**\n * Whether multiple rows can be selected at the same time.\n *\n * @default false\n */\n enableMultiRowSelection?: boolean;\n /**\n * Forces the user to always have one row selected.\n * When activating that setting, a good practice is to have a row already selected in the initial state.\n *\n * @default false\n */\n forceSelection?: boolean;\n /**\n * Whether to sync the table state with the URL.\n *\n * @default false\n */\n syncWithUrl?: boolean;\n}\n\nconst defaultOptions: UseTableOptions = {\n enableRowSelection: true,\n enableMultiRowSelection: false,\n forceSelection: false,\n syncWithUrl: false,\n};\n\nconst defaultState: Partial<TableState> = {\n pagination: {\n pageIndex: 0,\n pageSize: 50,\n },\n totalEntries: null,\n sorting: [],\n globalFilter: '',\n predicates: {},\n layout: null,\n dateRange: [null, null],\n rowSelection: {},\n columnVisibility: {},\n};\n\nconst serialization = <K extends keyof TableState>(\n input: Pick<UseUrlSyncedStateOptions<TableState[K]>, 'serializer' | 'deserializer'>,\n) => Object.freeze(input);\n\nconst PAGINATION_SERIALIZATION = serialization<'pagination'>({\n serializer: ({pageIndex, pageSize}) => [\n ['page', (pageIndex + 1).toString()],\n ['pageSize', pageSize.toString()],\n ],\n deserializer: (params, initialState) =>\n defaultsDeep(\n {\n pageIndex: params.get('page') ? Math.max(1, parseInt(params.get('page'), 10)) - 1 : undefined,\n pageSize: params.get('pageSize') ? parseInt(params.get('pageSize'), 10) : undefined,\n },\n initialState,\n ),\n});\n\nconst SORTING_SERIALIZATION = serialization<'sorting'>({\n serializer: (sorting) => [['sortBy', sorting.map(({id, desc}) => `${id}.${desc ? 'desc' : 'asc'}`).join(',')]],\n deserializer: (params, initialState) => {\n if (!params.has('sortBy')) {\n return initialState;\n }\n const sorts = params.get('sortBy')?.split(',') ?? [];\n return sorts.map((sort) => {\n const [id, order] = sort.split('.');\n return {id, desc: order === 'desc'};\n });\n },\n});\n\nconst GLOBAL_FILTER_SERIALIZATION = serialization<'globalFilter'>({\n serializer: (filter) => [['filter', filter]],\n deserializer: (params, initialState) => params.get('filter') ?? initialState,\n});\n\nconst PREDICATES_SERIALIZATION = serialization<'predicates'>({\n serializer: (predicates) => Object.entries(predicates),\n deserializer: (params, initialState) =>\n Object.keys(initialState).reduce(\n (acc, predicateKey) => {\n acc[predicateKey] = params.get(predicateKey) ?? initialState[predicateKey];\n return acc;\n },\n {} as TableState['predicates'],\n ),\n});\n\nconst LAYOUT_SERIALIZATION = serialization<'layout'>({\n serializer: (_layout) => [['layout', _layout]],\n deserializer: (params, initialState) => params.get('layout') ?? initialState,\n});\n\nconst DATE_RANGE_SERIALIZATION = serialization<'dateRange'>({\n serializer: ([from, to]) => [\n ['from', from?.toISOString() ?? '', true],\n ['to', to?.toISOString() ?? '', true],\n ],\n deserializer: (params, initial) => [\n params.get('from') ? new Date(params.get('from') as string) : initial[0],\n params.get('to') ? new Date(params.get('to') as string) : initial[1],\n ],\n});\n\nconst COLUMN_VISIBILITY_SERIALIZATION = serialization<'columnVisibility'>({\n serializer: (columns) => [\n [\n 'show',\n Object.entries(columns)\n .filter(([, visible]) => visible === true)\n .map(([columnName]) => columnName)\n .join(','),\n ],\n [\n 'hide',\n Object.entries(columns)\n .filter(([, visible]) => visible === false)\n .map(([columnName]) => columnName)\n .join(','),\n ],\n ],\n deserializer: (params, initial) => {\n if (!params.has('show') && !params.has('hide')) {\n return initial;\n }\n const visible = params.get('show')?.split(',') ?? [];\n const invisible = params.get('hide')?.split(',') ?? [];\n const columns = {} as TableState['columnVisibility'];\n visible.forEach((column) => {\n columns[column] = true;\n });\n invisible.forEach((column) => {\n columns[column] = false;\n });\n return columns;\n },\n});\n\nexport const useTable = <TData>(userOptions: UseTableOptions<TData> = {}): TableStore<TData> => {\n const options = defaultsDeep({}, userOptions, defaultOptions) as UseTableOptions<TData>;\n const initialState = defaultsDeep({}, options.initialState, defaultState) as TableState<TData>;\n /**\n * The `useUrlSyncedState` hook defaults to synchronize, but the table wants to default to not synchronize,\n * so always pass the sync option as a resolved boolean value.\n */\n const sync = !!options.syncWithUrl;\n\n // (Optionally) synced with url\n const [pagination, setPagination] = useUrlSyncedState<TableState<TData>['pagination']>({\n ...PAGINATION_SERIALIZATION,\n initialState: initialState.pagination,\n sync,\n });\n const [sorting, setSorting] = useUrlSyncedState<TableState<TData>['sorting']>({\n ...SORTING_SERIALIZATION,\n initialState: initialState.sorting,\n sync,\n });\n const [globalFilter, setGlobalFilter] = useUrlSyncedState<TableState<TData>['globalFilter']>({\n ...GLOBAL_FILTER_SERIALIZATION,\n initialState: initialState.globalFilter,\n sync,\n });\n const [predicates, setPredicates] = useUrlSyncedState<TableState<TData>['predicates']>({\n ...PREDICATES_SERIALIZATION,\n initialState: initialState.predicates,\n sync,\n });\n const [layout, setLayout] = useUrlSyncedState<TableState<TData>['layout']>({\n ...LAYOUT_SERIALIZATION,\n initialState: initialState.layout,\n sync,\n });\n const [dateRange, setDateRange] = useUrlSyncedState<TableState<TData>['dateRange']>({\n ...DATE_RANGE_SERIALIZATION,\n initialState: initialState.dateRange,\n sync,\n });\n const [columnVisibility, setColumnVisibility] = useUrlSyncedState<TableState<TData>['columnVisibility']>({\n ...COLUMN_VISIBILITY_SERIALIZATION,\n initialState: initialState.columnVisibility,\n sync,\n });\n\n // unsynced\n const [totalEntries, _setTotalEntries] = useState<TableState<TData>['totalEntries']>(initialState.totalEntries);\n const [unfilteredTotalEntries, setUnfilteredTotalEntries] = useState<TableState<TData>['totalEntries']>(\n initialState.totalEntries,\n );\n const [expanded, setExpanded] = useState<TableState<TData>['expanded']>(initialState.expanded);\n const [rowSelection, setRowSelection] = useState<TableState<TData>['rowSelection']>(initialState.rowSelection);\n\n const isFiltered =\n !!globalFilter ||\n Object.keys(predicates).some((predicate) => !!predicates[predicate]) ||\n !!dateRange?.[0] ||\n !!dateRange?.[1];\n\n const isVacant = unfilteredTotalEntries === 0;\n\n const setTotalEntries: typeof _setTotalEntries = useCallback(\n (updater) => {\n _setTotalEntries((old) => {\n const newTotalEntries = updater instanceof Function ? updater(old) : updater;\n if (!isFiltered) {\n setUnfilteredTotalEntries(newTotalEntries);\n }\n return newTotalEntries;\n });\n },\n [isFiltered],\n );\n\n const clearFilters = useCallback(() => {\n setPredicates(initialState.predicates);\n setGlobalFilter('');\n }, []);\n\n const clearRowSelection = useCallback(() => {\n setRowSelection({});\n }, []);\n\n const getSelectedRows = useCallback(() => Object.values(rowSelection), [rowSelection]);\n\n const getSelectedRow = () => getSelectedRows()[0] ?? null;\n\n useDidUpdate(() => {\n if (!options.enableMultiRowSelection) {\n clearRowSelection();\n }\n }, [globalFilter, pagination, sorting, dateRange, predicates]);\n\n const state = useMemo(\n () => ({\n pagination,\n totalEntries,\n sorting,\n globalFilter,\n expanded,\n predicates,\n layout,\n dateRange,\n rowSelection,\n columnVisibility,\n }),\n [\n pagination,\n totalEntries,\n sorting,\n globalFilter,\n expanded,\n predicates,\n layout,\n dateRange,\n rowSelection,\n columnVisibility,\n ],\n );\n\n return {\n state,\n setPagination,\n setTotalEntries,\n setSorting,\n setGlobalFilter,\n setExpanded,\n setPredicates,\n setLayout,\n setDateRange,\n setRowSelection,\n setColumnVisibility,\n isFiltered,\n isVacant,\n clearFilters,\n clearRowSelection,\n getSelectedRows,\n getSelectedRow,\n rowSelectionEnabled: options.enableRowSelection,\n rowSelectionForced: options.forceSelection,\n multiRowSelectionEnabled: options.enableMultiRowSelection,\n };\n};\n"],"names":["useDidUpdate","defaultsDeep","useCallback","useMemo","useState","useUrlSyncedState","defaultOptions","enableRowSelection","enableMultiRowSelection","forceSelection","syncWithUrl","defaultState","pagination","pageIndex","pageSize","totalEntries","sorting","globalFilter","predicates","layout","dateRange","rowSelection","columnVisibility","serialization","input","Object","freeze","PAGINATION_SERIALIZATION","serializer","toString","deserializer","params","initialState","get","Math","max","parseInt","undefined","SORTING_SERIALIZATION","map","id","desc","join","has","sorts","split","sort","order","GLOBAL_FILTER_SERIALIZATION","filter","PREDICATES_SERIALIZATION","entries","keys","reduce","acc","predicateKey","LAYOUT_SERIALIZATION","_layout","DATE_RANGE_SERIALIZATION","from","to","toISOString","initial","Date","COLUMN_VISIBILITY_SERIALIZATION","columns","visible","columnName","invisible","forEach","column","useTable","userOptions","options","sync","setPagination","setSorting","setGlobalFilter","setPredicates","setLayout","setDateRange","setColumnVisibility","_setTotalEntries","unfilteredTotalEntries","setUnfilteredTotalEntries","expanded","setExpanded","setRowSelection","isFiltered","some","predicate","isVacant","setTotalEntries","updater","old","newTotalEntries","Function","clearFilters","clearRowSelection","getSelectedRows","values","getSelectedRow","state","rowSelectionEnabled","rowSelectionForced","multiRowSelectionEnabled"],"mappings":"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"}
@@ -1,15 +1,18 @@
1
1
  import { Dispatch, SetStateAction } from 'react';
2
2
  /**
3
- * A search param entry defines the key (index 0) and value (index 1) of a search parameter.
3
+ * A search param entry defines the encoded value of a search parameter as `[key, value, alwaysEmit?]`.
4
+ * The third entry is an optional boolean that defaults to `false`.
5
+ * Setting `alwaysEmit` to `true` means any non-nullish value is always written to the search params,
6
+ * even if it matches the initial value. It is also written on initialization.
4
7
  */
5
- export type SearchParamEntry = [string, string | null | undefined];
8
+ export type SearchParamEntry = [string, string | null | undefined, boolean?];
6
9
  export interface UseUrlSyncedStateOptions<T> {
7
10
  /**
8
11
  * The initial state to use, if there would be no search params to deserialize from.
9
12
  * These values are also treated as defaults, and if the current state matches the initialState,
10
13
  * no value will be written to the search params.
11
14
  */
12
- initialState: T extends object ? Readonly<T> : T;
15
+ initialState: T | (() => T);
13
16
  /**
14
17
  * The serializer function is used to determine how the state is translated to url search parameters.
15
18
  * Called each time the state changes.
@@ -26,10 +29,11 @@ export interface UseUrlSyncedStateOptions<T> {
26
29
  * May return a partial state, values that are not deserialed are taken from the `initialState`.
27
30
  * Called only once when initializing the state.
28
31
  * @param params All the search parameters of the current url.
32
+ * @param initialState The initialState, can be used to take defaults from.
29
33
  * @returns The initial state based on the current url.
30
34
  * @example (params) => params.get('filter') ?? '',
31
35
  */
32
- deserializer: (params: URLSearchParams) => T;
36
+ deserializer: (params: URLSearchParams, initialState: T) => T;
33
37
  /**
34
38
  * Whether the state should be synced with the url, defaults to `true`.
35
39
  * When set to `false`, the hook behaves just like a regular `useState` hook from react.
@@ -1 +1 @@
1
- {"version":3,"file":"use-url-synced-state.d.ts","sourceRoot":"","sources":["../../../../src/components/table/use-url-synced-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAoB,MAAM,OAAO,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AA8CnE,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACvC;;;;OAIG;IACH,YAAY,EAAE,CAAC,SAAS,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACjD;;;;;;;;;OASG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,CAAC,KAAK,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC1D;;;;;;;OAOG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,CAAC,CAAC;IAC7C;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,WAAW,wBAAwB,CAAC,CAAC,CAAC,8CAkCxE,CAAC"}
1
+ {"version":3,"file":"use-url-synced-state.d.ts","sourceRoot":"","sources":["../../../../src/components/table/use-url-synced-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAoB,MAAM,OAAO,CAAC;AAElE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AAyC7E,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACvC;;;;OAIG;IACH,YAAY,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5B;;;;;;;;;OASG;IACH,UAAU,EAAE,CAAC,UAAU,EAAE,CAAC,KAAK,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC1D;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC;IAC9D;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAKD,eAAO,MAAM,iBAAiB,GAAI,CAAC,WAAW,wBAAwB,CAAC,CAAC,CAAC,8CA+CxE,CAAC"}
@@ -1,19 +1,11 @@
1
1
  import { useMemo, useState } from 'react';
2
2
  /**
3
- * Iterates over the `SearchParamEntry` values, and applies them to `target`, optionally filtering values.
3
+ * Get the index of the ? in a URL that denotes the start of the "search".
4
+ * Performs a nested search for '#/', to detect hash router urls and take the params of the hash in that case.
4
5
  *
5
- * @param target The target to write values to, can be a Map (for the initial values) or `URLSearchParams`.
6
- * @param entries The entries to apply (as returned by the serializer).
7
- * @param filter Optional filter that allows to treat non-empty values as empty (e.g. to not set default values).
8
- */ const applyValues = (target, entries, filter)=>{
9
- for (const entry of entries){
10
- if (entry[1] && filter(entry)) {
11
- target.set(entry[0], entry[1]);
12
- } else {
13
- target.delete(entry[0]);
14
- }
15
- }
16
- };
6
+ * @param url The URL to search.
7
+ * @returns The location of the question mark, or `-1` if not found.
8
+ */ const indexOfSearch = (url)=>url.indexOf('?', url.indexOf('#/') + 1);
17
9
  /**
18
10
  * Read the **current** search params from `window.location`, with support for detecting React's HashRouter.
19
11
  * Also returns a method that will yield the href (string) value, after any changes made on the params object.
@@ -21,45 +13,68 @@ import { useMemo, useState } from 'react';
21
13
  * @returns The `URLSearchParams` instance, and a function that can be used to get an updated href.
22
14
  */ const getSearchParams = ()=>{
23
15
  const href = window.location.href;
24
- // Search for '#/' to detect hash router urls
25
- const searchStart = href.indexOf('?', href.indexOf('#/') + 1);
26
- const params = new URLSearchParams(searchStart < 0 ? '' : href.substring(searchStart));
27
- return [
28
- params,
29
- ()=>{
30
- let result = searchStart < 0 ? href : href.substring(0, searchStart);
31
- if (params.size > 0) {
32
- result = result.concat('?', params.toString());
33
- }
34
- return result;
35
- }
36
- ];
16
+ const searchStart = indexOfSearch(href);
17
+ return new URLSearchParams(searchStart < 0 ? undefined : href.substring(searchStart));
18
+ };
19
+ /**
20
+ * Apply the search params to the current location, using `replaceState` (no navigation history).
21
+ * Note that only parameters in the `params` argument will be set, any other current params will be removed.
22
+ *
23
+ * @param params The parameters to apply.
24
+ */ const applySearchParams = (params)=>{
25
+ const currentHref = window.location.href;
26
+ const index = indexOfSearch(currentHref);
27
+ let nextHref = index < 0 ? currentHref : currentHref.substring(0, index);
28
+ if (params.size > 0) {
29
+ nextHref = nextHref.concat('?', params.toString());
30
+ }
31
+ if (nextHref !== currentHref) {
32
+ window.history.replaceState(null, '', nextHref);
33
+ }
37
34
  };
35
+ const getInitialState = (options)=>options.initialState instanceof Function ? options.initialState() : options.initialState;
38
36
  export const useUrlSyncedState = (options)=>{
39
37
  const sync = options.sync !== false;
40
- const initialState = useMemo(()=>sync ? options.deserializer(getSearchParams()[0]) : options.initialState, [
41
- options.initialState,
42
- options.sync
43
- ]);
44
- const [state, setState] = useState(initialState);
45
- // Capture the initial state as a map, to compare values and not set them if they match.
38
+ const [state, setState] = useState(()=>{
39
+ const initialState = getInitialState(options);
40
+ return sync ? options.deserializer(getSearchParams(), initialState) : initialState;
41
+ });
42
+ // Capture the initial state as a map (first render only!), to compare values and see if they should be set to the params.
46
43
  const initialStateSerialized = useMemo(()=>{
47
- const v = new Map();
48
- applyValues(v, options.serializer(options.initialState), (_)=>true);
49
- return v;
50
- }, [
51
- initialState,
52
- options.serializer
53
- ]);
54
- const enhancedSetState = useMemo(()=>sync ? (updater)=>{
44
+ const stateMap = new Map();
45
+ let initialize = null;
46
+ for (const [key, value, alwaysEmit] of options.serializer(getInitialState(options))){
47
+ stateMap.set(key, value);
48
+ if (alwaysEmit && value) {
49
+ initialize ?? (initialize = getSearchParams());
50
+ initialize.set(key, value);
51
+ }
52
+ }
53
+ if (initialize) {
54
+ applySearchParams(initialize);
55
+ }
56
+ return stateMap;
57
+ }, []);
58
+ const enhancedSetState = useMemo(()=>{
59
+ if (!sync) {
60
+ return setState;
61
+ }
62
+ return (updater)=>{
55
63
  setState((old)=>{
56
- const [search, getUrl] = getSearchParams();
57
64
  const newValue = updater instanceof Function ? updater(old) : updater;
58
- applyValues(search, options.serializer(newValue), ([key, value])=>initialStateSerialized.get(key) !== value);
59
- window.history.replaceState(null, '', getUrl());
65
+ const search = getSearchParams();
66
+ for (const [key, value, alwaysEmit] of options.serializer(newValue)){
67
+ if (value && (alwaysEmit || !Object.is(initialStateSerialized.get(key), value))) {
68
+ search.set(key, value);
69
+ } else {
70
+ search.delete(key);
71
+ }
72
+ }
73
+ applySearchParams(search);
60
74
  return newValue;
61
75
  });
62
- } : setState, [
76
+ };
77
+ }, [
63
78
  sync
64
79
  ]);
65
80
  return [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/table/use-url-synced-state.ts"],"sourcesContent":["import {Dispatch, SetStateAction, useMemo, useState} from 'react';\n\n/**\n * A search param entry defines the key (index 0) and value (index 1) of a search parameter.\n */\nexport type SearchParamEntry = [string, string | null | undefined];\n\n/**\n * Iterates over the `SearchParamEntry` values, and applies them to `target`, optionally filtering values.\n *\n * @param target The target to write values to, can be a Map (for the initial values) or `URLSearchParams`.\n * @param entries The entries to apply (as returned by the serializer).\n * @param filter Optional filter that allows to treat non-empty values as empty (e.g. to not set default values).\n */\nconst applyValues = (\n target: Map<string, string> | URLSearchParams,\n entries: Iterable<SearchParamEntry>,\n filter: (entry: Readonly<SearchParamEntry>) => boolean,\n): void => {\n for (const entry of entries) {\n if (entry[1] && filter(entry)) {\n target.set(entry[0], entry[1]);\n } else {\n target.delete(entry[0]);\n }\n }\n};\n\n/**\n * Read the **current** search params from `window.location`, with support for detecting React's HashRouter.\n * Also returns a method that will yield the href (string) value, after any changes made on the params object.\n *\n * @returns The `URLSearchParams` instance, and a function that can be used to get an updated href.\n */\nconst getSearchParams = (): [URLSearchParams, () => string] => {\n const href = window.location.href;\n // Search for '#/' to detect hash router urls\n const searchStart = href.indexOf('?', href.indexOf('#/') + 1);\n const params = new URLSearchParams(searchStart < 0 ? '' : href.substring(searchStart));\n return [\n params,\n () => {\n let result = searchStart < 0 ? href : href.substring(0, searchStart);\n if (params.size > 0) {\n result = result.concat('?', params.toString());\n }\n return result;\n },\n ];\n};\n\nexport interface UseUrlSyncedStateOptions<T> {\n /**\n * The initial state to use, if there would be no search params to deserialize from.\n * These values are also treated as defaults, and if the current state matches the initialState,\n * no value will be written to the search params.\n */\n initialState: T extends object ? Readonly<T> : T;\n /**\n * The serializer function is used to determine how the state is translated to url search parameters.\n * Called each time the state changes.\n * Note that the serializer should always return entries for keys it controls, also if the current value is \"unset\" (`null` or empty).\n * This ensures params get removed from the search when they are being unset.\n *\n * @param stateValue The new state value to serialize.\n * @returns An iterable of `[key, value]` to set as url search parameters.\n * @example (filterValue) => [['filter', filterValue]] // ?filter=filterValue\n */\n serializer: (stateValue: T) => Iterable<SearchParamEntry>;\n /**\n * The deserializer function is used to determine how the url parameters influence the initial state.\n * May return a partial state, values that are not deserialed are taken from the `initialState`.\n * Called only once when initializing the state.\n * @param params All the search parameters of the current url.\n * @returns The initial state based on the current url.\n * @example (params) => params.get('filter') ?? '',\n */\n deserializer: (params: URLSearchParams) => T;\n /**\n * Whether the state should be synced with the url, defaults to `true`.\n * When set to `false`, the hook behaves just like a regular `useState` hook from react.\n */\n sync?: boolean;\n}\n\nexport const useUrlSyncedState = <T>(options: UseUrlSyncedStateOptions<T>) => {\n const sync = options.sync !== false;\n const initialState = useMemo(\n () => (sync ? options.deserializer(getSearchParams()[0]) : options.initialState),\n [options.initialState, options.sync],\n );\n const [state, setState] = useState<T>(initialState);\n // Capture the initial state as a map, to compare values and not set them if they match.\n const initialStateSerialized = useMemo(() => {\n const v = new Map<string, string>();\n applyValues(v, options.serializer(options.initialState), (_) => true);\n return v;\n }, [initialState, options.serializer]);\n const enhancedSetState = useMemo<Dispatch<SetStateAction<T>>>(\n () =>\n sync\n ? (updater: SetStateAction<T>) => {\n setState((old) => {\n const [search, getUrl] = getSearchParams();\n const newValue = updater instanceof Function ? updater(old) : updater;\n applyValues(\n search,\n options.serializer(newValue),\n ([key, value]) => initialStateSerialized.get(key) !== value,\n );\n window.history.replaceState(null, '', getUrl());\n return newValue;\n });\n }\n : setState,\n [sync],\n );\n\n return [state, enhancedSetState] as const;\n};\n"],"names":["useMemo","useState","applyValues","target","entries","filter","entry","set","delete","getSearchParams","href","window","location","searchStart","indexOf","params","URLSearchParams","substring","result","size","concat","toString","useUrlSyncedState","options","sync","initialState","deserializer","state","setState","initialStateSerialized","v","Map","serializer","_","enhancedSetState","updater","old","search","getUrl","newValue","Function","key","value","get","history","replaceState"],"mappings":"AAAA,SAAkCA,OAAO,EAAEC,QAAQ,QAAO,QAAQ;AAOlE;;;;;;CAMC,GACD,MAAMC,cAAc,CAChBC,QACAC,SACAC;IAEA,KAAK,MAAMC,SAASF,QAAS;QACzB,IAAIE,KAAK,CAAC,EAAE,IAAID,OAAOC,QAAQ;YAC3BH,OAAOI,GAAG,CAACD,KAAK,CAAC,EAAE,EAAEA,KAAK,CAAC,EAAE;QACjC,OAAO;YACHH,OAAOK,MAAM,CAACF,KAAK,CAAC,EAAE;QAC1B;IACJ;AACJ;AAEA;;;;;CAKC,GACD,MAAMG,kBAAkB;IACpB,MAAMC,OAAOC,OAAOC,QAAQ,CAACF,IAAI;IACjC,6CAA6C;IAC7C,MAAMG,cAAcH,KAAKI,OAAO,CAAC,KAAKJ,KAAKI,OAAO,CAAC,QAAQ;IAC3D,MAAMC,SAAS,IAAIC,gBAAgBH,cAAc,IAAI,KAAKH,KAAKO,SAAS,CAACJ;IACzE,OAAO;QACHE;QACA;YACI,IAAIG,SAASL,cAAc,IAAIH,OAAOA,KAAKO,SAAS,CAAC,GAAGJ;YACxD,IAAIE,OAAOI,IAAI,GAAG,GAAG;gBACjBD,SAASA,OAAOE,MAAM,CAAC,KAAKL,OAAOM,QAAQ;YAC/C;YACA,OAAOH;QACX;KACH;AACL;AAoCA,OAAO,MAAMI,oBAAoB,CAAIC;IACjC,MAAMC,OAAOD,QAAQC,IAAI,KAAK;IAC9B,MAAMC,eAAezB,QACjB,IAAOwB,OAAOD,QAAQG,YAAY,CAACjB,iBAAiB,CAAC,EAAE,IAAIc,QAAQE,YAAY,EAC/E;QAACF,QAAQE,YAAY;QAAEF,QAAQC,IAAI;KAAC;IAExC,MAAM,CAACG,OAAOC,SAAS,GAAG3B,SAAYwB;IACtC,wFAAwF;IACxF,MAAMI,yBAAyB7B,QAAQ;QACnC,MAAM8B,IAAI,IAAIC;QACd7B,YAAY4B,GAAGP,QAAQS,UAAU,CAACT,QAAQE,YAAY,GAAG,CAACQ,IAAM;QAChE,OAAOH;IACX,GAAG;QAACL;QAAcF,QAAQS,UAAU;KAAC;IACrC,MAAME,mBAAmBlC,QACrB,IACIwB,OACM,CAACW;YACGP,SAAS,CAACQ;gBACN,MAAM,CAACC,QAAQC,OAAO,GAAG7B;gBACzB,MAAM8B,WAAWJ,mBAAmBK,WAAWL,QAAQC,OAAOD;gBAC9DjC,YACImC,QACAd,QAAQS,UAAU,CAACO,WACnB,CAAC,CAACE,KAAKC,MAAM,GAAKb,uBAAuBc,GAAG,CAACF,SAASC;gBAE1D/B,OAAOiC,OAAO,CAACC,YAAY,CAAC,MAAM,IAAIP;gBACtC,OAAOC;YACX;QACJ,IACAX,UACV;QAACJ;KAAK;IAGV,OAAO;QAACG;QAAOO;KAAiB;AACpC,EAAE"}
1
+ {"version":3,"sources":["../../../../src/components/table/use-url-synced-state.ts"],"sourcesContent":["import {Dispatch, SetStateAction, useMemo, useState} from 'react';\n\n/**\n * A search param entry defines the encoded value of a search parameter as `[key, value, alwaysEmit?]`.\n * The third entry is an optional boolean that defaults to `false`.\n * Setting `alwaysEmit` to `true` means any non-nullish value is always written to the search params,\n * even if it matches the initial value. It is also written on initialization.\n */\nexport type SearchParamEntry = [string, string | null | undefined, boolean?];\n\n/**\n * Get the index of the ? in a URL that denotes the start of the \"search\".\n * Performs a nested search for '#/', to detect hash router urls and take the params of the hash in that case.\n *\n * @param url The URL to search.\n * @returns The location of the question mark, or `-1` if not found.\n */\nconst indexOfSearch = (url: string): number => url.indexOf('?', url.indexOf('#/') + 1);\n\n/**\n * Read the **current** search params from `window.location`, with support for detecting React's HashRouter.\n * Also returns a method that will yield the href (string) value, after any changes made on the params object.\n *\n * @returns The `URLSearchParams` instance, and a function that can be used to get an updated href.\n */\nconst getSearchParams = (): URLSearchParams => {\n const href = window.location.href;\n const searchStart = indexOfSearch(href);\n return new URLSearchParams(searchStart < 0 ? undefined : href.substring(searchStart));\n};\n\n/**\n * Apply the search params to the current location, using `replaceState` (no navigation history).\n * Note that only parameters in the `params` argument will be set, any other current params will be removed.\n *\n * @param params The parameters to apply.\n */\nconst applySearchParams = (params: URLSearchParams): void => {\n const currentHref = window.location.href;\n const index = indexOfSearch(currentHref);\n let nextHref = index < 0 ? currentHref : currentHref.substring(0, index);\n if (params.size > 0) {\n nextHref = nextHref.concat('?', params.toString());\n }\n if (nextHref !== currentHref) {\n window.history.replaceState(null, '', nextHref);\n }\n};\n\nexport interface UseUrlSyncedStateOptions<T> {\n /**\n * The initial state to use, if there would be no search params to deserialize from.\n * These values are also treated as defaults, and if the current state matches the initialState,\n * no value will be written to the search params.\n */\n initialState: T | (() => T);\n /**\n * The serializer function is used to determine how the state is translated to url search parameters.\n * Called each time the state changes.\n * Note that the serializer should always return entries for keys it controls, also if the current value is \"unset\" (`null` or empty).\n * This ensures params get removed from the search when they are being unset.\n *\n * @param stateValue The new state value to serialize.\n * @returns An iterable of `[key, value]` to set as url search parameters.\n * @example (filterValue) => [['filter', filterValue]] // ?filter=filterValue\n */\n serializer: (stateValue: T) => Iterable<SearchParamEntry>;\n /**\n * The deserializer function is used to determine how the url parameters influence the initial state.\n * May return a partial state, values that are not deserialed are taken from the `initialState`.\n * Called only once when initializing the state.\n * @param params All the search parameters of the current url.\n * @param initialState The initialState, can be used to take defaults from.\n * @returns The initial state based on the current url.\n * @example (params) => params.get('filter') ?? '',\n */\n deserializer: (params: URLSearchParams, initialState: T) => T;\n /**\n * Whether the state should be synced with the url, defaults to `true`.\n * When set to `false`, the hook behaves just like a regular `useState` hook from react.\n */\n sync?: boolean;\n}\n\nconst getInitialState = <T>(options: UseUrlSyncedStateOptions<T>): T =>\n options.initialState instanceof Function ? options.initialState() : options.initialState;\n\nexport const useUrlSyncedState = <T>(options: UseUrlSyncedStateOptions<T>) => {\n const sync = options.sync !== false;\n const [state, setState] = useState<T>(() => {\n const initialState = getInitialState(options);\n return sync ? options.deserializer(getSearchParams(), initialState) : initialState;\n });\n // Capture the initial state as a map (first render only!), to compare values and see if they should be set to the params.\n const initialStateSerialized = useMemo(() => {\n const stateMap = new Map<string, string>();\n let initialize: URLSearchParams | null = null;\n for (const [key, value, alwaysEmit] of options.serializer(getInitialState(options))) {\n stateMap.set(key, value);\n if (alwaysEmit && value) {\n initialize ??= getSearchParams();\n initialize.set(key, value);\n }\n }\n if (initialize) {\n applySearchParams(initialize);\n }\n return stateMap;\n }, []);\n\n const enhancedSetState = useMemo<Dispatch<SetStateAction<T>>>(() => {\n if (!sync) {\n return setState;\n }\n return (updater: SetStateAction<T>) => {\n setState((old) => {\n const newValue = updater instanceof Function ? updater(old) : updater;\n\n const search = getSearchParams();\n for (const [key, value, alwaysEmit] of options.serializer(newValue)) {\n if (value && (alwaysEmit || !Object.is(initialStateSerialized.get(key), value))) {\n search.set(key, value);\n } else {\n search.delete(key);\n }\n }\n applySearchParams(search);\n\n return newValue;\n });\n };\n }, [sync]);\n\n return [state, enhancedSetState] as const;\n};\n"],"names":["useMemo","useState","indexOfSearch","url","indexOf","getSearchParams","href","window","location","searchStart","URLSearchParams","undefined","substring","applySearchParams","params","currentHref","index","nextHref","size","concat","toString","history","replaceState","getInitialState","options","initialState","Function","useUrlSyncedState","sync","state","setState","deserializer","initialStateSerialized","stateMap","Map","initialize","key","value","alwaysEmit","serializer","set","enhancedSetState","updater","old","newValue","search","Object","is","get","delete"],"mappings":"AAAA,SAAkCA,OAAO,EAAEC,QAAQ,QAAO,QAAQ;AAUlE;;;;;;CAMC,GACD,MAAMC,gBAAgB,CAACC,MAAwBA,IAAIC,OAAO,CAAC,KAAKD,IAAIC,OAAO,CAAC,QAAQ;AAEpF;;;;;CAKC,GACD,MAAMC,kBAAkB;IACpB,MAAMC,OAAOC,OAAOC,QAAQ,CAACF,IAAI;IACjC,MAAMG,cAAcP,cAAcI;IAClC,OAAO,IAAII,gBAAgBD,cAAc,IAAIE,YAAYL,KAAKM,SAAS,CAACH;AAC5E;AAEA;;;;;CAKC,GACD,MAAMI,oBAAoB,CAACC;IACvB,MAAMC,cAAcR,OAAOC,QAAQ,CAACF,IAAI;IACxC,MAAMU,QAAQd,cAAca;IAC5B,IAAIE,WAAWD,QAAQ,IAAID,cAAcA,YAAYH,SAAS,CAAC,GAAGI;IAClE,IAAIF,OAAOI,IAAI,GAAG,GAAG;QACjBD,WAAWA,SAASE,MAAM,CAAC,KAAKL,OAAOM,QAAQ;IACnD;IACA,IAAIH,aAAaF,aAAa;QAC1BR,OAAOc,OAAO,CAACC,YAAY,CAAC,MAAM,IAAIL;IAC1C;AACJ;AAqCA,MAAMM,kBAAkB,CAAIC,UACxBA,QAAQC,YAAY,YAAYC,WAAWF,QAAQC,YAAY,KAAKD,QAAQC,YAAY;AAE5F,OAAO,MAAME,oBAAoB,CAAIH;IACjC,MAAMI,OAAOJ,QAAQI,IAAI,KAAK;IAC9B,MAAM,CAACC,OAAOC,SAAS,GAAG7B,SAAY;QAClC,MAAMwB,eAAeF,gBAAgBC;QACrC,OAAOI,OAAOJ,QAAQO,YAAY,CAAC1B,mBAAmBoB,gBAAgBA;IAC1E;IACA,0HAA0H;IAC1H,MAAMO,yBAAyBhC,QAAQ;QACnC,MAAMiC,WAAW,IAAIC;QACrB,IAAIC,aAAqC;QACzC,KAAK,MAAM,CAACC,KAAKC,OAAOC,WAAW,IAAId,QAAQe,UAAU,CAAChB,gBAAgBC,UAAW;YACjFS,SAASO,GAAG,CAACJ,KAAKC;YAClB,IAAIC,cAAcD,OAAO;gBACrBF,eAAAA,aAAe9B;gBACf8B,WAAWK,GAAG,CAACJ,KAAKC;YACxB;QACJ;QACA,IAAIF,YAAY;YACZtB,kBAAkBsB;QACtB;QACA,OAAOF;IACX,GAAG,EAAE;IAEL,MAAMQ,mBAAmBzC,QAAqC;QAC1D,IAAI,CAAC4B,MAAM;YACP,OAAOE;QACX;QACA,OAAO,CAACY;YACJZ,SAAS,CAACa;gBACN,MAAMC,WAAWF,mBAAmBhB,WAAWgB,QAAQC,OAAOD;gBAE9D,MAAMG,SAASxC;gBACf,KAAK,MAAM,CAAC+B,KAAKC,OAAOC,WAAW,IAAId,QAAQe,UAAU,CAACK,UAAW;oBACjE,IAAIP,SAAUC,CAAAA,cAAc,CAACQ,OAAOC,EAAE,CAACf,uBAAuBgB,GAAG,CAACZ,MAAMC,MAAK,GAAI;wBAC7EQ,OAAOL,GAAG,CAACJ,KAAKC;oBACpB,OAAO;wBACHQ,OAAOI,MAAM,CAACb;oBAClB;gBACJ;gBACAvB,kBAAkBgC;gBAElB,OAAOD;YACX;QACJ;IACJ,GAAG;QAAChB;KAAK;IAET,OAAO;QAACC;QAAOY;KAAiB;AACpC,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coveord/plasma-mantine",
3
- "version": "55.5.1",
3
+ "version": "55.6.0",
4
4
  "description": "A Plasma flavoured Mantine theme",
5
5
  "keywords": [
6
6
  "plasma",
@@ -43,7 +43,8 @@ describe('useUrlSyncedState', () => {
43
43
  useUrlSyncedState({
44
44
  initialState: true,
45
45
  serializer: (state) => [['key', state ? 'true' : 'false']],
46
- deserializer: (params) => params.get('key') === 'true',
46
+ deserializer: (params, initialState) =>
47
+ params.has('key') ? params.get('key') === 'true' : initialState,
47
48
  sync: true,
48
49
  }),
49
50
  );
@@ -4,4 +4,4 @@ export {type TablePredicateProps} from './table-predicate/TablePredicate';
4
4
  export {type TableAction, type TableLayout, type TableLayoutProps, type TableProps} from './Table.types';
5
5
  export {useTableContext} from './TableContext';
6
6
  export {useTable, type TableState, type TableStore, type UseTableOptions} from './use-table';
7
- export {useUrlSyncedState} from './use-url-synced-state';
7
+ export {useUrlSyncedState, type UseUrlSyncedStateOptions, type SearchParamEntry} from './use-url-synced-state';
@@ -3,7 +3,7 @@ import {type ExpandedState, type PaginationState, type SortingState} from '@tans
3
3
  import defaultsDeep from 'lodash.defaultsdeep';
4
4
  import {Dispatch, SetStateAction, useCallback, useMemo, useState} from 'react';
5
5
  import {type DateRangePickerValue} from '../date-range-picker';
6
- import {useUrlSyncedState} from './use-url-synced-state';
6
+ import {useUrlSyncedState, UseUrlSyncedStateOptions} from './use-url-synced-state';
7
7
 
8
8
  // Create a deeply optional version of another type
9
9
  type DeepPartial<T> = {
@@ -215,6 +215,106 @@ const defaultState: Partial<TableState> = {
215
215
  columnVisibility: {},
216
216
  };
217
217
 
218
+ const serialization = <K extends keyof TableState>(
219
+ input: Pick<UseUrlSyncedStateOptions<TableState[K]>, 'serializer' | 'deserializer'>,
220
+ ) => Object.freeze(input);
221
+
222
+ const PAGINATION_SERIALIZATION = serialization<'pagination'>({
223
+ serializer: ({pageIndex, pageSize}) => [
224
+ ['page', (pageIndex + 1).toString()],
225
+ ['pageSize', pageSize.toString()],
226
+ ],
227
+ deserializer: (params, initialState) =>
228
+ defaultsDeep(
229
+ {
230
+ pageIndex: params.get('page') ? Math.max(1, parseInt(params.get('page'), 10)) - 1 : undefined,
231
+ pageSize: params.get('pageSize') ? parseInt(params.get('pageSize'), 10) : undefined,
232
+ },
233
+ initialState,
234
+ ),
235
+ });
236
+
237
+ const SORTING_SERIALIZATION = serialization<'sorting'>({
238
+ serializer: (sorting) => [['sortBy', sorting.map(({id, desc}) => `${id}.${desc ? 'desc' : 'asc'}`).join(',')]],
239
+ deserializer: (params, initialState) => {
240
+ if (!params.has('sortBy')) {
241
+ return initialState;
242
+ }
243
+ const sorts = params.get('sortBy')?.split(',') ?? [];
244
+ return sorts.map((sort) => {
245
+ const [id, order] = sort.split('.');
246
+ return {id, desc: order === 'desc'};
247
+ });
248
+ },
249
+ });
250
+
251
+ const GLOBAL_FILTER_SERIALIZATION = serialization<'globalFilter'>({
252
+ serializer: (filter) => [['filter', filter]],
253
+ deserializer: (params, initialState) => params.get('filter') ?? initialState,
254
+ });
255
+
256
+ const PREDICATES_SERIALIZATION = serialization<'predicates'>({
257
+ serializer: (predicates) => Object.entries(predicates),
258
+ deserializer: (params, initialState) =>
259
+ Object.keys(initialState).reduce(
260
+ (acc, predicateKey) => {
261
+ acc[predicateKey] = params.get(predicateKey) ?? initialState[predicateKey];
262
+ return acc;
263
+ },
264
+ {} as TableState['predicates'],
265
+ ),
266
+ });
267
+
268
+ const LAYOUT_SERIALIZATION = serialization<'layout'>({
269
+ serializer: (_layout) => [['layout', _layout]],
270
+ deserializer: (params, initialState) => params.get('layout') ?? initialState,
271
+ });
272
+
273
+ const DATE_RANGE_SERIALIZATION = serialization<'dateRange'>({
274
+ serializer: ([from, to]) => [
275
+ ['from', from?.toISOString() ?? '', true],
276
+ ['to', to?.toISOString() ?? '', true],
277
+ ],
278
+ deserializer: (params, initial) => [
279
+ params.get('from') ? new Date(params.get('from') as string) : initial[0],
280
+ params.get('to') ? new Date(params.get('to') as string) : initial[1],
281
+ ],
282
+ });
283
+
284
+ const COLUMN_VISIBILITY_SERIALIZATION = serialization<'columnVisibility'>({
285
+ serializer: (columns) => [
286
+ [
287
+ 'show',
288
+ Object.entries(columns)
289
+ .filter(([, visible]) => visible === true)
290
+ .map(([columnName]) => columnName)
291
+ .join(','),
292
+ ],
293
+ [
294
+ 'hide',
295
+ Object.entries(columns)
296
+ .filter(([, visible]) => visible === false)
297
+ .map(([columnName]) => columnName)
298
+ .join(','),
299
+ ],
300
+ ],
301
+ deserializer: (params, initial) => {
302
+ if (!params.has('show') && !params.has('hide')) {
303
+ return initial;
304
+ }
305
+ const visible = params.get('show')?.split(',') ?? [];
306
+ const invisible = params.get('hide')?.split(',') ?? [];
307
+ const columns = {} as TableState['columnVisibility'];
308
+ visible.forEach((column) => {
309
+ columns[column] = true;
310
+ });
311
+ invisible.forEach((column) => {
312
+ columns[column] = false;
313
+ });
314
+ return columns;
315
+ },
316
+ });
317
+
218
318
  export const useTable = <TData>(userOptions: UseTableOptions<TData> = {}): TableStore<TData> => {
219
319
  const options = defaultsDeep({}, userOptions, defaultOptions) as UseTableOptions<TData>;
220
320
  const initialState = defaultsDeep({}, options.initialState, defaultState) as TableState<TData>;
@@ -224,110 +324,40 @@ export const useTable = <TData>(userOptions: UseTableOptions<TData> = {}): Table
224
324
  */
225
325
  const sync = !!options.syncWithUrl;
226
326
 
227
- // synced with url
327
+ // (Optionally) synced with url
228
328
  const [pagination, setPagination] = useUrlSyncedState<TableState<TData>['pagination']>({
329
+ ...PAGINATION_SERIALIZATION,
229
330
  initialState: initialState.pagination,
230
- serializer: ({pageIndex, pageSize}) => [
231
- ['page', (pageIndex + 1).toString()],
232
- ['pageSize', pageSize.toString()],
233
- ],
234
- deserializer: (params) =>
235
- defaultsDeep(
236
- {
237
- pageIndex: params.get('page') ? parseInt(params.get('page'), 10) - 1 : undefined,
238
- pageSize: params.get('pageSize') ? parseInt(params.get('pageSize'), 10) : undefined,
239
- },
240
- initialState.pagination,
241
- ),
242
331
  sync,
243
332
  });
244
333
  const [sorting, setSorting] = useUrlSyncedState<TableState<TData>['sorting']>({
334
+ ...SORTING_SERIALIZATION,
245
335
  initialState: initialState.sorting,
246
- serializer: (_sorting) => [
247
- ['sortBy', _sorting.map(({id, desc}) => `${id}.${desc ? 'desc' : 'asc'}`).join(',')],
248
- ],
249
- deserializer: (params) => {
250
- if (!params.has('sortBy')) {
251
- return initialState.sorting;
252
- }
253
- const sorts = params.get('sortBy')?.split(',') ?? [];
254
- return sorts.map((sort) => {
255
- const [id, order] = sort.split('.');
256
- return {id, desc: order === 'desc'};
257
- });
258
- },
259
336
  sync,
260
337
  });
261
338
  const [globalFilter, setGlobalFilter] = useUrlSyncedState<TableState<TData>['globalFilter']>({
339
+ ...GLOBAL_FILTER_SERIALIZATION,
262
340
  initialState: initialState.globalFilter,
263
- serializer: (filter) => [['filter', filter]],
264
- deserializer: (params) => params.get('filter') ?? initialState.globalFilter,
265
341
  sync,
266
342
  });
267
343
  const [predicates, setPredicates] = useUrlSyncedState<TableState<TData>['predicates']>({
344
+ ...PREDICATES_SERIALIZATION,
268
345
  initialState: initialState.predicates,
269
- serializer: (_predicates) => Object.entries(_predicates).map(([key, value]) => [key, value]),
270
- deserializer: (params) =>
271
- Object.keys(initialState.predicates).reduce(
272
- (acc, predicateKey) => {
273
- acc[predicateKey] = params.get(predicateKey) ?? initialState.predicates[predicateKey];
274
- return acc;
275
- },
276
- {} as TableState<TData>['predicates'],
277
- ),
278
346
  sync,
279
347
  });
280
348
  const [layout, setLayout] = useUrlSyncedState<TableState<TData>['layout']>({
349
+ ...LAYOUT_SERIALIZATION,
281
350
  initialState: initialState.layout,
282
- serializer: (_layout) => [['layout', _layout]],
283
- deserializer: (params) => params.get('layout') ?? initialState.layout,
284
351
  sync,
285
352
  });
286
353
  const [dateRange, setDateRange] = useUrlSyncedState<TableState<TData>['dateRange']>({
354
+ ...DATE_RANGE_SERIALIZATION,
287
355
  initialState: initialState.dateRange,
288
- serializer: ([from, to]) => [
289
- ['from', from?.toISOString() ?? ''],
290
- ['to', to?.toISOString() ?? ''],
291
- ],
292
- deserializer: (params) => [
293
- params.get('from') ? new Date(params.get('from') as string) : initialState.dateRange[0],
294
- params.get('to') ? new Date(params.get('to') as string) : initialState.dateRange[1],
295
- ],
296
356
  sync,
297
357
  });
298
358
  const [columnVisibility, setColumnVisibility] = useUrlSyncedState<TableState<TData>['columnVisibility']>({
359
+ ...COLUMN_VISIBILITY_SERIALIZATION,
299
360
  initialState: initialState.columnVisibility,
300
- serializer: (columns) => [
301
- [
302
- 'show',
303
- Object.entries(columns)
304
- .filter(([, visible]) => visible === true)
305
- .map(([columnName]) => columnName)
306
- .join(','),
307
- ],
308
- [
309
- 'hide',
310
- Object.entries(columns)
311
- .filter(([, visible]) => visible === false)
312
- .map(([columnName]) => columnName)
313
- .join(','),
314
- ],
315
- ],
316
- deserializer: (params) => {
317
- if (!params.has('show') && !params.has('hide')) {
318
- return initialState.columnVisibility;
319
- }
320
- const visible = params.get('show')?.split(',') ?? [];
321
- const invisible = params.get('hide')?.split(',') ?? [];
322
- const columns = {} as TableState<TData>['columnVisibility'];
323
- visible.forEach((column) => {
324
- columns[column] = true;
325
- });
326
- invisible.forEach((column) => {
327
- columns[column] = false;
328
- });
329
- return columns;
330
- },
331
361
  sync,
332
362
  });
333
363