@wordrhyme/auto-crud 1.5.6 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -525,104 +525,6 @@ function DataTable({ table, actionBar, children, className,...props }) {
525
525
  });
526
526
  }
527
527
 
528
- //#endregion
529
- //#region src/lib/id.ts
530
- const prefixes = {};
531
- function generateId(prefixOrOptions, inputOptions = {}) {
532
- const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
533
- const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
534
- const { length = 12, separator = "_" } = finalOptions;
535
- const id = (0, nanoid.customAlphabet)("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
536
- return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
537
- }
538
-
539
- //#endregion
540
- //#region src/lib/readable-filters.ts
541
- const OP_SUFFIX = "__op";
542
- const INDEX_SEPARATOR = "__";
543
- const RANGE_VARIANTS = new Set(["range", "dateRange"]);
544
- function getColumnId(column) {
545
- if ("id" in column && column.id) return String(column.id);
546
- if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
547
- return null;
548
- }
549
- function getColumnVariant(column) {
550
- if ("columnDef" in column) return column.columnDef.meta?.variant;
551
- return column.meta?.variant;
552
- }
553
- function splitKey(key) {
554
- const match = key.match(/^(.*?)(?:__(\d+))?$/);
555
- if (!match) return { base: key };
556
- const index$1 = match[2] ? Number(match[2]) : void 0;
557
- return {
558
- base: match[1] ?? key,
559
- index: index$1
560
- };
561
- }
562
- function isFilterKey(key, columnIds) {
563
- const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
564
- return columnIds.has(base);
565
- }
566
- function parseValueByVariant(value, variant) {
567
- if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
568
- if (RANGE_VARIANTS.has(variant)) {
569
- const parts = value.split(",");
570
- return [parts[0] ?? "", parts[1] ?? ""];
571
- }
572
- if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
573
- return value;
574
- }
575
- function serializeValue(value, variant) {
576
- if (Array.isArray(value)) {
577
- if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
578
- return value.join(",");
579
- }
580
- return value;
581
- }
582
- function parseReadableFilters(params, columns) {
583
- const columnsById = /* @__PURE__ */ new Map();
584
- for (const column of columns) {
585
- const id = getColumnId(column);
586
- if (id) columnsById.set(id, column);
587
- }
588
- const filters = [];
589
- for (const [key, rawValue] of params.entries()) {
590
- if (key.endsWith(OP_SUFFIX)) continue;
591
- const { base } = splitKey(key);
592
- const column = columnsById.get(base);
593
- if (!column) continue;
594
- const variant = getColumnVariant(column) ?? "text";
595
- const opKey = `${key}${OP_SUFFIX}`;
596
- const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
597
- const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
598
- filters.push({
599
- id: base,
600
- value: parsedValue,
601
- variant,
602
- operator,
603
- filterId: key || generateId({ length: 8 })
604
- });
605
- }
606
- return filters;
607
- }
608
- function applyReadableFilters(params, columns, filters) {
609
- const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
610
- for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
611
- const counters = /* @__PURE__ */ new Map();
612
- for (const filter of filters) {
613
- const columnId = String(filter.id);
614
- const count = (counters.get(columnId) ?? 0) + 1;
615
- counters.set(columnId, count);
616
- const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
617
- const variant = filter.variant ?? "text";
618
- const value = serializeValue(filter.value, variant);
619
- params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
620
- const defaultOperator = getDefaultFilterOperator(variant);
621
- if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
622
- }
623
- return params;
624
- }
625
-
626
528
  //#endregion
627
529
  //#region src/hooks/use-url-state.ts
628
530
  const parseAsInteger = {
@@ -873,15 +775,120 @@ function useQueryStates(parsers, options = {}) {
873
775
  }, [setValues])];
874
776
  }
875
777
 
