dishui 0.0.84 → 0.0.89

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 (89) hide show
  1. package/dist/components/Input/index.js +52 -62
  2. package/dist/components/Input/index.js.map +1 -1
  3. package/dist/components/LayoutSuite/index.d.ts +10 -0
  4. package/dist/components/LayoutSuite/index.js +27 -18
  5. package/dist/components/LayoutSuite/index.js.map +1 -1
  6. package/dist/components/Markdown/markdownComponents.js +1 -0
  7. package/dist/components/Markdown/markdownComponents.js.map +1 -1
  8. package/dist/components/Markdown/useMarkdownSource.js +46 -31
  9. package/dist/components/Markdown/useMarkdownSource.js.map +1 -1
  10. package/dist/components/Markdown/useMarkmapRenderer.js +44 -31
  11. package/dist/components/Markdown/useMarkmapRenderer.js.map +1 -1
  12. package/dist/components/Markdown/useMermaidRenderer.js +49 -31
  13. package/dist/components/Markdown/useMermaidRenderer.js.map +1 -1
  14. package/dist/components/ProtectedRoute/index.d.ts +2 -1
  15. package/dist/components/ProtectedRoute/index.js +16 -6
  16. package/dist/components/ProtectedRoute/index.js.map +1 -1
  17. package/dist/components/Select/index.d.ts +6 -0
  18. package/dist/components/Select/index.js +166 -136
  19. package/dist/components/Select/index.js.map +1 -1
  20. package/dist/components/Tabs/index.d.ts +12 -1
  21. package/dist/components/Tabs/index.js +42 -17
  22. package/dist/components/Tabs/index.js.map +1 -1
  23. package/dist/components/pro/ArticleContextMenu/index.js +7 -7
  24. package/dist/components/pro/CodeEditor/editors/CodeEditor.js +200 -189
  25. package/dist/components/pro/CodeEditor/editors/CodeEditor.js.map +1 -1
  26. package/dist/components/pro/CodeEditor/types.d.ts +3 -0
  27. package/dist/components/pro/DataTable/DataTable.d.ts +1 -1
  28. package/dist/components/pro/DataTable/DataTable.js +98 -240
  29. package/dist/components/pro/DataTable/DataTable.js.map +1 -1
  30. package/dist/components/pro/DataTable/components/TableBody.js +14 -16
  31. package/dist/components/pro/DataTable/components/TableBody.js.map +1 -1
  32. package/dist/components/pro/DataTable/components/TablePagination.d.ts +3 -9
  33. package/dist/components/pro/DataTable/components/TablePagination.js +29 -25
  34. package/dist/components/pro/DataTable/components/TablePagination.js.map +1 -1
  35. package/dist/components/pro/DataTable/tanstack-table.d.ts +2 -2
  36. package/dist/components/pro/DataTable/tanstack-table.js +29 -21
  37. package/dist/components/pro/DataTable/tanstack-table.js.map +1 -1
  38. package/dist/components/pro/DataTable/treeDataUtils.d.ts +2 -0
  39. package/dist/components/pro/DataTable/treeDataUtils.js +18 -19
  40. package/dist/components/pro/DataTable/treeDataUtils.js.map +1 -1
  41. package/dist/components/pro/DataTable/types.d.ts +24 -0
  42. package/dist/components/pro/DataTable/useDataTableModel.d.ts +16 -0
  43. package/dist/components/pro/DataTable/useDataTableModel.js +148 -0
  44. package/dist/components/pro/DataTable/useDataTableModel.js.map +1 -0
  45. package/dist/components/pro/FileExplorer/Breadcrumb.js +12 -12
  46. package/dist/components/pro/Flow/nodes/DataNodeView.js +87 -82
  47. package/dist/components/pro/Flow/nodes/DataNodeView.js.map +1 -1
  48. package/dist/components/pro/Flow/nodes/MediaContent.js +1 -1
  49. package/dist/components/pro/Flow/nodes/MediaContent.js.map +1 -1
  50. package/dist/components/pro/Flow/nodes/MediaOverlay.js +1 -1
  51. package/dist/components/pro/Flow/nodes/MediaOverlay.js.map +1 -1
  52. package/dist/components/pro/Flow/nodes/Model3DView.js +69 -63
  53. package/dist/components/pro/Flow/nodes/Model3DView.js.map +1 -1
  54. package/dist/components/pro/Flow/nodes/chartGeometryBudget.d.ts +14 -0
  55. package/dist/components/pro/Flow/nodes/chartGeometryBudget.js +76 -0
  56. package/dist/components/pro/Flow/nodes/chartGeometryBudget.js.map +1 -0
  57. package/dist/components/pro/Flow/nodes/demandRender.d.ts +11 -0
  58. package/dist/components/pro/Flow/nodes/demandRender.js +26 -0
  59. package/dist/components/pro/Flow/nodes/demandRender.js.map +1 -0
  60. package/dist/components/pro/RichTextEditor/SlashMenu.js +68 -58
  61. package/dist/components/pro/RichTextEditor/SlashMenu.js.map +1 -1
  62. package/dist/contexts/AppContext.d.ts +1 -0
  63. package/dist/contexts/AppContext.js +144 -145
  64. package/dist/contexts/AppContext.js.map +1 -1
  65. package/dist/contexts/authentication.d.ts +7 -0
  66. package/dist/contexts/authentication.js +22 -0
  67. package/dist/contexts/authentication.js.map +1 -0
  68. package/dist/dishui.css +1 -1
  69. package/dist/hooks/useControllableState.d.ts +6 -14
  70. package/dist/hooks/useControllableState.js +21 -15
  71. package/dist/hooks/useControllableState.js.map +1 -1
  72. package/dist/hooks/useTableColumns.d.ts +3 -3
  73. package/dist/hooks/useTableColumns.js +2 -2
  74. package/dist/hooks/useTableColumns.js.map +1 -1
  75. package/dist/hooks/useTableData.d.ts +11 -4
  76. package/dist/hooks/useTableData.js +44 -26
  77. package/dist/hooks/useTableData.js.map +1 -1
  78. package/dist/index-nostyled.d.ts +2 -2
  79. package/dist/index.d.ts +2 -2
  80. package/dist/index.js +97 -97
  81. package/dist/utils/i18n.js +1 -1
  82. package/dist/utils/i18n.js.map +1 -1
  83. package/dist/utils/routerBuilder.d.ts +7 -5
  84. package/dist/utils/routerBuilder.js +11 -15
  85. package/dist/utils/routerBuilder.js.map +1 -1
  86. package/dist/utils/safeStorage.d.ts +8 -0
  87. package/dist/utils/safeStorage.js +45 -0
  88. package/dist/utils/safeStorage.js.map +1 -0
  89. package/package.json +22 -5
@@ -1,20 +1,12 @@
1
1
  /**
2
- * A hook for managing controllable state that can be either controlled (by parent via props)
3
- * or uncontrolled (managed internally). Handles bidirectional sync between external props
4
- * and internal state without causing infinite loops.
5
- *
6
- * Key design decisions:
7
- * - Returns the raw useState setter → always stable reference, safe for useMemo deps
8
- * - Uses a version counter to distinguish internal vs external state changes
9
- * - onChange is called via useEffect (not inside state updater) for React safety
10
- * - onChangeRef avoids stale closures for the onChange callback
11
- *
12
- * @param externalValue - The value from props (controlled mode when onChange is provided)
13
- * @param onChange - Optional callback to notify parent of internal state changes
14
- * @param isEqual - Optional equality check (defaults to reference equality)
15
- * @returns [currentValue, setValue] - The current value and a stable setter
2
+ * A defined externalValue is strictly controlled, even without onChange (read-only).
3
+ * undefined opts into internal state; defaultValue is read only on mount.
4
+ * onChange reports distinct setter requests synchronously, never prop updates.
5
+ * Controlled functional updates use the last committed prop, not a pending request.
6
+ * Do not switch modes: switching back resumes the retained internal state.
16
7
  */
17
8
  export declare function useControllableState<T>(externalValue: T, onChange?: (value: T) => void, isEqual?: (a: T, b: T) => boolean): [T, React.Dispatch<React.SetStateAction<T>>];