778
+ //#endregion
779
+ //#region src/lib/id.ts
780
+ const prefixes = {};
781
+ function generateId(prefixOrOptions, inputOptions = {}) {
782
+ const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
783
+ const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
784
+ const { length = 12, separator = "_" } = finalOptions;
785
+ const id = (0, nanoid.customAlphabet)("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
786
+ return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
787
+ }
788
+
789
+ //#endregion
790
+ //#region src/lib/readable-filters.ts
791
+ const OP_SUFFIX = "__op";
792
+ const INDEX_SEPARATOR = "__";
793
+ const RANGE_VARIANTS = new Set(["range", "dateRange"]);
794
+ function getColumnId(column) {
795
+ if ("id" in column && column.id) return String(column.id);
796
+ if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
797
+ return null;
798
+ }
799
+ function getColumnVariant(column) {
800
+ if ("columnDef" in column) return column.columnDef.meta?.variant;
801
+ return column.meta?.variant;
802
+ }
803
+ function splitKey(key) {
804
+ const match = key.match(/^(.*?)(?:__(\d+))?$/);
805
+ if (!match) return { base: key };
806
+ const index$1 = match[2] ? Number(match[2]) : void 0;
807
+ return {
808
+ base: match[1] ?? key,
809
+ index: index$1
810
+ };
811
+ }
812
+ function isFilterKey(key, columnIds) {
813
+ const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
814
+ return columnIds.has(base);
815
+ }
816
+ function parseValueByVariant(value, variant) {
817
+ if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
818
+ if (RANGE_VARIANTS.has(variant)) {
819
+ const parts = value.split(",");
820
+ return [parts[0] ?? "", parts[1] ?? ""];
821
+ }
822
+ if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
823
+ return value;
824
+ }
825
+ function serializeValue(value, variant) {
826
+ if (Array.isArray(value)) {
827
+ if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
828
+ return value.join(",");
829
+ }
830
+ return value;
831
+ }
832
+ function parseReadableFilters(params, columns) {
833
+ const columnsById = /* @__PURE__ */ new Map();
834
+ for (const column of columns) {
835
+ const id = getColumnId(column);
836
+ if (id) columnsById.set(id, column);
837
+ }
838
+ const filters = [];
839
+ for (const [key, rawValue] of params.entries()) {
840
+ if (key.endsWith(OP_SUFFIX)) continue;
841
+ const { base } = splitKey(key);
842
+ const column = columnsById.get(base);
843
+ if (!column) continue;
844
+ const variant = getColumnVariant(column) ?? "text";
845
+ const opKey = `${key}${OP_SUFFIX}`;
846
+ const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
847
+ const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
848
+ filters.push({
849
+ id: base,
850
+ value: parsedValue,
851
+ variant,
852
+ operator,
853
+ filterId: key || generateId({ length: 8 })
854
+ });
855
+ }
856
+ return filters;
857
+ }
858
+ function applyReadableFilters(params, columns, filters) {
859
+ const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
860
+ for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
861
+ const counters = /* @__PURE__ */ new Map();
862
+ for (const filter of filters) {
863
+ const columnId = String(filter.id);
864
+ const count = (counters.get(columnId) ?? 0) + 1;
865
+ counters.set(columnId, count);
866
+ const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
867
+ const variant = filter.variant ?? "text";
868
+ const value = serializeValue(filter.value, variant);
869
+ params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
870
+ const defaultOperator = getDefaultFilterOperator(variant);
871
+ if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
872
+ }
873
+ return params;
874
+ }
875
+
876
876
  //#endregion
877
877
  //#region src/hooks/use-readable-filters.ts
878
878
  const URL_CHANGE_EVENT = "urlchange";
879
879
  function toSearchString(params) {
880
- const queryString = params.toString().replace(/%2C/g, ",");
880
+ const queryString = params.toString().replace(/%2C/gu, ",");
881
881
  return queryString ? `?${queryString}` : "";
882
882
  }
883
+ function filterSignature(filters) {
884
+ return JSON.stringify(filters.map(({ id, operator, value, variant }) => JSON.stringify([
885
+ id,
886
+ operator,
887
+ variant === "multiSelect" && Array.isArray(value) ? [...value].sort() : value
888
+ ])).sort());
889
+ }
883
890
  function useReadableFilters(columns, options = {}) {
884
- const { history = "replace", scroll = false } = options;
891
+ const { history = "replace", scroll = false, resetPageKey = "page" } = options;
885
892
  const columnSnapshot = react.useMemo(() => columns, [columns]);
886
893
  const lastWrittenUrlRef = react.useRef(null);
887
894
  const filtersRef = react.useRef(null);
@@ -909,7 +916,10 @@ function useReadableFilters(columns, options = {}) {
909
916
  }, [columnSnapshot]);
910
917
  const writeToUrl = react.useCallback((next) => {
911
918
  const params = getUrlParams();
919
+ const previousFilters = parseReadableFilters(params, columnSnapshot);
912
920
  applyReadableFilters(params, columnSnapshot, next);
921
+ const nextFilters = parseReadableFilters(params, columnSnapshot);
922
+ if (resetPageKey !== false && resetPageKey.length > 0 && filterSignature(previousFilters) !== filterSignature(nextFilters)) params.delete(resetPageKey);
913
923
  lastWrittenUrlRef.current = toSearchString(params);
914
924
  setSearchParams(params, {
915
925
  history,
@@ -918,6 +928,7 @@ function useReadableFilters(columns, options = {}) {
918
928
  }, [
919
929
  columnSnapshot,
920
930
  history,
931
+ resetPageKey,
921
932
  scroll
922
933
  ]);
923
934
  return [filters, react.useCallback((updater) => {
@@ -1053,7 +1064,10 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1053
1064
  return true;
1054
1065
  });
1055
1066
  }, [table, columnDefs]);
1056
- const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1067
+ const [filters, setFilters] = useReadableFilters(columns, {
1068
+ debounceMs,
1069
+ resetPageKey: table.options.meta?.queryKeys?.page ?? "page"
1070
+ });
1057
1071
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
1058
1072
  const [joinOperator, setJoinOperator] = useQueryState(table.options.meta?.queryKeys?.joinOperator ?? "", parseAsStringEnum(["and", "or"]).withDefault("and"));
1059
1073
  const onFilterAdd = react.useCallback(() => {
@@ -1591,7 +1605,10 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1591
1605
  setSelectedColumn(null);
1592
1606
  }
1593
1607
  }, [inputValue, selectedColumn]);
1594
- const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1608
+ const [filters, setFilters] = useReadableFilters(columns, {
1609
+ debounceMs,
1610
+ resetPageKey: table.options.meta?.queryKeys?.page ?? "page"
1611
+ });
1595
1612
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
1596
1613
  const onFilterAdd = react.useCallback((column, value) => {
1597
1614
  if (!value.trim() && column.columnDef.meta?.variant !== "boolean") return;
@@ -2397,6 +2414,7 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2397
2414
  const queryStateOptions = table.options.meta?.queryStateOptions;
2398
2415
  const [queryFilters, setQueryFilters] = useReadableFilters(columns, {
2399
2416
  ...queryStateOptions,
2417
+ resetPageKey: table.options.meta?.queryKeys?.page ?? "page",
2400
2418
  shallow
2401
2419
  });
2402
2420
  const filters = externalFilters ?? queryFilters;
package/dist/index.d.cts CHANGED
@@ -1346,12 +1346,12 @@ declare const filterItemSchema: z.ZodObject<{
1346
1346
  variant: z.ZodEnum<{
1347
1347
  number: "number";
1348
1348
  boolean: "boolean";
1349
- date: "date";
1350
1349
  text: "text";
1350
+ range: "range";
1351
+ date: "date";
1352
+ dateRange: "dateRange";
1351
1353
  select: "select";
1352
1354
  multiSelect: "multiSelect";
1353
- dateRange: "dateRange";
1354
- range: "range";
1355
1355
  }>;
1356
1356
  operator: z.ZodEnum<{
1357
1357
  iLike: "iLike";
@@ -1691,6 +1691,8 @@ type ColumnLike<TData> = Column<TData> | ColumnDef<TData, unknown> | {
1691
1691
  };
1692
1692
  interface UseReadableFiltersOptions extends UrlStateOptions {
1693
1693
  debounceMs?: number;
1694
+ /** Reset server-side pagination when effective filters change. */
1695
+ resetPageKey?: string | false;
1694
1696
  }
1695
1697
  declare function useReadableFilters<TData>(columns: ColumnLike<TData>[], options?: UseReadableFiltersOptions): [ExtendedColumnFilter<TData>[], (value: ExtendedColumnFilter<TData>[] | ((prev: ExtendedColumnFilter<TData>[]) => ExtendedColumnFilter<TData>[])) => void];
1696
1698
  //#endregion
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import * as _tanstack_react_table0 from "@tanstack/react-table";
3
3
  import { Column, ColumnDef, ColumnSort, Row, RowData, Table, TableOptions, TableState } from "@tanstack/react-table";
4
4
  import { DropdownMenuTrigger, PopoverContent } from "@wordrhyme/shadcn";
5
5
  import { ClassValue } from "clsx";
6
- import * as react_jsx_runtime3 from "react/jsx-runtime";
6
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
7
7
  import { MultiCombobox, MultiComboboxOption, MultiComboboxProps, MultiComboboxTriggerRenderProps, Select, SelectMode, SelectOption, SelectProps, SelectSearchableDynamicProps, SelectSearchableMultipleProps, SelectSearchableProps, SelectSearchableSingleProps, SelectSimpleProps, SelectTriggerRenderProps } from "@wordrhyme/shadcn-ui";
8
8
  import { z } from "zod";
9
9
  import { JsonSchemaFormScope } from "@wordrhyme/formily-shadcn";
@@ -413,7 +413,7 @@ declare function AutoTableActionBar<TData>({
413
413
  extraActions,
414
414
  actions,
415
415
  deleteConfirmation
416
- }: AutoTableActionBarProps<TData>): react_jsx_runtime3.JSX.Element;
416
+ }: AutoTableActionBarProps<TData>): react_jsx_runtime2.JSX.Element;
417
417
  //#endregion
418
418
  //#region src/lib/schema-bridge/types.d.ts
419
419
  /**
@@ -592,7 +592,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
592
592
  onSelectedCountChange,
593
593
  onSelectedRowsChange,
594
594
  getSelectedRows
595
- }: AutoTableProps<T>): react_jsx_runtime3.JSX.Element;
595
+ }: AutoTableProps<T>): react_jsx_runtime2.JSX.Element;
596
596
  //#endregion
597
597
  //#region src/i18n/locale.d.ts
598
598
  /**
@@ -1093,7 +1093,7 @@ declare function AutoCrudTable<TSchema extends z.ZodObject<z.ZodRawShape>>({
1093
1093
  toolbarActions,
1094
1094
  locale: localeProp,
1095
1095
  onCreate
1096
- }: AutoCrudTableProps<TSchema>): react_jsx_runtime3.JSX.Element;
1096
+ }: AutoCrudTableProps<TSchema>): react_jsx_runtime2.JSX.Element;
1097
1097
  //#endregion
1098
1098
  //#region src/components/auto-crud/auto-form.d.ts
1099
1099
  interface AutoFormProps<T extends z.ZodObject<z.ZodRawShape>> {
@@ -1132,7 +1132,7 @@ declare function AutoFormInner<T extends z.ZodObject<z.ZodRawShape>>({
1132
1132
  labelAlign,
1133
1133
  labelWidth,
1134
1134
  showSubmitButton
1135
- }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime3.JSX.Element;
1135
+ }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime2.JSX.Element;
1136
1136
  declare const AutoForm: <T extends z.ZodObject<z.ZodRawShape>>(props: AutoFormProps<T> & {
1137
1137
  ref?: React.Ref<AutoFormRef>;
1138
1138
  }) => ReturnType<typeof AutoFormInner>;
@@ -1346,12 +1346,12 @@ declare const filterItemSchema: z.ZodObject<{
1346
1346
  variant: z.ZodEnum<{
1347
1347
  number: "number";
1348
1348
  boolean: "boolean";
1349
- date: "date";
1350
1349
  text: "text";
1350
+ range: "range";
1351
+ date: "date";
1352
+ dateRange: "dateRange";
1351
1353
  select: "select";
1352
1354
  multiSelect: "multiSelect";
1353
- dateRange: "dateRange";
1354
- range: "range";
1355
1355
  }>;
1356
1356
  operator: z.ZodEnum<{
1357
1357
  iLike: "iLike";
@@ -1448,7 +1448,7 @@ declare function AutoTableSimpleFilters<TData>({
1448
1448
  filters: externalFilters,
1449
1449
  onFiltersChange,
1450
1450
  leading
1451
- }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime3.JSX.Element | null;
1451
+ }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime2.JSX.Element | null;
1452
1452
  //#endregion
1453
1453
  //#region src/components/auto-crud/form-modal.d.ts
1454
1454
  type ModalVariant = 'dialog' | 'sheet';
@@ -1492,7 +1492,7 @@ declare function CrudFormModal<T extends z.ZodObject<z.ZodRawShape>>({
1492
1492
  labelAlign,
1493
1493
  labelWidth,
1494
1494
  className
1495
- }: CrudFormModalProps<T>): react_jsx_runtime3.JSX.Element;
1495
+ }: CrudFormModalProps<T>): react_jsx_runtime2.JSX.Element;
1496
1496
  //#endregion
1497
1497
  //#region src/components/auto-crud/import-dialog.d.ts
1498
1498
  interface ImportDialogProps {
@@ -1514,7 +1514,7 @@ declare function ImportDialog({
1514
1514
  columns,
1515
1515
  title,
1516
1516
  locale
1517
- }: ImportDialogProps): react_jsx_runtime3.JSX.Element;
1517
+ }: ImportDialogProps): react_jsx_runtime2.JSX.Element;
1518
1518
  //#endregion
1519
1519
  //#region src/components/auto-crud/export-dialog.d.ts
1520
1520
  type ExportMode = 'selected' | 'filtered';
@@ -1534,7 +1534,7 @@ declare function ExportDialog({
1534
1534
  selectedCount,
1535
1535
  onExport,
1536
1536
  canExportFiltered
1537
- }: ExportDialogProps): react_jsx_runtime3.JSX.Element;
1537
+ }: ExportDialogProps): react_jsx_runtime2.JSX.Element;
1538
1538
  //#endregion
1539
1539
  //#region src/components/data-table/data-table.d.ts
1540
1540
  interface DataTableProps<TData> extends React$1.ComponentProps<'div'> {
@@ -1547,7 +1547,7 @@ declare function DataTable<TData>({
1547
1547
  children,
1548
1548
  className,
1549
1549
  ...props
1550
- }: DataTableProps<TData>): react_jsx_runtime3.JSX.Element;
1550
+ }: DataTableProps<TData>): react_jsx_runtime2.JSX.Element;
1551
1551
  //#endregion
1552
1552
  //#region src/components/data-table/data-table-advanced-toolbar.d.ts
1553
1553
  interface DataTableAdvancedToolbarProps<TData> extends React$1.ComponentProps<'div'> {
@@ -1558,7 +1558,7 @@ declare function DataTableAdvancedToolbar<TData>({
1558
1558
  children,
1559
1559
  className,
1560
1560
  ...props
1561
- }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime3.JSX.Element;
1561
+ }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime2.JSX.Element;
1562
1562
  //#endregion
1563
1563
  //#region src/components/data-table/data-table-column-header.d.ts
1564
1564
  interface DataTableColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {
@@ -1570,7 +1570,7 @@ declare function DataTableColumnHeader<TData, TValue>({
1570
1570
  label,
1571
1571
  className,
1572
1572
  ...props
1573
- }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime3.JSX.Element;
1573
+ }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime2.JSX.Element;
1574
1574
  //#endregion
1575
1575
  //#region src/components/data-table/data-table-faceted-filter.d.ts
1576
1576
  interface DataTableFacetedFilterProps<TData, TValue> {
@@ -1584,7 +1584,7 @@ declare function DataTableFacetedFilter<TData, TValue>({
1584
1584
  title,
1585
1585
  options,
1586
1586
  multiple
1587
- }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime3.JSX.Element;
1587
+ }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime2.JSX.Element;
1588
1588
  //#endregion
1589
1589
  //#region src/components/data-table/data-table-pagination.d.ts
1590
1590
  interface DataTablePaginationProps<TData> extends React.ComponentProps<'div'> {
@@ -1596,7 +1596,7 @@ declare function DataTablePagination<TData>({
1596
1596
  pageSizeOptions,
1597
1597
  className,
1598
1598
  ...props
1599
- }: DataTablePaginationProps<TData>): react_jsx_runtime3.JSX.Element;
1599
+ }: DataTablePaginationProps<TData>): react_jsx_runtime2.JSX.Element;
1600
1600
  //#endregion
1601
1601
  //#region src/components/data-table/data-table-toolbar.d.ts
1602
1602
  interface DataTableToolbarProps<TData> extends React$1.ComponentProps<'div'> {
@@ -1607,7 +1607,7 @@ declare function DataTableToolbar<TData>({
1607
1607
  children,
1608
1608
  className,
1609
1609
  ...props
1610
- }: DataTableToolbarProps<TData>): react_jsx_runtime3.JSX.Element;
1610
+ }: DataTableToolbarProps<TData>): react_jsx_runtime2.JSX.Element;
1611
1611
  //#endregion
1612
1612
  //#region src/components/data-table/data-table-view-options.d.ts
1613
1613
  interface DataTableViewOptionsProps<TData> extends React$1.ComponentProps<typeof PopoverContent> {
@@ -1618,7 +1618,7 @@ declare function DataTableViewOptions<TData>({
1618
1618
  table,
1619
1619
  disabled,
1620
1620
  ...props
1621
- }: DataTableViewOptionsProps<TData>): react_jsx_runtime3.JSX.Element;
1621
+ }: DataTableViewOptionsProps<TData>): react_jsx_runtime2.JSX.Element;
1622
1622
  //#endregion
1623
1623
  //#region src/lib/crud-actions.d.ts
1624
1624
  type CrudActionZone = 'toolbar' | 'row' | 'batch';
@@ -1691,6 +1691,8 @@ type ColumnLike<TData> = Column<TData> | ColumnDef<TData, unknown> | {
1691
1691
  };
1692
1692
  interface UseReadableFiltersOptions extends UrlStateOptions {
1693
1693
  debounceMs?: number;
1694
+ /** Reset server-side pagination when effective filters change. */
1695
+ resetPageKey?: string | false;
1694
1696
  }
1695
1697
  declare function useReadableFilters<TData>(columns: ColumnLike<TData>[], options?: UseReadableFiltersOptions): [ExtendedColumnFilter<TData>[], (value: ExtendedColumnFilter<TData>[] | ((prev: ExtendedColumnFilter<TData>[]) => ExtendedColumnFilter<TData>[])) => void];
1696
1698
  //#endregion
package/dist/index.js CHANGED
@@ -488,104 +488,6 @@ function DataTable({ table, actionBar, children, className,...props }) {
488
488
  });
489
489
  }
490
490
 
491
- //#endregion
492
- //#region src/lib/id.ts
493
- const prefixes = {};
494
- function generateId(prefixOrOptions, inputOptions = {}) {
495
- const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
496
- const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
497
- const { length = 12, separator = "_" } = finalOptions;
498
- const id = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
499
- return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
500
- }
501
-
502
- //#endregion
503
- //#region src/lib/readable-filters.ts
504
- const OP_SUFFIX = "__op";
505
- const INDEX_SEPARATOR = "__";
506
- const RANGE_VARIANTS = new Set(["range", "dateRange"]);
507
- function getColumnId(column) {
508
- if ("id" in column && column.id) return String(column.id);
509
- if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
510
- return null;
511
- }
512
- function getColumnVariant(column) {
513
- if ("columnDef" in column) return column.columnDef.meta?.variant;
514
- return column.meta?.variant;
515
- }
516
- function splitKey(key) {
517
- const match = key.match(/^(.*?)(?:__(\d+))?$/);
518
- if (!match) return { base: key };
519
- const index$1 = match[2] ? Number(match[2]) : void 0;
520
- return {
521
- base: match[1] ?? key,
522
- index: index$1
523
- };
524
- }
525
- function isFilterKey(key, columnIds) {
526
- const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
527
- return columnIds.has(base);
528
- }
529
- function parseValueByVariant(value, variant) {
530
- if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
531
- if (RANGE_VARIANTS.has(variant)) {
532
- const parts = value.split(",");
533
- return [parts[0] ?? "", parts[1] ?? ""];
534
- }
535
- if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
536
- return value;
537
- }
538
- function serializeValue(value, variant) {
539
- if (Array.isArray(value)) {
540
- if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
541
- return value.join(",");
542
- }
543
- return value;
544
- }
545
- function parseReadableFilters(params, columns) {
546
- const columnsById = /* @__PURE__ */ new Map();
547
- for (const column of columns) {
548
- const id = getColumnId(column);
549
- if (id) columnsById.set(id, column);
550
- }
551
- const filters = [];
552
- for (const [key, rawValue] of params.entries()) {
553
- if (key.endsWith(OP_SUFFIX)) continue;
554
- const { base } = splitKey(key);
555
- const column = columnsById.get(base);
556
- if (!column) continue;
557
- const variant = getColumnVariant(column) ?? "text";
558
- const opKey = `${key}${OP_SUFFIX}`;
559
- const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
560
- const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
561
- filters.push({
562
- id: base,
563
- value: parsedValue,
564
- variant,
565
- operator,
566
- filterId: key || generateId({ length: 8 })
567
- });
568
- }
569
- return filters;
570
- }
571
- function applyReadableFilters(params, columns, filters) {
572
- const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
573
- for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
574
- const counters = /* @__PURE__ */ new Map();
575
- for (const filter of filters) {
576
- const columnId = String(filter.id);
577
- const count = (counters.get(columnId) ?? 0) + 1;
578
- counters.set(columnId, count);
579
- const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
580
- const variant = filter.variant ?? "text";
581
- const value = serializeValue(filter.value, variant);
582
- params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
583
- const defaultOperator = getDefaultFilterOperator(variant);
584
- if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
585
- }
586
- return params;
587
- }
588
-
589
491
  //#endregion
590
492
  //#region src/hooks/use-url-state.ts
591
493
  const parseAsInteger = {
@@ -836,15 +738,120 @@ function useQueryStates(parsers, options = {}) {
836
738
  }, [setValues])];
837
739
  }
838
740
 
741
+ //#endregion
742
+ //#region src/lib/id.ts
743
+ const prefixes = {};
744
+ function generateId(prefixOrOptions, inputOptions = {}) {
745
+ const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
746
+ const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
747
+ const { length = 12, separator = "_" } = finalOptions;
748
+ const id = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
749
+ return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
750
+ }
751
+
752
+ //#endregion
753
+ //#region src/lib/readable-filters.ts
754
+ const OP_SUFFIX = "__op";
755
+ const INDEX_SEPARATOR = "__";
756
+ const RANGE_VARIANTS = new Set(["range", "dateRange"]);
757
+ function getColumnId(column) {
758
+ if ("id" in column && column.id) return String(column.id);
759
+ if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
760
+ return null;
761
+ }
762
+ function getColumnVariant(column) {
763
+ if ("columnDef" in column) return column.columnDef.meta?.variant;
764
+ return column.meta?.variant;
765
+ }
766
+ function splitKey(key) {
767
+ const match = key.match(/^(.*?)(?:__(\d+))?$/);
768
+ if (!match) return { base: key };
769
+ const index$1 = match[2] ? Number(match[2]) : void 0;
770
+ return {
771
+ base: match[1] ?? key,
772
+ index: index$1
773
+ };
774
+ }
775
+ function isFilterKey(key, columnIds) {
776
+ const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
777
+ return columnIds.has(base);
778
+ }
779
+ function parseValueByVariant(value, variant) {
780
+ if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
781
+ if (RANGE_VARIANTS.has(variant)) {
782
+ const parts = value.split(",");
783
+ return [parts[0] ?? "", parts[1] ?? ""];
784
+ }
785
+ if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
786
+ return value;
787
+ }
788
+ function serializeValue(value, variant) {
789
+ if (Array.isArray(value)) {
790
+ if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
791
+ return value.join(",");
792
+ }
793
+ return value;
794
+ }
795
+ function parseReadableFilters(params, columns) {
796
+ const columnsById = /* @__PURE__ */ new Map();
797
+ for (const column of columns) {
798
+ const id = getColumnId(column);
799
+ if (id) columnsById.set(id, column);
800
+ }
801
+ const filters = [];
802
+ for (const [key, rawValue] of params.entries()) {
803
+ if (key.endsWith(OP_SUFFIX)) continue;
804
+ const { base } = splitKey(key);
805
+ const column = columnsById.get(base);
806
+ if (!column) continue;
807
+ const variant = getColumnVariant(column) ?? "text";
808
+ const opKey = `${key}${OP_SUFFIX}`;
809
+ const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
810
+ const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
811
+ filters.push({
812
+ id: base,
813
+ value: parsedValue,
814
+ variant,
815
+ operator,
816
+ filterId: key || generateId({ length: 8 })
817
+ });
818
+ }
819
+ return filters;
820
+ }
821
+ function applyReadableFilters(params, columns, filters) {
822
+ const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
823
+ for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
824
+ const counters = /* @__PURE__ */ new Map();
825
+ for (const filter of filters) {
826
+ const columnId = String(filter.id);
827
+ const count = (counters.get(columnId) ?? 0) + 1;
828
+ counters.set(columnId, count);
829
+ const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
830
+ const variant = filter.variant ?? "text";
831
+ const value = serializeValue(filter.value, variant);
832
+ params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
833
+ const defaultOperator = getDefaultFilterOperator(variant);
834
+ if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
835
+ }
836
+ return params;
837
+ }
838
+
839
839
  //#endregion