9
+ export declare function useControllableState<T>(externalValue: T | undefined, onChange: ((value: T) => void) | undefined, isEqual: ((a: T, b: T) => boolean) | undefined, defaultValue: T): [T, React.Dispatch<React.SetStateAction<T>>];
18
10
  /**
19
11
  * Structural deep equality for the plain values used by DataTable state
20
12
  * (arrays, plain objects, primitives). Avoids the overhead and edge cases of
@@ -1,20 +1,26 @@
1
- import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
1
+ import { useCallback as e, useLayoutEffect as t, useRef as n, useState as r } from "react";
2
2
  //#region src/hooks/useControllableState.ts
3
- function i(i, a, o = (e, t) => e === t) {
4
- let [s, c] = r(i), l = n(a);
5
- l.current = a;
6
- let u = n(i), d = n(0), f = n(0);
7
- o(u.current, i) || (u.current = i, c(i));
8
- let p = e((e) => {
9
- d.current++, c(e);
10
- }, []), m = n(!0);
3
+ function i(i, a, o = Object.is, s) {
4
+ let [c, l] = r(() => i === void 0 ? s : i), u = i !== void 0, d = u ? i : c, f = n({
5
+ value: d,
6
+ controlled: u,
7
+ onChange: a,
8
+ isEqual: o
9
+ });
11
10
  return t(() => {
12
- if (m.current) {
13
- m.current = !1;
14
- return;
15
- }
16
- d.current !== f.current && (f.current = d.current, l.current?.(s));
17
- }, [s]), [s, p];
11
+ f.current = {
12
+ value: d,
13
+ controlled: u,
14
+ onChange: a,
15
+ isEqual: o
16
+ };
17
+ }), [d, e((e) => {
18
+ let t = f.current, n = typeof e == "function" ? e(t.value) : e;
19
+ t.isEqual(t.value, n) || (t.controlled || (f.current = {
20
+ ...t,
21
+ value: n
22
+ }, l(() => n)), t.onChange?.(n));
23
+ }, [])];
18
24
  }
19
25
  function a(e, t) {
20
26
  if (Object.is(e, t)) return !0;
@@ -1 +1 @@
1
- {"version":3,"file":"useControllableState.js","names":[],"sources":["../../src/hooks/useControllableState.ts"],"sourcesContent":["import { useState, useRef, useCallback, useEffect } from 'react';\n\n/**\n * A hook for managing controllable state that can be either controlled (by parent via props)\n * or uncontrolled (managed internally). Handles bidirectional sync between external props\n * and internal state without causing infinite loops.\n *\n * Key design decisions:\n * - Returns the raw useState setter → always stable reference, safe for useMemo deps\n * - Uses a version counter to distinguish internal vs external state changes\n * - onChange is called via useEffect (not inside state updater) for React safety\n * - onChangeRef avoids stale closures for the onChange callback\n *\n * @param externalValue - The value from props (controlled mode when onChange is provided)\n * @param onChange - Optional callback to notify parent of internal state changes\n * @param isEqual - Optional equality check (defaults to reference equality)\n * @returns [currentValue, setValue] - The current value and a stable setter\n */\nexport function useControllableState<T>(\n externalValue: T,\n onChange?: (value: T) => void,\n isEqual: (a: T, b: T) => boolean = (a, b) => a === b\n): [T, React.Dispatch<React.SetStateAction<T>>] {\n const [internalValue, setInternalValue] = useState<T>(externalValue);\n\n // Always-current ref for onChange to avoid stale closures\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n // Track external value to detect parent-driven changes\n const prevExternalRef = useRef<T>(externalValue);\n\n // Version counter: incremented only by internal (user-driven) changes\n const internalVersionRef = useRef(0);\n const notifiedVersionRef = useRef(0);\n\n // Sync external → internal (during render, no extra render cycle)\n if (!isEqual(prevExternalRef.current, externalValue)) {\n prevExternalRef.current = externalValue;\n setInternalValue(externalValue);\n }\n\n // Wrap setter to track internal changes via version counter\n const setValue = useCallback(\n (updater: React.SetStateAction<T>) => {\n internalVersionRef.current++;\n setInternalValue(updater);\n },\n []\n );\n\n // Notify parent only when value changed from an internal action (not external sync)\n const isFirstRenderRef = useRef(true);\n useEffect(() => {\n if (isFirstRenderRef.current) {\n isFirstRenderRef.current = false;\n return;\n }\n if (internalVersionRef.current !== notifiedVersionRef.current) {\n notifiedVersionRef.current = internalVersionRef.current;\n onChangeRef.current?.(internalValue);\n }\n }, [internalValue]);\n\n return [internalValue, setValue];\n}\n\n/**\n * Structural deep equality for the plain values used by DataTable state\n * (arrays, plain objects, primitives). Avoids the overhead and edge cases of\n * `JSON.stringify` (key ordering, `undefined`, functions) that the previous\n * implementation relied on.\n */\nexport function deepEqual<T>(a: T, b: T): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {\n return false;\n }\n\n const aIsArray = Array.isArray(a);\n const bIsArray = Array.isArray(b);\n if (aIsArray !== bIsArray) return false;\n\n if (aIsArray && bIsArray) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":";;AAkBA,SAAgB,EACZ,GACA,GACA,KAAoC,GAAG,MAAM,MAAM,GACP;CAC5C,IAAM,CAAC,GAAe,KAAoB,EAAY,CAAa,GAG7D,IAAc,EAAO,CAAQ;CACnC,EAAY,UAAU;CAGtB,IAAM,IAAkB,EAAU,CAAa,GAGzC,IAAqB,EAAO,CAAC,GAC7B,IAAqB,EAAO,CAAC;CAGnC,AAAK,EAAQ,EAAgB,SAAS,CAAa,MAC/C,EAAgB,UAAU,GAC1B,EAAiB,CAAa;CAIlC,IAAM,IAAW,GACZ,MAAqC;EAElC,AADA,EAAmB,WACnB,EAAiB,CAAO;CAC5B,GACA,CAAC,CACL,GAGM,IAAmB,EAAO,EAAI;CAYpC,OAXA,QAAgB;EACZ,IAAI,EAAiB,SAAS;GAC1B,EAAiB,UAAU;GAC3B;EACJ;EACA,AAAI,EAAmB,YAAY,EAAmB,YAClD,EAAmB,UAAU,EAAmB,SAChD,EAAY,UAAU,CAAa;CAE3C,GAAG,CAAC,CAAa,CAAC,GAEX,CAAC,GAAe,CAAQ;AACnC;AAQA,SAAgB,EAAa,GAAM,GAAe;CAC9C,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAC5B,IAAI,OAAO,KAAM,aAAY,KAAc,OAAO,KAAM,aAAY,GAChE,OAAO;CAGX,IAAM,IAAW,MAAM,QAAQ,CAAC,GAC1B,IAAW,MAAM,QAAQ,CAAC;CAChC,IAAI,MAAa,GAAU,OAAO;CAElC,IAAI,KAAY,GAAU;EACtB,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;EAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC1B,IAAI,CAAC,EAAU,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO;EAEvC,OAAO;CACX;CAEA,IAAM,IAAQ,OAAO,KAAK,CAA4B,GAChD,IAAQ,OAAO,KAAK,CAA4B;CACtD,IAAI,EAAM,WAAW,EAAM,QAAQ,OAAO;CAC1C,KAAK,IAAM,KAAO,GAEd,IADI,CAAC,OAAO,UAAU,eAAe,KAAK,GAAG,CAAG,KAC5C,CAAC,EAAW,EAA8B,IAAO,EAA8B,EAAI,GACnF,OAAO;CAGf,OAAO;AACX"}
1
+ {"version":3,"file":"useControllableState.js","names":[],"sources":["../../src/hooks/useControllableState.ts"],"sourcesContent":["import { useState, useRef, useCallback, useLayoutEffect } from 'react';\n\n/**\n * A defined externalValue is strictly controlled, even without onChange (read-only).\n * undefined opts into internal state; defaultValue is read only on mount.\n * onChange reports distinct setter requests synchronously, never prop updates.\n * Controlled functional updates use the last committed prop, not a pending request.\n * Do not switch modes: switching back resumes the retained internal state.\n */\nexport function useControllableState<T>(\n externalValue: T,\n onChange?: (value: T) => void,\n isEqual?: (a: T, b: T) => boolean,\n): [T, React.Dispatch<React.SetStateAction<T>>];\nexport function useControllableState<T>(\n externalValue: T | undefined,\n onChange: ((value: T) => void) | undefined,\n isEqual: ((a: T, b: T) => boolean) | undefined,\n defaultValue: T,\n): [T, React.Dispatch<React.SetStateAction<T>>];\nexport function useControllableState<T>(\n externalValue: T | undefined,\n onChange?: (value: T) => void,\n isEqual: (a: T, b: T) => boolean = Object.is,\n defaultValue?: T,\n): [T, React.Dispatch<React.SetStateAction<T>>] {\n const [internalValue, setInternalValue] = useState<T>(() =>\n externalValue !== undefined ? externalValue : defaultValue as T,\n );\n const controlled = externalValue !== undefined;\n const value = controlled ? externalValue : internalValue;\n const committed = useRef({ value, controlled, onChange, isEqual });\n\n // Abandoned renders must not publish props/callbacks to the stable setter.\n useLayoutEffect(() => {\n committed.current = { value, controlled, onChange, isEqual };\n });\n\n const setValue = useCallback((updater: React.SetStateAction<T>) => {\n const current = committed.current;\n const next = typeof updater === 'function'\n ? (updater as (previous: T) => T)(current.value)\n : updater;\n if (current.isEqual(current.value, next)) return;\n if (!current.controlled) {\n // Resolve outside React's updater so batched requests compose without\n // replaying notifications in StrictMode.\n committed.current = { ...current, value: next };\n setInternalValue(() => next);\n }\n current.onChange?.(next);\n }, []);\n\n return [value, setValue];\n}\n\n/**\n * Structural deep equality for the plain values used by DataTable state\n * (arrays, plain objects, primitives). Avoids the overhead and edge cases of\n * `JSON.stringify` (key ordering, `undefined`, functions) that the previous\n * implementation relied on.\n */\nexport function deepEqual<T>(a: T, b: T): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {\n return false;\n }\n\n const aIsArray = Array.isArray(a);\n const bIsArray = Array.isArray(b);\n if (aIsArray !== bIsArray) return false;\n\n if (aIsArray && bIsArray) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":";;AAoBA,SAAgB,EACZ,GACA,GACA,IAAmC,OAAO,IAC1C,GAC4C;CAC5C,IAAM,CAAC,GAAe,KAAoB,QACtC,MAAkB,KAAA,IAA4B,IAAhB,CAClC,GACM,IAAa,MAAkB,KAAA,GAC/B,IAAQ,IAAa,IAAgB,GACrC,IAAY,EAAO;EAAE;EAAO;EAAY;EAAU;CAAQ,CAAC;CAsBjE,OAnBA,QAAsB;EAClB,EAAU,UAAU;GAAE;GAAO;GAAY;GAAU;EAAQ;CAC/D,CAAC,GAiBM,CAAC,GAfS,GAAa,MAAqC;EAC/D,IAAM,IAAU,EAAU,SACpB,IAAO,OAAO,KAAY,aACzB,EAA+B,EAAQ,KAAK,IAC7C;EACF,EAAQ,QAAQ,EAAQ,OAAO,CAAI,MAClC,EAAQ,eAGT,EAAU,UAAU;GAAE,GAAG;GAAS,OAAO;EAAK,GAC9C,QAAuB,CAAI,IAE/B,EAAQ,WAAW,CAAI;CAC3B,GAAG,CAAC,CAEW,CAAQ;AAC3B;AAQA,SAAgB,EAAa,GAAM,GAAe;CAC9C,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAC5B,IAAI,OAAO,KAAM,aAAY,KAAc,OAAO,KAAM,aAAY,GAChE,OAAO;CAGX,IAAM,IAAW,MAAM,QAAQ,CAAC,GAC1B,IAAW,MAAM,QAAQ,CAAC;CAChC,IAAI,MAAa,GAAU,OAAO;CAElC,IAAI,KAAY,GAAU;EACtB,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;EAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC1B,IAAI,CAAC,EAAU,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO;EAEvC,OAAO;CACX;CAEA,IAAM,IAAQ,OAAO,KAAK,CAA4B,GAChD,IAAQ,OAAO,KAAK,CAA4B;CACtD,IAAI,EAAM,WAAW,EAAM,QAAQ,OAAO;CAC1C,KAAK,IAAM,KAAO,GAEd,IADI,CAAC,OAAO,UAAU,eAAe,KAAK,GAAG,CAAG,KAC5C,CAAC,EAAW,EAA8B,IAAO,EAA8B,EAAI,GACnF,OAAO;CAGf,OAAO;AACX"}
@@ -67,8 +67,8 @@ export interface ColumnConfig<T extends object = any> {
67
67
  * ]);
68
68
  * ```
69
69
  *
70
- * @param configs - 列配置数组
71
- * @param deps - 额外依赖项(当 cell 渲染器依赖外部状态时使用)
70
+ * @param configs - 列配置数组;按引用跟踪更新。需要缓存时由调用方用 useMemo 稳定配置。
71
+ * @param deps - 额外依赖项,不能代替 configs;每次渲染保持依赖项数量不变。
72
72
  */
73
- export declare function useTableColumns<T extends object>(configs: ColumnConfig<T>[], deps?: any[]): ColumnDef<T, any>[];
73
+ export declare function useTableColumns<T extends object>(configs: ColumnConfig<T>[], deps?: readonly unknown[]): ColumnDef<T, any>[];
74
74
  export default useTableColumns;
@@ -1,4 +1,4 @@
1
- import { createActionColumn as e, createColumn as t, createComputedColumn as n, createSelectionColumn as r, dateSort as i, numericSort as a } from "../data-table.js";
1
+ import { createActionColumn as e, createColumn as t, createComputedColumn as n, createSelectionColumn as r, dateSort as i, numericSort as a } from "../components/pro/DataTable/utils.js";
2
2
  import { useMemo as o } from "react";
3
3
  //#region src/hooks/useTableColumns.ts
4
4
  function s(o) {
@@ -35,7 +35,7 @@ function s(o) {
35
35
  return l === "computed" ? n(s.startsWith("_") ? s.slice(1) : s, c, g, x) : t(s, c, x);
36
36
  }
37
37
  function c(e, t = []) {
38
- return o(() => e.map(s), t);
38
+ return o(() => e.map(s), [e, ...t]);
39
39
  }
40
40
  //#endregion
41
41
  export { c as default, c as useTableColumns };
@@ -1 +1 @@
1
- {"version":3,"file":"useTableColumns.js","names":[],"sources":["../../src/hooks/useTableColumns.ts"],"sourcesContent":["import { useMemo, ReactNode } from 'react';\nimport { ColumnDef, Row, SortingFn } from '../components/pro/DataTable/tanstack-table';\nimport {\n createColumn,\n createComputedColumn,\n createActionColumn,\n createSelectionColumn,\n numericSort,\n dateSort,\n stringSort,\n} from '../components/pro/DataTable';\n\n/**\n * 列筛选配置\n */\nexport interface ColumnFilterConfig {\n /** 筛选类型 */\n type: 'text' | 'select' | 'number' | 'dateRange';\n /** 下拉选项(仅 select 类型使用) */\n options?: { value: string; label: string }[];\n}\n\n/**\n * 列配置项(JSON 驱动)\n */\nexport interface ColumnConfig<T extends object = any> {\n /** 字段名 / accessorKey。特殊值: '_selection' 自动创建选择列 */\n key: string;\n /** 列标题 */\n header: string;\n /**\n * 列类型\n * - 'text' 普通文本列(默认)\n * - 'number' 数值列,自动使用 numericSort\n * - 'date' 日期列,自动使用 dateSort\n * - 'computed' 计算列,需提供 accessorFn\n * - 'action' 操作列,禁用排序\n * - 'selection' 选择列(含全选复选框)\n */\n type?: 'text' | 'number' | 'date' | 'computed' | 'action' | 'selection';\n /** 是否可排序,默认 true(action/selection 默认 false) */\n sortable?: boolean;\n /** 自定义单元格渲染 */\n cell?: ((info: any) => ReactNode) | ((props: { row: Row<T> }) => ReactNode);\n /** 筛选配置 */\n filter?: ColumnFilterConfig;\n /** 列宽 */\n width?: number;\n /** 最小列宽 */\n minWidth?: number;\n /** 最大列宽 */\n maxWidth?: number;\n /** 计算列的 accessorFn(仅 type='computed' 时使用) */\n accessorFn?: (row: T) => any;\n /** 自定义排序函数 */\n sortingFn?: SortingFn<any>;\n /** 任意额外选项,会被合并到最终 ColumnDef 中 */\n extra?: Partial<ColumnDef<T, any>>;\n}\n\n/**\n * 将单个 ColumnConfig 转换为 ColumnDef\n */\nfunction buildColumnDef<T extends object>(config: ColumnConfig<T>): ColumnDef<T, any> {\n const { key, header, type = 'text', sortable, cell, filter, width, minWidth, maxWidth, accessorFn, sortingFn, extra } = config;\n\n // 选择列\n if (type === 'selection') {\n return createSelectionColumn<T>(extra);\n }\n\n // 操作列\n if (type === 'action') {\n return createActionColumn<T>({\n id: key.startsWith('_') ? 'actions' : key,\n header,\n cell: cell as any,\n ...(width !== undefined ? { size: width } : {}),\n ...(minWidth !== undefined ? { minSize: minWidth } : {}),\n ...(maxWidth !== undefined ? { maxSize: maxWidth } : {}),\n ...extra,\n });\n }\n\n // 构建 meta(筛选信息)\n const meta: Record<string, any> = {};\n if (filter) {\n meta.filterType = filter.type;\n if (filter.options) {\n meta.filterOptions = filter.options;\n }\n }\n\n // 自动选择排序函数\n let resolvedSortingFn: SortingFn<any> | undefined = sortingFn;\n if (!resolvedSortingFn) {\n switch (type) {\n case 'number':\n resolvedSortingFn = numericSort;\n break;\n case 'date':\n resolvedSortingFn = dateSort;\n break;\n // 'text' 和 'computed' 使用默认排序\n }\n }\n\n // 基础选项(v9:使用 sortFn)\n const baseOptions: Partial<ColumnDef<T, any>> = {\n ...(cell !== undefined ? { cell: cell as any } : {}),\n ...(resolvedSortingFn ? { sortFn: resolvedSortingFn } : {}),\n ...(sortable === false ? { enableSorting: false } : {}),\n ...(Object.keys(meta).length > 0 ? { meta } : {}),\n ...(width !== undefined ? { size: width } : {}),\n ...(minWidth !== undefined ? { minSize: minWidth } : {}),\n ...(maxWidth !== undefined ? { maxSize: maxWidth } : {}),\n ...extra,\n };\n\n // 计算列\n if (type === 'computed') {\n return createComputedColumn<T>(\n key.startsWith('_') ? key.slice(1) : key,\n header,\n accessorFn,\n baseOptions\n );\n }\n\n // 普通列 / number / date\n return createColumn<T>(key, header, baseOptions);\n}\n\n/**\n * useTableColumns - 通过 JSON 配置数组快速创建 DataTable 列定义\n *\n * @example\n * ```tsx\n * const columns = useTableColumns<Order>([\n * { key: '_selection', type: 'selection', header: '' },\n * { key: 'orderNumber', header: '订单编号', filter: { type: 'text' } },\n * { key: 'total', header: '总金额', type: 'number',\n * cell: info => `¥${info.getValue<number>().toFixed(2)}`,\n * filter: { type: 'number' } },\n * { key: '_actions', type: 'action', header: '操作',\n * cell: ({ row }) => <button>查看</button> }\n * ]);\n * ```\n *\n * @param configs - 列配置数组\n * @param deps - 额外依赖项(当 cell 渲染器依赖外部状态时使用)\n */\nexport function useTableColumns<T extends object>(\n configs: ColumnConfig<T>[],\n deps: any[] = []\n): ColumnDef<T, any>[] {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => configs.map(buildColumnDef), deps);\n}\n\nexport default useTableColumns;\n"],"mappings":";;;AA+DA,SAAS,EAAiC,GAA4C;CAClF,IAAM,EAAE,QAAK,WAAQ,UAAO,QAAQ,aAAU,SAAM,WAAQ,UAAO,aAAU,aAAU,eAAY,cAAW,aAAU;CAGxH,IAAI,MAAS,aACT,OAAO,EAAyB,CAAK;CAIzC,IAAI,MAAS,UACT,OAAO,EAAsB;EACzB,IAAI,EAAI,WAAW,GAAG,IAAI,YAAY;EACtC;EACM;EACN,GAAI,MAAU,KAAA,IAA8B,CAAC,IAAnB,EAAE,MAAM,EAAM;EACxC,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAG;CACP,CAAC;CAIL,IAAM,IAA4B,CAAC;CACnC,AAAI,MACA,EAAK,aAAa,EAAO,MACrB,EAAO,YACP,EAAK,gBAAgB,EAAO;CAKpC,IAAI,IAAgD;CACpD,IAAI,CAAC,GACD,QAAQ,GAAR;EACI,KAAK;GACD,IAAoB;GACpB;EACJ,KAAK,QACD,IAAoB;CAG5B;CAIJ,IAAM,IAA0C;EAC5C,GAAI,MAAS,KAAA,IAAoC,CAAC,IAAzB,EAAQ,QAAY;EAC7C,GAAI,IAAoB,EAAE,QAAQ,EAAkB,IAAI,CAAC;EACzD,GAAI,MAAa,KAAQ,EAAE,eAAe,GAAM,IAAI,CAAC;EACrD,GAAI,OAAO,KAAK,CAAI,CAAC,CAAC,SAAS,IAAI,EAAE,QAAK,IAAI,CAAC;EAC/C,GAAI,MAAU,KAAA,IAA8B,CAAC,IAAnB,EAAE,MAAM,EAAM;EACxC,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAG;CACP;CAaA,OAVI,MAAS,aACF,EACH,EAAI,WAAW,GAAG,IAAI,EAAI,MAAM,CAAC,IAAI,GACrC,GACA,GACA,CACJ,IAIG,EAAgB,GAAK,GAAQ,CAAW;AACnD;AAqBA,SAAgB,EACZ,GACA,IAAc,CAAC,GACI;CAEnB,OAAO,QAAc,EAAQ,IAAI,CAAc,GAAG,CAAI;AAC1D"}
1
+ {"version":3,"file":"useTableColumns.js","names":[],"sources":["../../src/hooks/useTableColumns.ts"],"sourcesContent":["import { useMemo, ReactNode } from 'react';\nimport { ColumnDef, Row, SortingFn } from '../components/pro/DataTable/tanstack-table';\nimport {\n createColumn,\n createComputedColumn,\n createActionColumn,\n createSelectionColumn,\n numericSort,\n dateSort,\n} from '../components/pro/DataTable/utils';\n\n/**\n * 列筛选配置\n */\nexport interface ColumnFilterConfig {\n /** 筛选类型 */\n type: 'text' | 'select' | 'number' | 'dateRange';\n /** 下拉选项(仅 select 类型使用) */\n options?: { value: string; label: string }[];\n}\n\n/**\n * 列配置项(JSON 驱动)\n */\nexport interface ColumnConfig<T extends object = any> {\n /** 字段名 / accessorKey。特殊值: '_selection' 自动创建选择列 */\n key: string;\n /** 列标题 */\n header: string;\n /**\n * 列类型\n * - 'text' 普通文本列(默认)\n * - 'number' 数值列,自动使用 numericSort\n * - 'date' 日期列,自动使用 dateSort\n * - 'computed' 计算列,需提供 accessorFn\n * - 'action' 操作列,禁用排序\n * - 'selection' 选择列(含全选复选框)\n */\n type?: 'text' | 'number' | 'date' | 'computed' | 'action' | 'selection';\n /** 是否可排序,默认 true(action/selection 默认 false) */\n sortable?: boolean;\n /** 自定义单元格渲染 */\n cell?: ((info: any) => ReactNode) | ((props: { row: Row<T> }) => ReactNode);\n /** 筛选配置 */\n filter?: ColumnFilterConfig;\n /** 列宽 */\n width?: number;\n /** 最小列宽 */\n minWidth?: number;\n /** 最大列宽 */\n maxWidth?: number;\n /** 计算列的 accessorFn(仅 type='computed' 时使用) */\n accessorFn?: (row: T) => any;\n /** 自定义排序函数 */\n sortingFn?: SortingFn<any>;\n /** 任意额外选项,会被合并到最终 ColumnDef 中 */\n extra?: Partial<ColumnDef<T, any>>;\n}\n\n/**\n * 将单个 ColumnConfig 转换为 ColumnDef\n */\nfunction buildColumnDef<T extends object>(config: ColumnConfig<T>): ColumnDef<T, any> {\n const { key, header, type = 'text', sortable, cell, filter, width, minWidth, maxWidth, accessorFn, sortingFn, extra } = config;\n\n // 选择列\n if (type === 'selection') {\n return createSelectionColumn<T>(extra);\n }\n\n // 操作列\n if (type === 'action') {\n return createActionColumn<T>({\n id: key.startsWith('_') ? 'actions' : key,\n header,\n cell: cell as any,\n ...(width !== undefined ? { size: width } : {}),\n ...(minWidth !== undefined ? { minSize: minWidth } : {}),\n ...(maxWidth !== undefined ? { maxSize: maxWidth } : {}),\n ...extra,\n });\n }\n\n // 构建 meta(筛选信息)\n const meta: Record<string, any> = {};\n if (filter) {\n meta.filterType = filter.type;\n if (filter.options) {\n meta.filterOptions = filter.options;\n }\n }\n\n // 自动选择排序函数\n let resolvedSortingFn: SortingFn<any> | undefined = sortingFn;\n if (!resolvedSortingFn) {\n switch (type) {\n case 'number':\n resolvedSortingFn = numericSort;\n break;\n case 'date':\n resolvedSortingFn = dateSort;\n break;\n // 'text' 和 'computed' 使用默认排序\n }\n }\n\n // 基础选项(v9:使用 sortFn)\n const baseOptions: Partial<ColumnDef<T, any>> = {\n ...(cell !== undefined ? { cell: cell as any } : {}),\n ...(resolvedSortingFn ? { sortFn: resolvedSortingFn } : {}),\n ...(sortable === false ? { enableSorting: false } : {}),\n ...(Object.keys(meta).length > 0 ? { meta } : {}),\n ...(width !== undefined ? { size: width } : {}),\n ...(minWidth !== undefined ? { minSize: minWidth } : {}),\n ...(maxWidth !== undefined ? { maxSize: maxWidth } : {}),\n ...extra,\n };\n\n // 计算列\n if (type === 'computed') {\n return createComputedColumn<T>(\n key.startsWith('_') ? key.slice(1) : key,\n header,\n accessorFn,\n baseOptions\n );\n }\n\n // 普通列 / number / date\n return createColumn<T>(key, header, baseOptions);\n}\n\n/**\n * useTableColumns - 通过 JSON 配置数组快速创建 DataTable 列定义\n *\n * @example\n * ```tsx\n * const columns = useTableColumns<Order>([\n * { key: '_selection', type: 'selection', header: '' },\n * { key: 'orderNumber', header: '订单编号', filter: { type: 'text' } },\n * { key: 'total', header: '总金额', type: 'number',\n * cell: info => `¥${info.getValue<number>().toFixed(2)}`,\n * filter: { type: 'number' } },\n * { key: '_actions', type: 'action', header: '操作',\n * cell: ({ row }) => <button>查看</button> }\n * ]);\n * ```\n *\n * @param configs - 列配置数组;按引用跟踪更新。需要缓存时由调用方用 useMemo 稳定配置。\n * @param deps - 额外依赖项,不能代替 configs;每次渲染保持依赖项数量不变。\n */\nexport function useTableColumns<T extends object>(\n configs: ColumnConfig<T>[],\n deps: readonly unknown[] = []\n): ColumnDef<T, any>[] {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => configs.map(buildColumnDef), [configs, ...deps]);\n}\n\nexport default useTableColumns;\n"],"mappings":";;;AA8DA,SAAS,EAAiC,GAA4C;CAClF,IAAM,EAAE,QAAK,WAAQ,UAAO,QAAQ,aAAU,SAAM,WAAQ,UAAO,aAAU,aAAU,eAAY,cAAW,aAAU;CAGxH,IAAI,MAAS,aACT,OAAO,EAAyB,CAAK;CAIzC,IAAI,MAAS,UACT,OAAO,EAAsB;EACzB,IAAI,EAAI,WAAW,GAAG,IAAI,YAAY;EACtC;EACM;EACN,GAAI,MAAU,KAAA,IAA8B,CAAC,IAAnB,EAAE,MAAM,EAAM;EACxC,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAG;CACP,CAAC;CAIL,IAAM,IAA4B,CAAC;CACnC,AAAI,MACA,EAAK,aAAa,EAAO,MACrB,EAAO,YACP,EAAK,gBAAgB,EAAO;CAKpC,IAAI,IAAgD;CACpD,IAAI,CAAC,GACD,QAAQ,GAAR;EACI,KAAK;GACD,IAAoB;GACpB;EACJ,KAAK,QACD,IAAoB;CAG5B;CAIJ,IAAM,IAA0C;EAC5C,GAAI,MAAS,KAAA,IAAoC,CAAC,IAAzB,EAAQ,QAAY;EAC7C,GAAI,IAAoB,EAAE,QAAQ,EAAkB,IAAI,CAAC;EACzD,GAAI,MAAa,KAAQ,EAAE,eAAe,GAAM,IAAI,CAAC;EACrD,GAAI,OAAO,KAAK,CAAI,CAAC,CAAC,SAAS,IAAI,EAAE,QAAK,IAAI,CAAC;EAC/C,GAAI,MAAU,KAAA,IAA8B,CAAC,IAAnB,EAAE,MAAM,EAAM;EACxC,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAI,MAAa,KAAA,IAAoC,CAAC,IAAzB,EAAE,SAAS,EAAS;EACjD,GAAG;CACP;CAaA,OAVI,MAAS,aACF,EACH,EAAI,WAAW,GAAG,IAAI,EAAI,MAAM,CAAC,IAAI,GACrC,GACA,GACA,CACJ,IAIG,EAAgB,GAAK,GAAQ,CAAW;AACnD;AAqBA,SAAgB,EACZ,GACA,IAA2B,CAAC,GACT;CAEnB,OAAO,QAAc,EAAQ,IAAI,CAAc,GAAG,CAAC,GAAS,GAAG,CAAI,CAAC;AACxE"}
@@ -3,7 +3,7 @@ import { ColumnFiltersState, SortingState, RowSelectionState } from '../componen
3
3
  * useTableData 配置项
4
4
  */
5
5
  export interface UseTableDataOptions<T extends object> {
6
- /** 原始数据数组 */
6
+ /** 原始数据数组。未提供 getRowId 时使用此数组的索引作为 ID;重排、插删或刷新数据时应提供稳定业务 ID。 */
7
7
  data: T[];
8
8
  /** 初始排序状态 */
9
9
  initialSort?: SortingState;
@@ -11,9 +11,9 @@ export interface UseTableDataOptions<T extends object> {
11
11
  initialFilters?: ColumnFiltersState;
12
12
  /** 初始每页行数,默认 10 */
13
13
  initialPageSize?: number;
14
- /** 自定义筛选函数(客户端过滤)。若不提供则将原始数据直接传给 DataTable 做内置过滤 */
14
+ /** 自定义筛选函数(有筛选条件时执行)。提供后 tableProps.manualFiltering=true,避免 DataTable 二次过滤。 */
15
15
  filterFn?: (item: T, filters: ColumnFiltersState) => boolean;
16
- /** 获取行 ID 的函数 */
16
+ /** 获取唯一行 ID;index 是筛选前原始 data 的索引,而不是 tableData 的索引。 */
17
17
  getRowId?: (row: T, index: number) => string;
18
18
  }
19
19
  /**
@@ -22,18 +22,24 @@ export interface UseTableDataOptions<T extends object> {
22
22
  export interface UseTableDataReturn<T extends object> {
23
23
  /** 经过筛选后的数据(如果提供了 filterFn) */
24
24
  tableData: T[];
25
+ /** 是否由此 Hook 负责筛选;同时包含在 tableProps 中。 */
26
+ manualFiltering: boolean;
25
27
  sorting: SortingState;
26
28
  onSortingChange: (sorting: SortingState) => void;
27
29
  columnFilters: ColumnFiltersState;
28
30
  onColumnFiltersChange: (filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)) => void;
29
31
  pageIndex: number;
30
32
  pageSize: number;
33
+ /** Math.ceil(tableData.length / pageSize)。提供 filterFn 时是自定义筛选后的页数;
34
+ * 未提供时仅是原始数据页数,不包含 DataTable 的内置筛选(此 Hook 没有 columns,无法精确计算)。
35
+ * 内置筛选后的实际页数以 DataTable 的 table.getPageCount() 为准,不要将此值当作服务端总页数传入。
36
+ */
31
37
  pageCount: number;
32
38
  onPageChange: (page: number) => void;
33
39
  onPageSizeChange: (size: number) => void;
34
40
  selectedRows: RowSelectionState;
35
41
  onRowSelectionChange: (selection: RowSelectionState) => void;
36
- /** 当前选中的数据行(便捷字段) */
42
+ /** tableData 中选中的行,按输入顺序返回;自定义筛选隐藏的选中 ID 保留在 selectedRows,但不在此数组中。 */
37
43
  selectedData: T[];
38
44
  /**
39
45
  * 合并好的 props,可直接展开到 <DataTable>
@@ -54,6 +60,7 @@ export interface UseTableDataReturn<T extends object> {
54
60
  getRowId?: (row: T, index: number) => string;
55
61
  enableSorting: boolean;
56
62
  enableFiltering: boolean;
63
+ manualFiltering: boolean;
57
64
  enablePagination: boolean;
58
65
  enableSelection: boolean;
59
66
  };
@@ -2,65 +2,83 @@ import { useCallback as e, useMemo as t, useState as n } from "react";
2
2
  //#region src/hooks/useTableData.ts
3
3
  function r(r) {
4
4
  let { data: i, initialSort: a = [], initialFilters: o = [], initialPageSize: s = 10, filterFn: c, getRowId: l } = r, [u, d] = n(a), [f, p] = n(o), [m, h] = n(0), [g, _] = n(s), [v, y] = n({}), b = e((e) => {
5
- h(0), p(e);
5
+ h(0), d(e);
6
6
  }, []), x = e((e) => {
7
- h(e);
7
+ h(0), p(e);
8
8
  }, []), S = e((e) => {
9
+ h(e);
10
+ }, []), C = e((e) => {
9
11
  _(e), h(0);
10
- }, []), C = t(() => !c || f.length === 0 ? i : i.filter((e) => c(e, f)), [
12
+ }, []), { tableData: w, sourceIndexes: T } = t(() => {
13
+ if (!c || f.length === 0) return {
14
+ tableData: i,
15
+ sourceIndexes: i.map((e, t) => t)
16
+ };
17
+ let e = [], t = [];
18
+ return i.forEach((n, r) => {
19
+ c(n, f) && (e.push(n), t.push(r));
20
+ }), {
21
+ tableData: e,
22
+ sourceIndexes: t
23
+ };
24
+ }, [
11
25
  i,
12
26
  f,
13
27
  c
14
- ]);
28
+ ]), E = !!c, D = e((e, t) => {
29
+ let n = T[t] ?? t;
30
+ return l ? l(e, n) : String(n);
31
+ }, [T, l]);
15
32
  return {
16
- tableData: C,
33
+ tableData: w,
34
+ manualFiltering: E,
17
35
  sorting: u,
18
- onSortingChange: d,
36
+ onSortingChange: b,
19
37
  columnFilters: f,
20
- onColumnFiltersChange: b,
38
+ onColumnFiltersChange: x,
21
39
  pageIndex: m,
22
40
  pageSize: g,
23
- pageCount: t(() => Math.ceil(C.length / g), [C.length, g]),
24
- onPageChange: x,
25
- onPageSizeChange: S,
41
+ pageCount: t(() => Math.ceil(w.length / g), [w.length, g]),
42
+ onPageChange: S,
43
+ onPageSizeChange: C,
26
44
  selectedRows: v,
27
45
  onRowSelectionChange: y,
28
- selectedData: t(() => l ? C.filter((e, t) => {
29
- let n = l(e, t);
30
- return v[n];
31
- }) : Object.keys(v).filter((e) => v[e]).map((e) => C[Number(e)]).filter(Boolean), [
46
+ selectedData: t(() => w.filter((e, t) => v[D(e, t)]), [
32
47
  v,
33
- C,
34
- l
48
+ w,
49
+ D
35
50
  ]),
36
51
  tableProps: t(() => ({
37
- data: C,
52
+ data: w,
38
53
  sortingState: u,
39
- onSortingChange: d,
54
+ onSortingChange: b,
40
55
  columnFilters: f,
41
- onColumnFiltersChange: b,
56
+ onColumnFiltersChange: x,
42
57
  pageIndex: m,
43
58
  pageSize: g,
44
- onPageChange: x,
45
- onPageSizeChange: S,
59
+ onPageChange: S,
60
+ onPageSizeChange: C,
46
61
  selectedRows: v,
47
62
  onRowSelectionChange: y,
48
- ...l ? { getRowId: l } : {},
63
+ getRowId: D,
49
64
  enableSorting: !0,
50
65
  enableFiltering: !0,
66
+ manualFiltering: E,
51
67
  enablePagination: !0,
52
68
  enableSelection: !0
53
69
  }), [
54
- C,
70
+ w,
55
71
  u,
56
- f,
57
72
  b,
73
+ f,
74
+ x,
58
75
  m,
59
76
  g,
60
- x,
61
77
  S,
78
+ C,
62
79
  v,
63
- l
80
+ D,
81
+ E
64
82
  ])
65
83
  };
66
84
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useTableData.js","names":[],"sources":["../../src/hooks/useTableData.ts"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport {\n ColumnFiltersState,\n SortingState,\n RowSelectionState,\n} from '../components/pro/DataTable/tanstack-table';\n\n/**\n * useTableData 配置项\n */\nexport interface UseTableDataOptions<T extends object> {\n /** 原始数据数组 */\n data: T[];\n /** 初始排序状态 */\n initialSort?: SortingState;\n /** 初始筛选状态 */\n initialFilters?: ColumnFiltersState;\n /** 初始每页行数,默认 10 */\n initialPageSize?: number;\n /** 自定义筛选函数(客户端过滤)。若不提供则将原始数据直接传给 DataTable 做内置过滤 */\n filterFn?: (item: T, filters: ColumnFiltersState) => boolean;\n /** 获取行 ID 的函数 */\n getRowId?: (row: T, index: number) => string;\n}\n\n/**\n * useTableData 返回值\n */\nexport interface UseTableDataReturn<T extends object> {\n // 数据\n /** 经过筛选后的数据(如果提供了 filterFn) */\n tableData: T[];\n\n // 排序\n sorting: SortingState;\n onSortingChange: (sorting: SortingState) => void;\n\n // 筛选\n columnFilters: ColumnFiltersState;\n onColumnFiltersChange: (\n filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)\n ) => void;\n\n // 分页\n pageIndex: number;\n pageSize: number;\n pageCount: number;\n onPageChange: (page: number) => void;\n onPageSizeChange: (size: number) => void;\n\n // 选择\n selectedRows: RowSelectionState;\n onRowSelectionChange: (selection: RowSelectionState) => void;\n /** 当前选中的数据行(便捷字段) */\n selectedData: T[];\n\n /**\n * 合并好的 props,可直接展开到 <DataTable>\n * @example <DataTable columns={columns} {...tableProps} />\n */\n tableProps: {\n data: T[];\n sortingState: SortingState;\n onSortingChange: (sorting: SortingState) => void;\n columnFilters: ColumnFiltersState;\n onColumnFiltersChange: (\n filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)\n ) => void;\n pageIndex: number;\n pageSize: number;\n onPageChange: (page: number) => void;\n onPageSizeChange: (size: number) => void;\n selectedRows: RowSelectionState;\n onRowSelectionChange: (selection: RowSelectionState) => void;\n getRowId?: (row: T, index: number) => string;\n enableSorting: boolean;\n enableFiltering: boolean;\n enablePagination: boolean;\n enableSelection: boolean;\n };\n}\n\n/**\n * useTableData - 封装 DataTable 的数据状态管理\n *\n * 将排序、筛选、分页、选择等状态统一管理,\n * 返回的 `tableProps` 可以直接展开到 `<DataTable>` 上。\n *\n * @example\n * ```tsx\n * const { tableProps, selectedData } = useTableData({\n * data: orders,\n * initialSort: [{ id: 'orderDate', desc: true }],\n * initialPageSize: 5,\n * });\n *\n * <DataTable columns={columns} {...tableProps} />\n * ```\n */\nexport function useTableData<T extends object>(\n options: UseTableDataOptions<T>\n): UseTableDataReturn<T> {\n const {\n data,\n initialSort = [],\n initialFilters = [],\n initialPageSize = 10,\n filterFn,\n getRowId,\n } = options;\n\n // ─── 排序 ───\n const [sorting, setSorting] = useState<SortingState>(initialSort);\n\n // ─── 筛选 ───\n const [columnFilters, setColumnFiltersRaw] = useState<ColumnFiltersState>(initialFilters);\n\n // ─── 分页 ───\n const [pageIndex, setPageIndex] = useState(0);\n const [pageSize, setPageSize] = useState(initialPageSize);\n\n // ─── 选择 ───\n const [selectedRows, setSelectedRows] = useState<RowSelectionState>({});\n\n // ─── 当筛选变化时重置到第一页 ───\n const onColumnFiltersChange = useCallback(\n (updater: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)) => {\n setPageIndex(0);\n setColumnFiltersRaw(updater);\n },\n []\n );\n\n // ─── 分页回调 ───\n const onPageChange = useCallback((page: number) => {\n setPageIndex(page);\n }, []);\n\n const onPageSizeChange = useCallback((size: number) => {\n setPageSize(size);\n setPageIndex(0);\n }, []);\n\n // ─── 客户端过滤(仅当提供了 filterFn 时) ───\n const tableData = useMemo(() => {\n if (!filterFn || columnFilters.length === 0) {\n return data;\n }\n return data.filter((item) => filterFn(item, columnFilters));\n }, [data, columnFilters, filterFn]);\n\n // ─── 页数 ───\n const pageCount = useMemo(\n () => Math.ceil(tableData.length / pageSize),\n [tableData.length, pageSize]\n );\n\n // ─── 选中数据便捷字段 ───\n const selectedData = useMemo(() => {\n if (!getRowId) {\n // 用索引作为 key\n return Object.keys(selectedRows)\n .filter((k) => selectedRows[k])\n .map((k) => tableData[Number(k)])\n .filter(Boolean);\n }\n return tableData.filter((item, idx) => {\n const id = getRowId(item, idx);\n return selectedRows[id];\n });\n }, [selectedRows, tableData, getRowId]);\n\n // ─── 合并 props ───\n const tableProps = useMemo(\n () => ({\n data: tableData,\n sortingState: sorting,\n onSortingChange: setSorting,\n columnFilters,\n onColumnFiltersChange,\n pageIndex,\n pageSize,\n onPageChange,\n onPageSizeChange,\n selectedRows,\n onRowSelectionChange: setSelectedRows,\n ...(getRowId ? { getRowId } : {}),\n enableSorting: true,\n enableFiltering: true,\n enablePagination: true,\n enableSelection: true,\n }),\n [\n tableData,\n sorting,\n columnFilters,\n onColumnFiltersChange,\n pageIndex,\n pageSize,\n onPageChange,\n onPageSizeChange,\n selectedRows,\n getRowId,\n ]\n );\n\n return {\n tableData,\n sorting,\n onSortingChange: setSorting,\n columnFilters,\n onColumnFiltersChange,\n pageIndex,\n pageSize,\n pageCount,\n onPageChange,\n onPageSizeChange,\n selectedRows,\n onRowSelectionChange: setSelectedRows,\n selectedData,\n tableProps,\n };\n}\n\nexport default useTableData;\n"],"mappings":";;AAmGA,SAAgB,EACZ,GACqB;CACrB,IAAM,EACF,SACA,iBAAc,CAAC,GACf,oBAAiB,CAAC,GAClB,qBAAkB,IAClB,aACA,gBACA,GAGE,CAAC,GAAS,KAAc,EAAuB,CAAW,GAG1D,CAAC,GAAe,KAAuB,EAA6B,CAAc,GAGlF,CAAC,GAAW,KAAgB,EAAS,CAAC,GACtC,CAAC,GAAU,KAAe,EAAS,CAAe,GAGlD,CAAC,GAAc,KAAmB,EAA4B,CAAC,CAAC,GAGhE,IAAwB,GACzB,MAAqF;EAElF,AADA,EAAa,CAAC,GACd,EAAoB,CAAO;CAC/B,GACA,CAAC,CACL,GAGM,IAAe,GAAa,MAAiB;EAC/C,EAAa,CAAI;CACrB,GAAG,CAAC,CAAC,GAEC,IAAmB,GAAa,MAAiB;EAEnD,AADA,EAAY,CAAI,GAChB,EAAa,CAAC;CAClB,GAAG,CAAC,CAAC,GAGC,IAAY,QACV,CAAC,KAAY,EAAc,WAAW,IAC/B,IAEJ,EAAK,QAAQ,MAAS,EAAS,GAAM,CAAa,CAAC,GAC3D;EAAC;EAAM;EAAe;CAAQ,CAAC;CAyDlC,OAAO;EACH;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;EACA,WA9Dc,QACR,KAAK,KAAK,EAAU,SAAS,CAAQ,GAC3C,CAAC,EAAU,QAAQ,CAAQ,CA4D3B;EACA;EACA;EACA;EACA,sBAAsB;EACtB,cA7DiB,QACZ,IAOE,EAAU,QAAQ,GAAM,MAAQ;GACnC,IAAM,IAAK,EAAS,GAAM,CAAG;GAC7B,OAAO,EAAa;EACxB,CAAC,IARU,OAAO,KAAK,CAAY,CAAC,CAC3B,QAAQ,MAAM,EAAa,EAAE,CAAC,CAC9B,KAAK,MAAM,EAAU,OAAO,CAAC,EAAE,CAAC,CAChC,OAAO,OAAO,GAMxB;GAAC;GAAc;GAAW;EAAQ,CAiDjC;EACA,YA/Ce,SACR;GACH,MAAM;GACN,cAAc;GACd,iBAAiB;GACjB;GACA;GACA;GACA;GACA;GACA;GACA;GACA,sBAAsB;GACtB,GAAI,IAAW,EAAE,YAAS,IAAI,CAAC;GAC/B,eAAe;GACf,iBAAiB;GACjB,kBAAkB;GAClB,iBAAiB;EACrB,IACA;GACI;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACJ,CAiBA;CACJ;AACJ"}
1
+ {"version":3,"file":"useTableData.js","names":[],"sources":["../../src/hooks/useTableData.ts"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport {\n ColumnFiltersState,\n SortingState,\n RowSelectionState,\n} from '../components/pro/DataTable/tanstack-table';\n\n/**\n * useTableData 配置项\n */\nexport interface UseTableDataOptions<T extends object> {\n /** 原始数据数组。未提供 getRowId 时使用此数组的索引作为 ID;重排、插删或刷新数据时应提供稳定业务 ID。 */\n data: T[];\n /** 初始排序状态 */\n initialSort?: SortingState;\n /** 初始筛选状态 */\n initialFilters?: ColumnFiltersState;\n /** 初始每页行数,默认 10 */\n initialPageSize?: number;\n /** 自定义筛选函数(有筛选条件时执行)。提供后 tableProps.manualFiltering=true,避免 DataTable 二次过滤。 */\n filterFn?: (item: T, filters: ColumnFiltersState) => boolean;\n /** 获取唯一行 ID;index 是筛选前原始 data 的索引,而不是 tableData 的索引。 */\n getRowId?: (row: T, index: number) => string;\n}\n\n/**\n * useTableData 返回值\n */\nexport interface UseTableDataReturn<T extends object> {\n // 数据\n /** 经过筛选后的数据(如果提供了 filterFn) */\n tableData: T[];\n /** 是否由此 Hook 负责筛选;同时包含在 tableProps 中。 */\n manualFiltering: boolean;\n\n // 排序\n sorting: SortingState;\n onSortingChange: (sorting: SortingState) => void;\n\n // 筛选\n columnFilters: ColumnFiltersState;\n onColumnFiltersChange: (\n filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)\n ) => void;\n\n // 分页\n pageIndex: number;\n pageSize: number;\n /** Math.ceil(tableData.length / pageSize)。提供 filterFn 时是自定义筛选后的页数;\n * 未提供时仅是原始数据页数,不包含 DataTable 的内置筛选(此 Hook 没有 columns,无法精确计算)。\n * 内置筛选后的实际页数以 DataTable 的 table.getPageCount() 为准,不要将此值当作服务端总页数传入。\n */\n pageCount: number;\n onPageChange: (page: number) => void;\n onPageSizeChange: (size: number) => void;\n\n // 选择\n selectedRows: RowSelectionState;\n onRowSelectionChange: (selection: RowSelectionState) => void;\n /** tableData 中选中的行,按输入顺序返回;自定义筛选隐藏的选中 ID 保留在 selectedRows,但不在此数组中。 */\n selectedData: T[];\n\n /**\n * 合并好的 props,可直接展开到 <DataTable>\n * @example <DataTable columns={columns} {...tableProps} />\n */\n tableProps: {\n data: T[];\n sortingState: SortingState;\n onSortingChange: (sorting: SortingState) => void;\n columnFilters: ColumnFiltersState;\n onColumnFiltersChange: (\n filters: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)\n ) => void;\n pageIndex: number;\n pageSize: number;\n onPageChange: (page: number) => void;\n onPageSizeChange: (size: number) => void;\n selectedRows: RowSelectionState;\n onRowSelectionChange: (selection: RowSelectionState) => void;\n getRowId?: (row: T, index: number) => string;\n enableSorting: boolean;\n enableFiltering: boolean;\n manualFiltering: boolean;\n enablePagination: boolean;\n enableSelection: boolean;\n };\n}\n\n/**\n * useTableData - 封装 DataTable 的数据状态管理\n *\n * 将排序、筛选、分页、选择等状态统一管理,\n * 返回的 `tableProps` 可以直接展开到 `<DataTable>` 上。\n *\n * @example\n * ```tsx\n * const { tableProps, selectedData } = useTableData({\n * data: orders,\n * initialSort: [{ id: 'orderDate', desc: true }],\n * initialPageSize: 5,\n * });\n *\n * <DataTable columns={columns} {...tableProps} />\n * ```\n */\nexport function useTableData<T extends object>(\n options: UseTableDataOptions<T>\n): UseTableDataReturn<T> {\n const {\n data,\n initialSort = [],\n initialFilters = [],\n initialPageSize = 10,\n filterFn,\n getRowId,\n } = options;\n\n // ─── 排序 ───\n const [sorting, setSorting] = useState<SortingState>(initialSort);\n\n // ─── 筛选 ───\n const [columnFilters, setColumnFiltersRaw] = useState<ColumnFiltersState>(initialFilters);\n\n // ─── 分页 ───\n const [pageIndex, setPageIndex] = useState(0);\n const [pageSize, setPageSize] = useState(initialPageSize);\n\n // ─── 选择 ───\n const [selectedRows, setSelectedRows] = useState<RowSelectionState>({});\n\n const onSortingChange = useCallback((next: SortingState) => {\n setPageIndex(0);\n setSorting(next);\n }, []);\n\n // ─── 当筛选变化时重置到第一页 ───\n const onColumnFiltersChange = useCallback(\n (updater: ColumnFiltersState | ((prev: ColumnFiltersState) => ColumnFiltersState)) => {\n setPageIndex(0);\n setColumnFiltersRaw(updater);\n },\n []\n );\n\n // ─── 分页回调 ───\n const onPageChange = useCallback((page: number) => {\n setPageIndex(page);\n }, []);\n\n const onPageSizeChange = useCallback((size: number) => {\n setPageSize(size);\n setPageIndex(0);\n }, []);\n\n // Keep source indexes alongside rows, rather than mapping object identities:\n // the same object can appear at multiple positions in the input array.\n const { tableData, sourceIndexes } = useMemo(() => {\n if (!filterFn || columnFilters.length === 0) {\n return { tableData: data, sourceIndexes: data.map((_, index) => index) };\n }\n const tableData: T[] = [];\n const sourceIndexes: number[] = [];\n data.forEach((item, index) => {\n if (filterFn(item, columnFilters)) {\n tableData.push(item);\n sourceIndexes.push(index);\n }\n });\n return { tableData, sourceIndexes };\n }, [data, columnFilters, filterFn]);\n const manualFiltering = Boolean(filterFn);\n const resolveRowId = useCallback((row: T, index: number) => {\n const sourceIndex = sourceIndexes[index] ?? index;\n return getRowId ? getRowId(row, sourceIndex) : String(sourceIndex);\n }, [sourceIndexes, getRowId]);\n\n // ─── 页数 ───\n const pageCount = useMemo(\n () => Math.ceil(tableData.length / pageSize),\n [tableData.length, pageSize]\n );\n\n // ─── 选中数据便捷字段 ───\n const selectedData = useMemo(() =>\n tableData.filter((item, index) => selectedRows[resolveRowId(item, index)]),\n [selectedRows, tableData, resolveRowId]\n );\n\n // ─── 合并 props ───\n const tableProps = useMemo(\n () => ({\n data: tableData,\n sortingState: sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n pageIndex,\n pageSize,\n onPageChange,\n onPageSizeChange,\n selectedRows,\n onRowSelectionChange: setSelectedRows,\n getRowId: resolveRowId,\n enableSorting: true,\n enableFiltering: true,\n manualFiltering,\n enablePagination: true,\n enableSelection: true,\n }),\n [\n tableData,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n pageIndex,\n pageSize,\n onPageChange,\n onPageSizeChange,\n selectedRows,\n resolveRowId,\n manualFiltering,\n ]\n );\n\n return {\n tableData,\n manualFiltering,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n pageIndex,\n pageSize,\n pageCount,\n onPageChange,\n onPageSizeChange,\n selectedRows,\n onRowSelectionChange: setSelectedRows,\n selectedData,\n tableProps,\n };\n}\n\nexport default useTableData;\n"],"mappings":";;AA0GA,SAAgB,EACZ,GACqB;CACrB,IAAM,EACF,SACA,iBAAc,CAAC,GACf,oBAAiB,CAAC,GAClB,qBAAkB,IAClB,aACA,gBACA,GAGE,CAAC,GAAS,KAAc,EAAuB,CAAW,GAG1D,CAAC,GAAe,KAAuB,EAA6B,CAAc,GAGlF,CAAC,GAAW,KAAgB,EAAS,CAAC,GACtC,CAAC,GAAU,KAAe,EAAS,CAAe,GAGlD,CAAC,GAAc,KAAmB,EAA4B,CAAC,CAAC,GAEhE,IAAkB,GAAa,MAAuB;EAExD,AADA,EAAa,CAAC,GACd,EAAW,CAAI;CACnB,GAAG,CAAC,CAAC,GAGC,IAAwB,GACzB,MAAqF;EAElF,AADA,EAAa,CAAC,GACd,EAAoB,CAAO;CAC/B,GACA,CAAC,CACL,GAGM,IAAe,GAAa,MAAiB;EAC/C,EAAa,CAAI;CACrB,GAAG,CAAC,CAAC,GAEC,IAAmB,GAAa,MAAiB;EAEnD,AADA,EAAY,CAAI,GAChB,EAAa,CAAC;CAClB,GAAG,CAAC,CAAC,GAIC,EAAE,cAAW,qBAAkB,QAAc;EAC/C,IAAI,CAAC,KAAY,EAAc,WAAW,GACtC,OAAO;GAAE,WAAW;GAAM,eAAe,EAAK,KAAK,GAAG,MAAU,CAAK;EAAE;EAE3E,IAAM,IAAiB,CAAC,GAClB,IAA0B,CAAC;EAOjC,OANA,EAAK,SAAS,GAAM,MAAU;GAC1B,AAAI,EAAS,GAAM,CAAa,MAC5B,EAAU,KAAK,CAAI,GACnB,EAAc,KAAK,CAAK;EAEhC,CAAC,GACM;GAAE;GAAW;EAAc;CACtC,GAAG;EAAC;EAAM;EAAe;CAAQ,CAAC,GAC5B,IAAkB,EAAQ,GAC1B,IAAe,GAAa,GAAQ,MAAkB;EACxD,IAAM,IAAc,EAAc,MAAU;EAC5C,OAAO,IAAW,EAAS,GAAK,CAAW,IAAI,OAAO,CAAW;CACrE,GAAG,CAAC,GAAe,CAAQ,CAAC;CAmD5B,OAAO;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAzDc,QACR,KAAK,KAAK,EAAU,SAAS,CAAQ,GAC3C,CAAC,EAAU,QAAQ,CAAQ,CAuD3B;EACA;EACA;EACA;EACA,sBAAsB;EACtB,cAxDiB,QACjB,EAAU,QAAQ,GAAM,MAAU,EAAa,EAAa,GAAM,CAAK,EAAE,GACzE;GAAC;GAAc;GAAW;EAAY,CAsDtC;EACA,YAnDe,SACR;GACH,MAAM;GACN,cAAc;GACd;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,sBAAsB;GACtB,UAAU;GACV,eAAe;GACf,iBAAiB;GACjB;GACA,kBAAkB;GAClB,iBAAiB;EACrB,IACA;GACI;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACJ,CAkBA;CACJ;AACJ"}
@@ -1,7 +1,7 @@
1
1
  export { Resizable, ResizablePanel, ResizableHandle, useResize } from './components/Resizable';
2
2
  export type { ResizableProps, ResizablePanelProps, ResizableHandleProps, UseResizeOptions } from './components/Resizable';
3
- export { default as LayoutSuite, AdminSuite, WikiSuite, BlogSuite, BlankSuite, DashboardSuite } from './components/LayoutSuite';
4
- export type { AdminSuiteProps, WikiSuiteProps, BlogSuiteProps, BlankSuiteProps, DashboardSuiteProps, LayoutSuiteSidebarProps } from './components/LayoutSuite';
3
+ export { default as LayoutSuite, AdminSuite, MailSuite, WikiSuite, BlogSuite, BlankSuite, DashboardSuite } from './components/LayoutSuite';
4
+ export type { AdminSuiteProps, MailSuiteProps, WikiSuiteProps, BlogSuiteProps, BlankSuiteProps, DashboardSuiteProps, LayoutSuiteSidebarProps } from './components/LayoutSuite';
5
5
  export { default as ButtonMenu } from './components/ButtonMenu';
6
6
  export type { ButtonMenuProps, ButtonMenuItem } from './components/ButtonMenu';
7
7
  export { default as Button } from './components/Button';
package/dist/index.d.ts CHANGED
@@ -40,8 +40,8 @@ export { default as PageHeader } from './components/PageHeader';
40
40
  export type { PageHeaderProps } from './components/PageHeader';
41
41
  export { default as Page, PageBody, PageLeft, PageRight, PageMain, PageFooter, PageFloat } from './components/Page';
42
42
  export type { PageProps, PageBodyProps, PageAsideProps, PageMainProps, PageFooterProps, PageFloatProps, PagePanelProps } from './components/Page';
43
- export { default as LayoutSuite, AdminSuite, WikiSuite, BlogSuite, BlankSuite, DashboardSuite } from './components/LayoutSuite';
44
- export type { AdminSuiteProps, WikiSuiteProps, BlogSuiteProps, BlankSuiteProps, DashboardSuiteProps, LayoutSuiteSidebarProps } from './components/LayoutSuite';
43
+ export { default as LayoutSuite, AdminSuite, MailSuite, WikiSuite, BlogSuite, BlankSuite, DashboardSuite } from './components/LayoutSuite';
44
+ export type { AdminSuiteProps, MailSuiteProps, WikiSuiteProps, BlogSuiteProps, BlankSuiteProps, DashboardSuiteProps, LayoutSuiteSidebarProps } from './components/LayoutSuite';
45
45
  export { default as EmptyState } from './components/EmptyState';
46
46
  export { default as Avatar } from './components/Avatar';
47
47
  export type { AvatarProps, AvatarShape, AvatarBorderStyle, AvatarNotificationPosition, } from './components/Avatar';