840
840
  //#region src/hooks/use-readable-filters.ts
841
841
  const URL_CHANGE_EVENT = "urlchange";
842
842
  function toSearchString(params) {
843
- const queryString = params.toString().replace(/%2C/g, ",");
843
+ const queryString = params.toString().replace(/%2C/gu, ",");
844
844
  return queryString ? `?${queryString}` : "";
845
845
  }
846
+ function filterSignature(filters) {
847
+ return JSON.stringify(filters.map(({ id, operator, value, variant }) => JSON.stringify([
848
+ id,
849
+ operator,
850
+ variant === "multiSelect" && Array.isArray(value) ? [...value].sort() : value
851
+ ])).sort());
852
+ }
846
853
  function useReadableFilters(columns, options = {}) {
847
- const { history = "replace", scroll = false } = options;
854
+ const { history = "replace", scroll = false, resetPageKey = "page" } = options;
848
855
  const columnSnapshot = React.useMemo(() => columns, [columns]);
849
856
  const lastWrittenUrlRef = React.useRef(null);
850
857
  const filtersRef = React.useRef(null);
@@ -872,7 +879,10 @@ function useReadableFilters(columns, options = {}) {
872
879
  }, [columnSnapshot]);
873
880
  const writeToUrl = React.useCallback((next) => {
874
881
  const params = getUrlParams();
882
+ const previousFilters = parseReadableFilters(params, columnSnapshot);
875
883
  applyReadableFilters(params, columnSnapshot, next);
884
+ const nextFilters = parseReadableFilters(params, columnSnapshot);
885
+ if (resetPageKey !== false && resetPageKey.length > 0 && filterSignature(previousFilters) !== filterSignature(nextFilters)) params.delete(resetPageKey);
876
886
  lastWrittenUrlRef.current = toSearchString(params);
877
887
  setSearchParams(params, {
878
888
  history,
@@ -881,6 +891,7 @@ function useReadableFilters(columns, options = {}) {
881
891
  }, [
882
892
  columnSnapshot,
883
893
  history,
894
+ resetPageKey,
884
895
  scroll
885
896
  ]);
886
897
  return [filters, React.useCallback((updater) => {
@@ -1016,7 +1027,10 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1016
1027
  return true;
1017
1028
  });
1018
1029
  }, [table, columnDefs]);
1019
- const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1030
+ const [filters, setFilters] = useReadableFilters(columns, {
1031
+ debounceMs,
1032
+ resetPageKey: table.options.meta?.queryKeys?.page ?? "page"
1033
+ });
1020
1034
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
1021
1035
  const [joinOperator, setJoinOperator] = useQueryState(table.options.meta?.queryKeys?.joinOperator ?? "", parseAsStringEnum(["and", "or"]).withDefault("and"));
1022
1036
  const onFilterAdd = React.useCallback(() => {
@@ -1554,7 +1568,10 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1554
1568
  setSelectedColumn(null);
1555
1569
  }
1556
1570
  }, [inputValue, selectedColumn]);
1557
- const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1571
+ const [filters, setFilters] = useReadableFilters(columns, {
1572
+ debounceMs,
1573
+ resetPageKey: table.options.meta?.queryKeys?.page ?? "page"
1574
+ });
1558
1575
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
1559
1576
  const onFilterAdd = React.useCallback((column, value) => {
1560
1577
  if (!value.trim() && column.columnDef.meta?.variant !== "boolean") return;
@@ -2360,6 +2377,7 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2360
2377
  const queryStateOptions = table.options.meta?.queryStateOptions;
2361
2378
  const [queryFilters, setQueryFilters] = useReadableFilters(columns, {
2362
2379
  ...queryStateOptions,
2380
+ resetPageKey: table.options.meta?.queryKeys?.page ?? "page",
2363
2381
  shallow
2364
2382
  });
2365
2383
  const filters = externalFilters ?? queryFilters;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.5.6",
4
+ "version": "1.5.7",
5
5
  "description": "Schema-first CRUD components with auto-generated tables and forms",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -62,9 +62,9 @@
62
62
  "nanoid": "^5.1.6",
63
63
  "tailwind-merge": "^3.5.0",
64
64
  "vaul": "^1.1.2",
65
- "@wordrhyme/formily-shadcn": "1.13.4",
65
+ "@wordrhyme/shadcn": "1.3.4",
66
66
  "@wordrhyme/shadcn-ui": "1.32.9",
67
- "@wordrhyme/shadcn": "1.3.4"
67
+ "@wordrhyme/formily-shadcn": "1.13.4"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@storybook/react": "^8.6.18",
@@ -85,8 +85,8 @@
85
85
  "typescript": "^5.9.3",
86
86
  "vitest": "^3.2.4",
87
87
  "@internal/eslint-config": "0.3.0",
88
- "@internal/tsconfig": "0.1.0",
89
88
  "@internal/prettier-config": "0.0.1",
89
+ "@internal/tsconfig": "0.1.0",
90
90
  "@internal/tsdown-config": "0.1.0",
91
91
  "@internal/vitest-config": "0.1.0"
92
92
  },