kokotable 0.95.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 (308) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +320 -0
  3. package/contract/table-request.schema.json +424 -0
  4. package/dist/core/src/adapters/sources.d.ts +59 -0
  5. package/dist/core/src/adapters/sources.js +235 -0
  6. package/dist/core/src/client/aggregate.d.ts +94 -0
  7. package/dist/core/src/client/aggregate.js +222 -0
  8. package/dist/core/src/client/groupRows.d.ts +40 -0
  9. package/dist/core/src/client/groupRows.js +145 -0
  10. package/dist/core/src/client/runClientQuery.d.ts +67 -0
  11. package/dist/core/src/client/runClientQuery.js +157 -0
  12. package/dist/core/src/columns/ColumnBuilder.d.ts +121 -0
  13. package/dist/core/src/columns/ColumnBuilder.js +273 -0
  14. package/dist/core/src/columns/cascade.d.ts +24 -0
  15. package/dist/core/src/columns/cascade.js +93 -0
  16. package/dist/core/src/columns/deriveOperators.d.ts +38 -0
  17. package/dist/core/src/columns/deriveOperators.js +94 -0
  18. package/dist/core/src/columns/expandSorting.d.ts +19 -0
  19. package/dist/core/src/columns/expandSorting.js +52 -0
  20. package/dist/core/src/columns/helper.d.ts +196 -0
  21. package/dist/core/src/columns/helper.js +250 -0
  22. package/dist/core/src/columns/labels.d.ts +12 -0
  23. package/dist/core/src/columns/labels.js +21 -0
  24. package/dist/core/src/contract.d.ts +12 -0
  25. package/dist/core/src/contract.js +12 -0
  26. package/dist/core/src/contracts/aiColumnMeta.d.ts +102 -0
  27. package/dist/core/src/contracts/aiColumnMeta.js +197 -0
  28. package/dist/core/src/contracts/describeColumns.d.ts +48 -0
  29. package/dist/core/src/contracts/describeColumns.js +40 -0
  30. package/dist/core/src/contracts/exportData.d.ts +150 -0
  31. package/dist/core/src/contracts/exportData.js +228 -0
  32. package/dist/core/src/contracts/index.d.ts +6 -0
  33. package/dist/core/src/contracts/index.js +12 -0
  34. package/dist/core/src/contracts/legacyUrl.d.ts +47 -0
  35. package/dist/core/src/contracts/legacyUrl.js +156 -0
  36. package/dist/core/src/contracts/preset.d.ts +104 -0
  37. package/dist/core/src/contracts/preset.js +141 -0
  38. package/dist/core/src/contracts/presetStore.d.ts +81 -0
  39. package/dist/core/src/contracts/presetStore.js +146 -0
  40. package/dist/core/src/contracts/usePresets.d.ts +51 -0
  41. package/dist/core/src/contracts/usePresets.js +170 -0
  42. package/dist/core/src/defaults.d.ts +7 -0
  43. package/dist/core/src/defaults.js +9 -0
  44. package/dist/core/src/filters/codec.d.ts +20 -0
  45. package/dist/core/src/filters/codec.js +61 -0
  46. package/dist/core/src/filters/date.d.ts +30 -0
  47. package/dist/core/src/filters/date.js +179 -0
  48. package/dist/core/src/filters/describe.d.ts +45 -0
  49. package/dist/core/src/filters/describe.js +189 -0
  50. package/dist/core/src/filters/kind.d.ts +52 -0
  51. package/dist/core/src/filters/kind.js +110 -0
  52. package/dist/core/src/filters/kinds/_list.d.ts +13 -0
  53. package/dist/core/src/filters/kinds/_list.js +42 -0
  54. package/dist/core/src/filters/kinds/boolean.d.ts +3 -0
  55. package/dist/core/src/filters/kinds/boolean.js +21 -0
  56. package/dist/core/src/filters/kinds/cascade.d.ts +6 -0
  57. package/dist/core/src/filters/kinds/cascade.js +16 -0
  58. package/dist/core/src/filters/kinds/commonCode.d.ts +3 -0
  59. package/dist/core/src/filters/kinds/commonCode.js +13 -0
  60. package/dist/core/src/filters/kinds/date.d.ts +9 -0
  61. package/dist/core/src/filters/kinds/date.js +32 -0
  62. package/dist/core/src/filters/kinds/enum.d.ts +4 -0
  63. package/dist/core/src/filters/kinds/enum.js +13 -0
  64. package/dist/core/src/filters/kinds/index.d.ts +49 -0
  65. package/dist/core/src/filters/kinds/index.js +39 -0
  66. package/dist/core/src/filters/kinds/linear.d.ts +5 -0
  67. package/dist/core/src/filters/kinds/linear.js +16 -0
  68. package/dist/core/src/filters/kinds/number.d.ts +5 -0
  69. package/dist/core/src/filters/kinds/number.js +19 -0
  70. package/dist/core/src/filters/kinds/period.d.ts +4 -0
  71. package/dist/core/src/filters/kinds/period.js +17 -0
  72. package/dist/core/src/filters/kinds/relation.d.ts +5 -0
  73. package/dist/core/src/filters/kinds/relation.js +17 -0
  74. package/dist/core/src/filters/kinds/schedule.d.ts +5 -0
  75. package/dist/core/src/filters/kinds/schedule.js +16 -0
  76. package/dist/core/src/filters/kinds/text.d.ts +4 -0
  77. package/dist/core/src/filters/kinds/text.js +18 -0
  78. package/dist/core/src/filters/linear.d.ts +33 -0
  79. package/dist/core/src/filters/linear.js +65 -0
  80. package/dist/core/src/filters/list.d.ts +32 -0
  81. package/dist/core/src/filters/list.js +87 -0
  82. package/dist/core/src/filters/number.d.ts +17 -0
  83. package/dist/core/src/filters/number.js +82 -0
  84. package/dist/core/src/filters/registry.d.ts +23 -0
  85. package/dist/core/src/filters/registry.js +121 -0
  86. package/dist/core/src/filters/relation.d.ts +26 -0
  87. package/dist/core/src/filters/relation.js +75 -0
  88. package/dist/core/src/filters/schedule.d.ts +21 -0
  89. package/dist/core/src/filters/schedule.js +60 -0
  90. package/dist/core/src/filters/text.d.ts +44 -0
  91. package/dist/core/src/filters/text.js +127 -0
  92. package/dist/core/src/hooks/index.d.ts +6 -0
  93. package/dist/core/src/hooks/index.js +6 -0
  94. package/dist/core/src/hooks/useDataTable.d.ts +13 -0
  95. package/dist/core/src/hooks/useDataTable.js +109 -0
  96. package/dist/core/src/hooks/useGrouping.d.ts +33 -0
  97. package/dist/core/src/hooks/useGrouping.js +57 -0
  98. package/dist/core/src/hooks/useSearchHistory.d.ts +20 -0
  99. package/dist/core/src/hooks/useSearchHistory.js +52 -0
  100. package/dist/core/src/hooks/useTableData.d.ts +25 -0
  101. package/dist/core/src/hooks/useTableData.js +49 -0
  102. package/dist/core/src/hooks/useTableFromDefinition.d.ts +47 -0
  103. package/dist/core/src/hooks/useTableFromDefinition.js +174 -0
  104. package/dist/core/src/hooks/useTableSearch.d.ts +10 -0
  105. package/dist/core/src/hooks/useTableSearch.js +434 -0
  106. package/dist/core/src/hooks/useUrlSync.d.ts +64 -0
  107. package/dist/core/src/hooks/useUrlSync.js +145 -0
  108. package/dist/core/src/index.d.ts +38 -0
  109. package/dist/core/src/index.js +43 -0
  110. package/dist/core/src/pivot/runClientPivot.d.ts +101 -0
  111. package/dist/core/src/pivot/runClientPivot.js +230 -0
  112. package/dist/core/src/pivot/transforms.d.ts +19 -0
  113. package/dist/core/src/pivot/transforms.js +105 -0
  114. package/dist/core/src/registry.d.ts +46 -0
  115. package/dist/core/src/registry.js +67 -0
  116. package/dist/core/src/types/column.d.ts +213 -0
  117. package/dist/core/src/types/column.js +1 -0
  118. package/dist/core/src/types/data.d.ts +63 -0
  119. package/dist/core/src/types/data.js +1 -0
  120. package/dist/core/src/types/definition.d.ts +239 -0
  121. package/dist/core/src/types/definition.js +59 -0
  122. package/dist/core/src/types/filter.d.ts +94 -0
  123. package/dist/core/src/types/filter.js +1 -0
  124. package/dist/core/src/types/filterKind.d.ts +204 -0
  125. package/dist/core/src/types/filterKind.js +1 -0
  126. package/dist/core/src/types/grouping.d.ts +80 -0
  127. package/dist/core/src/types/grouping.js +4 -0
  128. package/dist/core/src/types/index.d.ts +8 -0
  129. package/dist/core/src/types/index.js +8 -0
  130. package/dist/core/src/types/operators.d.ts +64 -0
  131. package/dist/core/src/types/operators.js +108 -0
  132. package/dist/core/src/types/request.d.ts +114 -0
  133. package/dist/core/src/types/request.js +1 -0
  134. package/dist/core/src/types/search.d.ts +112 -0
  135. package/dist/core/src/types/search.js +1 -0
  136. package/dist/core/src/types/sources.d.ts +84 -0
  137. package/dist/core/src/types/sources.js +1 -0
  138. package/dist/core/src/types/table.d.ts +40 -0
  139. package/dist/core/src/types/table.js +1 -0
  140. package/dist/core/src/utils/compare.d.ts +13 -0
  141. package/dist/core/src/utils/compare.js +53 -0
  142. package/dist/core/src/utils/dateText.d.ts +12 -0
  143. package/dist/core/src/utils/dateText.js +30 -0
  144. package/dist/core/src/utils/getValue.d.ts +9 -0
  145. package/dist/core/src/utils/getValue.js +32 -0
  146. package/dist/index.d.ts +1 -0
  147. package/dist/index.js +2 -0
  148. package/dist/mui/src/TableLayout.d.ts +75 -0
  149. package/dist/mui/src/TableLayout.js +162 -0
  150. package/dist/mui/src/TableScreen.d.ts +70 -0
  151. package/dist/mui/src/TableScreen.js +174 -0
  152. package/dist/mui/src/addons/FilterModal.d.ts +45 -0
  153. package/dist/mui/src/addons/FilterModal.js +39 -0
  154. package/dist/mui/src/addons/FilterStatusModal.d.ts +40 -0
  155. package/dist/mui/src/addons/FilterStatusModal.js +68 -0
  156. package/dist/mui/src/addons/PresetBar.d.ts +56 -0
  157. package/dist/mui/src/addons/PresetBar.js +145 -0
  158. package/dist/mui/src/addons/ShareDropdown.d.ts +35 -0
  159. package/dist/mui/src/addons/ShareDropdown.js +41 -0
  160. package/dist/mui/src/addons/searchStore.d.ts +44 -0
  161. package/dist/mui/src/addons/searchStore.js +112 -0
  162. package/dist/mui/src/addons/searchSummary.d.ts +106 -0
  163. package/dist/mui/src/addons/searchSummary.js +109 -0
  164. package/dist/mui/src/columns/displayColumns.d.ts +100 -0
  165. package/dist/mui/src/columns/displayColumns.js +174 -0
  166. package/dist/mui/src/content/CardContent.d.ts +24 -0
  167. package/dist/mui/src/content/CardContent.js +25 -0
  168. package/dist/mui/src/content/ColumnMenu.d.ts +43 -0
  169. package/dist/mui/src/content/ColumnMenu.js +40 -0
  170. package/dist/mui/src/content/ColumnVisibilityMenu.d.ts +14 -0
  171. package/dist/mui/src/content/ColumnVisibilityMenu.js +37 -0
  172. package/dist/mui/src/content/EditCell.d.ts +19 -0
  173. package/dist/mui/src/content/EditCell.js +61 -0
  174. package/dist/mui/src/content/HeaderCell.d.ts +59 -0
  175. package/dist/mui/src/content/HeaderCell.js +88 -0
  176. package/dist/mui/src/content/SelectionActionBar.d.ts +17 -0
  177. package/dist/mui/src/content/SelectionActionBar.js +19 -0
  178. package/dist/mui/src/content/SortControl.d.ts +17 -0
  179. package/dist/mui/src/content/SortControl.js +45 -0
  180. package/dist/mui/src/content/TableContent.d.ts +191 -0
  181. package/dist/mui/src/content/TableContent.js +525 -0
  182. package/dist/mui/src/content/TableErrorFallback.d.ts +24 -0
  183. package/dist/mui/src/content/TableErrorFallback.js +29 -0
  184. package/dist/mui/src/content/cellError.d.ts +13 -0
  185. package/dist/mui/src/content/cellError.js +20 -0
  186. package/dist/mui/src/content/cellRenderers.d.ts +71 -0
  187. package/dist/mui/src/content/cellRenderers.js +127 -0
  188. package/dist/mui/src/content/cellValue.d.ts +27 -0
  189. package/dist/mui/src/content/cellValue.js +82 -0
  190. package/dist/mui/src/content/columnOrder.d.ts +7 -0
  191. package/dist/mui/src/content/columnOrder.js +48 -0
  192. package/dist/mui/src/content/density.d.ts +13 -0
  193. package/dist/mui/src/content/density.js +12 -0
  194. package/dist/mui/src/content/menuItems.d.ts +118 -0
  195. package/dist/mui/src/content/menuItems.js +128 -0
  196. package/dist/mui/src/content/pinning.d.ts +17 -0
  197. package/dist/mui/src/content/pinning.js +65 -0
  198. package/dist/mui/src/exporters/index.d.ts +1 -0
  199. package/dist/mui/src/exporters/index.js +1 -0
  200. package/dist/mui/src/exporters/useExportFormatters.d.ts +13 -0
  201. package/dist/mui/src/exporters/useExportFormatters.js +17 -0
  202. package/dist/mui/src/filter/FilterArea.d.ts +40 -0
  203. package/dist/mui/src/filter/FilterArea.js +37 -0
  204. package/dist/mui/src/filter/FilterInput.d.ts +14 -0
  205. package/dist/mui/src/filter/FilterInput.js +18 -0
  206. package/dist/mui/src/filter/inputs/DateFilterInput.d.ts +14 -0
  207. package/dist/mui/src/filter/inputs/DateFilterInput.js +112 -0
  208. package/dist/mui/src/filter/inputs/LinearFilterInput.d.ts +10 -0
  209. package/dist/mui/src/filter/inputs/LinearFilterInput.js +35 -0
  210. package/dist/mui/src/filter/inputs/NumberFilterInput.d.ts +12 -0
  211. package/dist/mui/src/filter/inputs/NumberFilterInput.js +111 -0
  212. package/dist/mui/src/filter/inputs/OperatorMenu.d.ts +14 -0
  213. package/dist/mui/src/filter/inputs/OperatorMenu.js +25 -0
  214. package/dist/mui/src/filter/inputs/RelationFilterInput.d.ts +11 -0
  215. package/dist/mui/src/filter/inputs/RelationFilterInput.js +67 -0
  216. package/dist/mui/src/filter/inputs/ScheduleFilterInput.d.ts +8 -0
  217. package/dist/mui/src/filter/inputs/ScheduleFilterInput.js +17 -0
  218. package/dist/mui/src/filter/inputs/SelectFilterInput.d.ts +16 -0
  219. package/dist/mui/src/filter/inputs/SelectFilterInput.js +57 -0
  220. package/dist/mui/src/filter/inputs/TextFilterInput.d.ts +13 -0
  221. package/dist/mui/src/filter/inputs/TextFilterInput.js +55 -0
  222. package/dist/mui/src/filter/kinds.d.ts +58 -0
  223. package/dist/mui/src/filter/kinds.js +103 -0
  224. package/dist/mui/src/grouping/AggregateChip.d.ts +55 -0
  225. package/dist/mui/src/grouping/AggregateChip.js +128 -0
  226. package/dist/mui/src/grouping/AggregationChips.d.ts +49 -0
  227. package/dist/mui/src/grouping/AggregationChips.js +80 -0
  228. package/dist/mui/src/grouping/AggregationSettingsButton.d.ts +14 -0
  229. package/dist/mui/src/grouping/AggregationSettingsButton.js +19 -0
  230. package/dist/mui/src/grouping/GroupingBar.d.ts +59 -0
  231. package/dist/mui/src/grouping/GroupingBar.js +267 -0
  232. package/dist/mui/src/grouping/StepExpandButton.d.ts +12 -0
  233. package/dist/mui/src/grouping/StepExpandButton.js +18 -0
  234. package/dist/mui/src/grouping/groupRow.d.ts +68 -0
  235. package/dist/mui/src/grouping/groupRow.js +129 -0
  236. package/dist/mui/src/headers/defaultHeader.d.ts +28 -0
  237. package/dist/mui/src/headers/defaultHeader.js +90 -0
  238. package/dist/mui/src/headers/defineHeader.d.ts +21 -0
  239. package/dist/mui/src/headers/defineHeader.js +68 -0
  240. package/dist/mui/src/headers/index.d.ts +5 -0
  241. package/dist/mui/src/headers/index.js +3 -0
  242. package/dist/mui/src/headers/meta.d.ts +18 -0
  243. package/dist/mui/src/headers/meta.js +11 -0
  244. package/dist/mui/src/headers/types.d.ts +84 -0
  245. package/dist/mui/src/headers/types.js +1 -0
  246. package/dist/mui/src/index.d.ts +110 -0
  247. package/dist/mui/src/index.js +79 -0
  248. package/dist/mui/src/layout/ResizeHandle.d.ts +30 -0
  249. package/dist/mui/src/layout/ResizeHandle.js +66 -0
  250. package/dist/mui/src/menus/columnMenu.d.ts +45 -0
  251. package/dist/mui/src/menus/columnMenu.js +144 -0
  252. package/dist/mui/src/menus/copy.d.ts +5 -0
  253. package/dist/mui/src/menus/copy.js +25 -0
  254. package/dist/mui/src/menus/index.d.ts +6 -0
  255. package/dist/mui/src/menus/index.js +4 -0
  256. package/dist/mui/src/menus/resolve.d.ts +15 -0
  257. package/dist/mui/src/menus/resolve.js +60 -0
  258. package/dist/mui/src/menus/rowMenu.d.ts +46 -0
  259. package/dist/mui/src/menus/rowMenu.js +76 -0
  260. package/dist/mui/src/pagination/PaginationArea.d.ts +16 -0
  261. package/dist/mui/src/pagination/PaginationArea.js +23 -0
  262. package/dist/mui/src/pivot/PivotTable.d.ts +18 -0
  263. package/dist/mui/src/pivot/PivotTable.js +100 -0
  264. package/dist/mui/src/provider/KokoTableProvider.d.ts +184 -0
  265. package/dist/mui/src/provider/KokoTableProvider.js +118 -0
  266. package/dist/mui/src/provider/labels.d.ts +85 -0
  267. package/dist/mui/src/provider/labels.js +104 -0
  268. package/dist/mui/src/registry.d.ts +30 -0
  269. package/dist/mui/src/registry.js +5 -0
  270. package/dist/mui/src/toolbar/GlobalFilterField.d.ts +36 -0
  271. package/dist/mui/src/toolbar/GlobalFilterField.js +78 -0
  272. package/dist/mui/src/virtual/VirtualTableContent.d.ts +41 -0
  273. package/dist/mui/src/virtual/VirtualTableContent.js +58 -0
  274. package/dist/mui/src/virtual/VirtualTableScreen.d.ts +13 -0
  275. package/dist/mui/src/virtual/VirtualTableScreen.js +23 -0
  276. package/dist/mui/src/virtual.d.ts +4 -0
  277. package/dist/mui/src/virtual.js +4 -0
  278. package/dist/mui/src/virtualColumns/VirtualColumnCard.d.ts +26 -0
  279. package/dist/mui/src/virtualColumns/VirtualColumnCard.js +29 -0
  280. package/dist/mui/src/virtualColumns/VirtualColumnDialog.d.ts +27 -0
  281. package/dist/mui/src/virtualColumns/VirtualColumnDialog.js +154 -0
  282. package/dist/mui/src/virtualColumns/VirtualColumnSection.d.ts +31 -0
  283. package/dist/mui/src/virtualColumns/VirtualColumnSection.js +55 -0
  284. package/dist/mui/src/virtualColumns/expression.d.ts +68 -0
  285. package/dist/mui/src/virtualColumns/expression.js +489 -0
  286. package/dist/mui/src/virtualColumns/useVirtualColumns.d.ts +67 -0
  287. package/dist/mui/src/virtualColumns/useVirtualColumns.js +111 -0
  288. package/dist/mui/src/virtualColumns/virtualColumnDefs.d.ts +29 -0
  289. package/dist/mui/src/virtualColumns/virtualColumnDefs.js +51 -0
  290. package/dist/mui-virtual.d.ts +1 -0
  291. package/dist/mui-virtual.js +2 -0
  292. package/dist/mui.d.ts +1 -0
  293. package/dist/mui.js +2 -0
  294. package/dist/query/src/index.d.ts +6 -0
  295. package/dist/query/src/index.js +7 -0
  296. package/dist/query/src/serverPivot.d.ts +42 -0
  297. package/dist/query/src/serverPivot.js +153 -0
  298. package/dist/query/src/useServerGrouping.d.ts +52 -0
  299. package/dist/query/src/useServerGrouping.js +186 -0
  300. package/dist/query/src/useServerTable.d.ts +20 -0
  301. package/dist/query/src/useServerTable.js +106 -0
  302. package/dist/query/src/useServerTableData.d.ts +48 -0
  303. package/dist/query/src/useServerTableData.js +64 -0
  304. package/dist/query/src/wire.d.ts +16 -0
  305. package/dist/query/src/wire.js +32 -0
  306. package/dist/query.d.ts +1 -0
  307. package/dist/query.js +2 -0
  308. package/package.json +125 -0
@@ -0,0 +1,273 @@
1
+ import { offeredOperators } from "../types/operators";
2
+ import { inferVariant } from "./deriveOperators";
3
+ import { getValue } from "../utils/getValue";
4
+ import { ALL_FILTER_VALUE } from "../filters/list";
5
+ import { filterKindVariant } from "../filters/kind";
6
+ import { builtinKindForVariant } from "../filters/kinds";
7
+ /** `dataType` → 기본 필터 UI 종류 (`deriveOperators.inferVariant` 와 같은 표) */
8
+ export const inferFilterVariant = (dataType) => inferVariant({ dataType });
9
+ const pad = (n) => String(n).padStart(2, "0");
10
+ /** 기본 포맷터 — 순수 함수라 렌더러가 없어도 동작한다 (CSV·PDF 내보내기 공용) */
11
+ export function formatByDataType(value, dataType) {
12
+ if (value === null || value === undefined)
13
+ return "";
14
+ switch (dataType) {
15
+ case "number": {
16
+ const num = Number(value);
17
+ return Number.isNaN(num) ? "" : num.toLocaleString("ko-KR");
18
+ }
19
+ case "date":
20
+ case "datetime": {
21
+ const date = value instanceof Date ? value : new Date(String(value));
22
+ if (Number.isNaN(date.getTime()))
23
+ return String(value);
24
+ const ymd = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
25
+ return dataType === "datetime"
26
+ ? `${ymd} ${pad(date.getHours())}:${pad(date.getMinutes())}`
27
+ : ymd;
28
+ }
29
+ default:
30
+ return String(value);
31
+ }
32
+ }
33
+ /**
34
+ * 컬럼 빌더 — 체이닝으로 컬럼 정의를 조립한다.
35
+ *
36
+ * 원본과 달리 **MUI 를 모른다.** 편집 입력·칩 같은 렌더 선언은 `meta.edit` 에
37
+ * 데이터로 남기고, 실제 컴포넌트는 렌더러 패키지가 읽어서 그린다. 그래야
38
+ * 같은 컬럼 정의로 테이블도, 카드도, 내보내기도 그릴 수 있다.
39
+ */
40
+ export class ColumnBuilder {
41
+ column;
42
+ key;
43
+ dataType;
44
+ constructor(key, header, dataType = "text") {
45
+ this.key = String(key);
46
+ this.dataType = dataType;
47
+ const variant = inferVariant({ dataType });
48
+ this.column = {
49
+ id: this.key,
50
+ header,
51
+ meta: { dataType },
52
+ filter: {
53
+ // 내장 kind 를 같이 붙인다 — 판정·직렬화·문구·codec 이 kind 경로로 간다 (dogfooding)
54
+ kind: builtinKindForVariant(variant),
55
+ variant,
56
+ operators: offeredOperators(variant),
57
+ },
58
+ };
59
+ this.applyAccessor();
60
+ this.applyDefaultCell();
61
+ return this;
62
+ }
63
+ /**
64
+ * 점 표기 key 는 `accessorKey` 대신 `accessorFn` 으로 읽는다.
65
+ * TanStack 의 깊은 `accessorKey` 접근은 중간 객체가 없으면 행마다 경고를 낸다
66
+ * (확장 자식 행 등) — 안전 접근으로 대체해 조용히 undefined 를 돌려준다.
67
+ *
68
+ * 값은 **변환하지 않는다.** 필터·정렬·집계는 2층이 원값(`getValue`)으로 계산하므로
69
+ * 셀만 다른 값(boolean 문자열, NaN→0, Date 객체)을 보면 두 경로가 어긋난다.
70
+ * 표시 포맷은 `formatByDataType` 이 원값에서 만든다.
71
+ */
72
+ applyAccessor() {
73
+ const key = this.key;
74
+ this.column.accessorFn = (row) => getValue(row, key);
75
+ }
76
+ applyDefaultCell() {
77
+ const dataType = this.dataType;
78
+ this.column.cell = ({ getValue: get }) => formatByDataType(get(), dataType);
79
+ if (dataType === "boolean") {
80
+ this.setFilterOptions([
81
+ { label: "true", value: "true" },
82
+ { label: "false", value: "false" },
83
+ ]);
84
+ }
85
+ }
86
+ /** 필터 UI 종류 변경 — 연산자 후보와 내장 kind 도 함께 갈아끼운다 (`custom` 은 kind 없음) */
87
+ setVariant(variant, operators) {
88
+ this.column.filter = {
89
+ ...this.column.filter,
90
+ kind: builtinKindForVariant(variant),
91
+ variant,
92
+ operators: operators ?? offeredOperators(variant),
93
+ };
94
+ return this;
95
+ }
96
+ /**
97
+ * 필터 kind 지정 — 연산자·기본값이 kind 로 좁혀진다 (`filter: { kind }` 를 인라인으로 쓸 때와 달리
98
+ * 레지스트리에 등록하지 않은 kind 에도 연산자·기본값을 줄 수 있다).
99
+ * 연산자 후보는 `spec.operators` > kind 의 `offered` > 전체, variant 는 `spec.variant` > kind 의 힌트.
100
+ */
101
+ setFilterKind(kind, spec = {}) {
102
+ this.column.filter = {
103
+ ...this.column.filter,
104
+ ...spec,
105
+ kind,
106
+ variant: spec.variant ?? filterKindVariant(kind),
107
+ operators: spec.operators ?? kind.offered ?? kind.operatorNames,
108
+ };
109
+ return this;
110
+ }
111
+ /** 필터 선언 병합. 필터 값 타입은 셀 값 `V` 와 다를 수 있어 `FilterSpec<any>` 로 받는다 */
112
+ setFilter(spec) {
113
+ this.column.filter = { ...this.column.filter, ...spec };
114
+ return this;
115
+ }
116
+ setOperators(operators, defaultOperator) {
117
+ return this.setFilter({ operators, ...(defaultOperator ? { defaultOperator } : {}) });
118
+ }
119
+ setDateFilterMode(mode) {
120
+ return this.setMeta({ dateFilterMode: mode });
121
+ }
122
+ setMeta(meta) {
123
+ this.column.meta = { ...this.column.meta, ...meta };
124
+ return this;
125
+ }
126
+ setProps(props) {
127
+ // ColumnDef 는 accessorKey/accessorFn/display 세 갈래 유니온이라 스프레드가
128
+ // 어느 갈래인지 잃는다. 빌더는 항상 accessorFn 갈래를 유지하므로 단언한다.
129
+ this.column = { ...this.column, ...props };
130
+ return this;
131
+ }
132
+ /**
133
+ * 소비 앱 확장 메타 (`meta.custom`) — 여러 번 부르면 얕게 병합된다.
134
+ * 읽는 쪽은 `columnMetaOf<Ext>(column)?.custom` 으로 타입을 살린다.
135
+ */
136
+ setCustom(ext) {
137
+ const previous = (this.column.meta?.custom ?? {});
138
+ return this.setMeta({ custom: { ...previous, ...ext } });
139
+ }
140
+ /** 헤더 규칙 — 툴팁·셀 정렬·헤더 정렬. 렌더러가 `meta.headerTooltip` / `meta.align` / `meta.headerAlign` 을 읽는다 */
141
+ setHeaderRule(rule) {
142
+ return this.setMeta({
143
+ ...(rule.tooltip !== undefined ? { headerTooltip: rule.tooltip } : {}),
144
+ ...(rule.align !== undefined ? { align: rule.align } : {}),
145
+ ...(rule.headerAlign !== undefined ? { headerAlign: rule.headerAlign } : {}),
146
+ });
147
+ }
148
+ setHeader(header) {
149
+ this.column.header = header;
150
+ return this;
151
+ }
152
+ /** 필터 패널에만 쓰는 라벨 (헤더가 ReactNode 일 때) */
153
+ setFilterLabel(label) {
154
+ this.column.filterLabel = label;
155
+ return this;
156
+ }
157
+ setSizing(options) {
158
+ const { ellipsis, ...sizing } = options;
159
+ this.setProps(sizing);
160
+ if (ellipsis)
161
+ this.setMeta({ ellipsis: true });
162
+ return this;
163
+ }
164
+ /** TanStack 셀 컨텍스트 → 순수 값 props (setCell/setCellWrapper 공용) */
165
+ toCellProps(ctx) {
166
+ return {
167
+ value: ctx.getValue(),
168
+ row: ctx.row.original,
169
+ rowIndex: ctx.row.index,
170
+ columnId: this.key,
171
+ renderedValue: formatByDataType(ctx.getValue(), this.dataType),
172
+ };
173
+ }
174
+ /** 셀 렌더러 교체 — `value` 는 셀 값 타입 `V` 로 온다 */
175
+ setCell(render) {
176
+ this.column.cell = (ctx) => render(this.toCellProps(ctx));
177
+ return this;
178
+ }
179
+ /** 기존 렌더 결과를 감싼다 (링크·칩 등) */
180
+ setCellWrapper(wrap) {
181
+ const previous = this.column.cell;
182
+ this.column.cell = (ctx) => {
183
+ const props = this.toCellProps(ctx);
184
+ const rendered = previous ? previous(ctx) : props.renderedValue;
185
+ return wrap(rendered, props);
186
+ };
187
+ return this;
188
+ }
189
+ /**
190
+ * 표시 여부.
191
+ *
192
+ * `forceHidden` 은 값 보관 전용 — 사용자 토글이나 프리셋 복원으로도 되살아나지
193
+ * 않는다. `isVisible: false` 는 "기본 숨김"일 뿐이라 되살아난다. 헤더가 없는
194
+ * 컬럼(통합 cascade 에 흡수된 레벨 등)은 반드시 `forceHidden` 을 쓴다.
195
+ */
196
+ setDisplay(options = {}) {
197
+ const display = options.display ?? true;
198
+ const forceHidden = options.forceHidden ?? false;
199
+ const hideFilter = (options.hideFilter ?? false) || forceHidden;
200
+ this.setMeta({
201
+ isVisible: forceHidden ? false : display,
202
+ ...(forceHidden ? { forceHidden: true } : {}),
203
+ });
204
+ // 필터 숨김은 FilterSpec 으로 — 원본이 meta.hideFilter 로 흩어 놨던 것을 모았다
205
+ this.setFilter({ hidden: hideFilter });
206
+ // 표시는 접고 필터만 남긴 컬럼은 "상세 조건" 그룹으로 내린다
207
+ if (!display && !hideFilter)
208
+ this.setFilter({ detail: true });
209
+ return this;
210
+ }
211
+ setEditable(enabled = true, onCommit) {
212
+ const kind = this.dataType === "number" ? "number" : "text";
213
+ return this.setEdit({ enabled, kind, onCommit });
214
+ }
215
+ /**
216
+ * 선택 편집 — 선언만 남긴다. dropdown/chip/button 컴포넌트는 렌더러가 그린다.
217
+ */
218
+ setEditSelect(config) {
219
+ return this.setEdit({ ...config, enabled: true, kind: "select" });
220
+ }
221
+ setEdit(spec) {
222
+ return this.setMeta({ edit: spec });
223
+ }
224
+ /**
225
+ * 필터 선택지. 목록이 있는 컬럼은 자유 텍스트 입력 대상이 아니다 —
226
+ * 라벨과 저장값이 달라 드롭다운이 사라지면 화면의 라벨을 쳐서 조용히 0건이 된다.
227
+ */
228
+ setFilterOptions(options, includeAll = true) {
229
+ const withAll = includeAll
230
+ ? [{ label: "전체", value: ALL_FILTER_VALUE }, ...options]
231
+ : options;
232
+ this.setMeta({ selectOptions: withAll });
233
+ const variant = this.column.filter?.variant;
234
+ if (variant === "text")
235
+ this.setVariant("select");
236
+ return this;
237
+ }
238
+ /** 집계 활성 — 서버 집계 키와 클라이언트 그룹 집계에 함께 쓰인다 */
239
+ setAgg(enabled, aggregations) {
240
+ const fallback = this.dataType === "number" ? ["sum"] : ["count_distinct"];
241
+ return this.setMeta({
242
+ enableAgg: enabled,
243
+ ...(enabled ? { aggregations: aggregations ?? fallback } : {}),
244
+ });
245
+ }
246
+ setFooter(footer) {
247
+ this.column.footer = footer;
248
+ return this;
249
+ }
250
+ /** 이 컬럼으로 정렬할 때 서버에 대신 보낼 실제 필드 (가상/통합 컬럼) */
251
+ setSortExpandsTo(fields) {
252
+ return this.setMeta({ sortExpandsTo: fields });
253
+ }
254
+ getColumn() {
255
+ return this.column;
256
+ }
257
+ build() {
258
+ return this.column;
259
+ }
260
+ }
261
+ /** 빌더 배열을 컬럼 정의 배열로 */
262
+ export function buildColumns(builders) {
263
+ const result = [];
264
+ for (const entry of builders) {
265
+ if (Array.isArray(entry))
266
+ result.push(...entry.map((b) => b.build()));
267
+ else if (entry instanceof ColumnBuilder)
268
+ result.push(entry.build());
269
+ else
270
+ result.push(entry);
271
+ }
272
+ return result;
273
+ }
@@ -0,0 +1,24 @@
1
+ import type { RowData } from "@tanstack/react-table";
2
+ import type { SelectOption, TableColumnDefs } from "../types/column";
3
+ import type { ColumnFilterEntry } from "../types/request";
4
+ /** 컬럼 id → 상위 선택으로 좁혀진 선택지 */
5
+ export type CascadeOptionMap = Map<string, SelectOption[]>;
6
+ /**
7
+ * 상위 필터 값에 따라 하위 컬럼의 선택지를 좁힌다.
8
+ *
9
+ * 두 가지 선언을 함께 처리한다:
10
+ * - `meta.dependentFilter` — CommonCode 계층. `allChildOptions` 를 상위 idx 로 거른다
11
+ * - `meta.customCascadeFilter` — 호출부가 준 `getOptions(...상위값)`
12
+ *
13
+ * **상위가 안 골라졌으면 빈 목록**이다. 전체를 보여 주면 상위와 무관한 값을 고를 수
14
+ * 있게 되고, 그렇게 만들어진 조합은 서버에서 0건이 된다.
15
+ */
16
+ export declare function resolveCascadeOptions<T extends RowData>(columns: TableColumnDefs<T>, filters: ColumnFilterEntry[]): CascadeOptionMap;
17
+ /**
18
+ * 상위가 바뀌어 더 이상 고를 수 없게 된 하위 값을 걷어낸다.
19
+ *
20
+ * 안 걷어내면 화면에는 없는 값이 조건으로 남아 조용히 0건이 된다.
21
+ * 선택지가 **비어 있으면 건너뛴다** — CommonCode 가 아직 안 실린 상태일 수 있어서,
22
+ * 그때 지우면 사용자가 고른 조건이 로딩 타이밍 때문에 날아간다.
23
+ */
24
+ export declare function pruneCascadeValues(filters: ColumnFilterEntry[], options: CascadeOptionMap): ColumnFilterEntry[];
@@ -0,0 +1,93 @@
1
+ import { ALL_FILTER_VALUE } from "../filters/list";
2
+ import { columnKey } from "./deriveOperators";
3
+ function filterValueOf(filters, id) {
4
+ return filters.find((f) => f?.id === id)?.value;
5
+ }
6
+ /** 필터 값에서 숫자 목록을 뽑는다. 비었으면 null */
7
+ function numericSelections(value) {
8
+ if (value === null || value === undefined || value === "")
9
+ return null;
10
+ const list = Array.isArray(value) ? value : [value];
11
+ const numbers = list
12
+ .filter((v) => v !== ALL_FILTER_VALUE)
13
+ .map(Number)
14
+ .filter((v) => !Number.isNaN(v));
15
+ return numbers.length > 0 ? numbers : null;
16
+ }
17
+ /** 원래 선택지에 "전체" 센티널이 있었으면 좁힌 목록에도 유지한다 */
18
+ function keepAllSentinel(original, narrowed) {
19
+ const all = original?.find((option) => option.value === ALL_FILTER_VALUE);
20
+ return all ? [all, ...narrowed] : narrowed;
21
+ }
22
+ /**
23
+ * 상위 필터 값에 따라 하위 컬럼의 선택지를 좁힌다.
24
+ *
25
+ * 두 가지 선언을 함께 처리한다:
26
+ * - `meta.dependentFilter` — CommonCode 계층. `allChildOptions` 를 상위 idx 로 거른다
27
+ * - `meta.customCascadeFilter` — 호출부가 준 `getOptions(...상위값)`
28
+ *
29
+ * **상위가 안 골라졌으면 빈 목록**이다. 전체를 보여 주면 상위와 무관한 값을 고를 수
30
+ * 있게 되고, 그렇게 만들어진 조합은 서버에서 0건이 된다.
31
+ */
32
+ export function resolveCascadeOptions(columns, filters) {
33
+ const map = new Map();
34
+ for (const column of columns) {
35
+ const id = columnKey(column);
36
+ if (!id)
37
+ continue;
38
+ const meta = column.meta;
39
+ const dependent = meta?.dependentFilter;
40
+ if (dependent) {
41
+ const parent = numericSelections(filterValueOf(filters, dependent.dependsOn));
42
+ if (parent === null) {
43
+ map.set(id, keepAllSentinel(meta?.selectOptions, []));
44
+ continue;
45
+ }
46
+ const narrowed = dependent.allChildOptions
47
+ .filter((option) => parent.includes(option.parentIdx))
48
+ .map((option) => ({
49
+ // 계층 체인이 있으면 그걸 보여 준다 — 같은 이름의 하위가 여럿일 때 구분된다
50
+ label: option.ancestorChain || option.name,
51
+ value: String(option.idx),
52
+ }));
53
+ map.set(id, keepAllSentinel(meta?.selectOptions, narrowed));
54
+ continue;
55
+ }
56
+ const custom = meta?.customCascadeFilter;
57
+ if (custom) {
58
+ const parents = custom.ancestorKeys.map((key) => numericSelections(filterValueOf(filters, key)));
59
+ try {
60
+ map.set(id, keepAllSentinel(meta?.selectOptions, custom.getOptions(...parents)));
61
+ }
62
+ catch {
63
+ // 호출부 함수가 던지면 필터를 못 그리는 것보다 빈 목록이 낫다
64
+ map.set(id, keepAllSentinel(meta?.selectOptions, []));
65
+ }
66
+ }
67
+ }
68
+ return map;
69
+ }
70
+ /**
71
+ * 상위가 바뀌어 더 이상 고를 수 없게 된 하위 값을 걷어낸다.
72
+ *
73
+ * 안 걷어내면 화면에는 없는 값이 조건으로 남아 조용히 0건이 된다.
74
+ * 선택지가 **비어 있으면 건너뛴다** — CommonCode 가 아직 안 실린 상태일 수 있어서,
75
+ * 그때 지우면 사용자가 고른 조건이 로딩 타이밍 때문에 날아간다.
76
+ */
77
+ export function pruneCascadeValues(filters, options) {
78
+ if (options.size === 0)
79
+ return filters;
80
+ const invalid = new Set();
81
+ for (const [id, valid] of options) {
82
+ if (valid.length === 0)
83
+ continue;
84
+ const current = filterValueOf(filters, id);
85
+ if (current === null || current === undefined || current === "")
86
+ continue;
87
+ const allowed = new Set(valid.map((option) => option.value));
88
+ const values = Array.isArray(current) ? current : [current];
89
+ if (values.some((value) => !allowed.has(String(value))))
90
+ invalid.add(id);
91
+ }
92
+ return invalid.size === 0 ? filters : filters.filter((f) => !invalid.has(f.id));
93
+ }
@@ -0,0 +1,38 @@
1
+ import type { RowData } from "@tanstack/react-table";
2
+ import type { TableColumnDef, TableColumnDefs, ColumnMeta } from "../types/column";
3
+ import type { FilterOperator, FilterVariant } from "../types/operators";
4
+ /** 컬럼 id — `id` 우선, 없으면 `accessorKey` */
5
+ export declare function columnKey<T extends RowData>(column: TableColumnDef<T>): string | undefined;
6
+ /** `filter.variant` 미지정 시 데이터 타입에서 유추 */
7
+ export declare function inferVariant(meta?: ColumnMeta): FilterVariant;
8
+ /** `filter.variant` > kind 의 힌트(`variant`/`dataKind`) > `meta.dataType` 유추 */
9
+ export declare function resolveVariant<T extends RowData>(column: TableColumnDef<T>): FilterVariant;
10
+ /** `filter.operators` > kind 의 `offered`(없으면 전체) > `OPERATORS_BY_VARIANT[variant]` */
11
+ export declare function resolveOperatorOptions<T extends RowData>(column: TableColumnDef<T>): readonly FilterOperator[];
12
+ /**
13
+ * 컬럼 정의에서 기본 연산자 맵을 만든다.
14
+ *
15
+ * **이것이 상태가 아니라 파생값이라는 점이 핵심이다.** 기존 구조는 같은 값을
16
+ * `generateColumnFilterFns` 로 계산해 `useFilterFnsInit` 이 effect 로 state 에
17
+ * 밀어 넣었고, 그동안 `filterFnsReady` 가 첫 조회를 막았다. 여기서는
18
+ * `useMemo(() => deriveOperators(columns), [columns])` 한 줄이면 끝이라
19
+ * 초기화 effect·게이트·중복 계산이 전부 사라진다.
20
+ */
21
+ export declare function deriveOperators<T extends RowData>(columns: TableColumnDefs<T>): Record<string, FilterOperator>;
22
+ /**
23
+ * 파생 → 초기 오버라이드 → 사용자 변경분 순으로 겹친다.
24
+ * `reset()` 은 `overrides` 만 비우면 되므로, 기존 `reset` 이
25
+ * `paramsRef.current.columnFilterFns` 를 특례로 보존하던 코드가 필요 없다.
26
+ */
27
+ export declare function mergeOperators(derived: Record<string, FilterOperator>, initial?: Record<string, FilterOperator>, overrides?: Record<string, FilterOperator>): Record<string, FilterOperator>;
28
+ /**
29
+ * 테이블 인스턴스에서 우리 `ColumnMeta` 를 꺼낸다.
30
+ *
31
+ * `ColumnDef.meta` 의 타입은 TanStack 의 `ColumnMeta` 인터페이스다. 우리는 전역
32
+ * module augmentation 대신 `TableColumnDef` 교차 타입으로 meta 를 얹었기 때문에
33
+ * (augmentation 은 이 패키지를 안 쓰는 소비자에게까지 새어 나간다), 인스턴스
34
+ * 경유로 읽을 때는 이 헬퍼로 한 번만 좁힌다.
35
+ */
36
+ export declare function columnMetaOf<Ext = {}>(columnDef: {
37
+ meta?: unknown;
38
+ } | undefined): ColumnMeta<Ext> | undefined;
@@ -0,0 +1,94 @@
1
+ import { OPERATORS_BY_VARIANT } from "../types/operators";
2
+ import { filterKindVariant, resolveFilterKind } from "../filters/kind";
3
+ /** 컬럼 id — `id` 우선, 없으면 `accessorKey` */
4
+ export function columnKey(column) {
5
+ const withKey = column;
6
+ if (withKey.id)
7
+ return withKey.id;
8
+ if (withKey.accessorKey != null)
9
+ return String(withKey.accessorKey);
10
+ return undefined;
11
+ }
12
+ /** `filter.variant` 미지정 시 데이터 타입에서 유추 */
13
+ export function inferVariant(meta) {
14
+ switch (meta?.dataType) {
15
+ case "number": return "number";
16
+ case "date": return "date";
17
+ case "datetime": return "datetime";
18
+ case "boolean": return "boolean";
19
+ case "enum":
20
+ case "select": return "select";
21
+ default: return "text";
22
+ }
23
+ }
24
+ /** `filter.variant` > kind 의 힌트(`variant`/`dataKind`) > `meta.dataType` 유추 */
25
+ export function resolveVariant(column) {
26
+ if (column.filter?.variant)
27
+ return column.filter.variant;
28
+ const kind = resolveFilterKind(column);
29
+ if (kind)
30
+ return filterKindVariant(kind);
31
+ return inferVariant(column.meta);
32
+ }
33
+ /** `filter.operators` > kind 의 `offered`(없으면 전체) > `OPERATORS_BY_VARIANT[variant]` */
34
+ export function resolveOperatorOptions(column) {
35
+ const declared = column.filter?.operators;
36
+ if (declared)
37
+ return declared;
38
+ const kind = resolveFilterKind(column);
39
+ if (kind)
40
+ return (kind.offered ?? kind.operatorNames);
41
+ return OPERATORS_BY_VARIANT[resolveVariant(column)];
42
+ }
43
+ /**
44
+ * 컬럼 정의에서 기본 연산자 맵을 만든다.
45
+ *
46
+ * **이것이 상태가 아니라 파생값이라는 점이 핵심이다.** 기존 구조는 같은 값을
47
+ * `generateColumnFilterFns` 로 계산해 `useFilterFnsInit` 이 effect 로 state 에
48
+ * 밀어 넣었고, 그동안 `filterFnsReady` 가 첫 조회를 막았다. 여기서는
49
+ * `useMemo(() => deriveOperators(columns), [columns])` 한 줄이면 끝이라
50
+ * 초기화 effect·게이트·중복 계산이 전부 사라진다.
51
+ */
52
+ export function deriveOperators(columns) {
53
+ const result = {};
54
+ for (const column of columns) {
55
+ const key = columnKey(column);
56
+ if (!key)
57
+ continue;
58
+ const spec = column.filter;
59
+ const options = resolveOperatorOptions(column);
60
+ // 명시 기본값 > 명시 목록의 첫 항목(기존 규칙) > kind 의 기본 연산자 > 후보 첫 항목
61
+ const kindDefault = resolveFilterKind(column)?.defaultOperator;
62
+ result[key] = spec?.defaultOperator
63
+ ?? (spec?.operators ? options[0] : undefined)
64
+ ?? kindDefault
65
+ ?? options[0]
66
+ ?? "contains";
67
+ // 통합 cascade: 레벨 키는 컬럼이 없어도 서버 필터 연산자가 필요하다
68
+ for (const level of column.meta?.cascadeFilterLevels ?? []) {
69
+ result[level.key] = (level.operator ||
70
+ level.operatorOptions?.[0] ||
71
+ "inArray");
72
+ }
73
+ }
74
+ return result;
75
+ }
76
+ /**
77
+ * 파생 → 초기 오버라이드 → 사용자 변경분 순으로 겹친다.
78
+ * `reset()` 은 `overrides` 만 비우면 되므로, 기존 `reset` 이
79
+ * `paramsRef.current.columnFilterFns` 를 특례로 보존하던 코드가 필요 없다.
80
+ */
81
+ export function mergeOperators(derived, initial, overrides) {
82
+ return { ...derived, ...initial, ...overrides };
83
+ }
84
+ /**
85
+ * 테이블 인스턴스에서 우리 `ColumnMeta` 를 꺼낸다.
86
+ *
87
+ * `ColumnDef.meta` 의 타입은 TanStack 의 `ColumnMeta` 인터페이스다. 우리는 전역
88
+ * module augmentation 대신 `TableColumnDef` 교차 타입으로 meta 를 얹었기 때문에
89
+ * (augmentation 은 이 패키지를 안 쓰는 소비자에게까지 새어 나간다), 인스턴스
90
+ * 경유로 읽을 때는 이 헬퍼로 한 번만 좁힌다.
91
+ */
92
+ export function columnMetaOf(columnDef) {
93
+ return columnDef?.meta;
94
+ }
@@ -0,0 +1,19 @@
1
+ import type { RowData } from "@tanstack/react-table";
2
+ import type { TableColumnDefs } from "../types/column";
3
+ import type { SortEntry } from "../types/request";
4
+ /**
5
+ * 통합 컬럼의 정렬을 실제 필드로 펼친다 (기존 `expandCascadeSorting` 계승).
6
+ *
7
+ * cascade 통합 컬럼이나 일정 통합 컬럼은 행에 없는 가상 컬럼이라 그 id 를 그대로
8
+ * 서버에 보내면 정렬이 먹지 않거나 400 이 난다. `meta.sortExpandsTo` 에 적힌
9
+ * 실제 필드 순서(상위 → 하위)로, 원래 항목과 **같은 방향**의 정렬 항목들로 바꾼다.
10
+ *
11
+ * - UI 상태(`SearchState.sorting`)는 통합 컬럼 id 를 유지한다 — 헤더의 정렬 화살표는
12
+ * 통합 컬럼에 그려져야 한다. 펼치는 곳은 요청(`TableSearch.request`) 조립부다.
13
+ * - 같은 id 가 두 번 나오면 앞의 것만 남긴다 (두 통합 컬럼이 같은 필드를 공유할 때
14
+ * 뒤쪽은 어차피 순서에 영향이 없다).
15
+ * - 펼친 항목은 `key` 를 갖지 않는다 — 통합 컬럼의 `key` 는 통합 컬럼용이다.
16
+ *
17
+ * 클라이언트 `applySorting` 은 `key ?? id` 를 읽으므로 펼친 요청이 그대로 동작한다.
18
+ */
19
+ export declare function expandSorting<T extends RowData>(sorting: SortEntry[] | undefined, columns: TableColumnDefs<T>): SortEntry[];
@@ -0,0 +1,52 @@
1
+ import { columnKey } from "./deriveOperators";
2
+ /**
3
+ * 통합 컬럼의 정렬을 실제 필드로 펼친다 (기존 `expandCascadeSorting` 계승).
4
+ *
5
+ * cascade 통합 컬럼이나 일정 통합 컬럼은 행에 없는 가상 컬럼이라 그 id 를 그대로
6
+ * 서버에 보내면 정렬이 먹지 않거나 400 이 난다. `meta.sortExpandsTo` 에 적힌
7
+ * 실제 필드 순서(상위 → 하위)로, 원래 항목과 **같은 방향**의 정렬 항목들로 바꾼다.
8
+ *
9
+ * - UI 상태(`SearchState.sorting`)는 통합 컬럼 id 를 유지한다 — 헤더의 정렬 화살표는
10
+ * 통합 컬럼에 그려져야 한다. 펼치는 곳은 요청(`TableSearch.request`) 조립부다.
11
+ * - 같은 id 가 두 번 나오면 앞의 것만 남긴다 (두 통합 컬럼이 같은 필드를 공유할 때
12
+ * 뒤쪽은 어차피 순서에 영향이 없다).
13
+ * - 펼친 항목은 `key` 를 갖지 않는다 — 통합 컬럼의 `key` 는 통합 컬럼용이다.
14
+ *
15
+ * 클라이언트 `applySorting` 은 `key ?? id` 를 읽으므로 펼친 요청이 그대로 동작한다.
16
+ */
17
+ export function expandSorting(sorting, columns) {
18
+ if (!sorting || sorting.length === 0)
19
+ return sorting ?? [];
20
+ const aliases = new Map();
21
+ for (const column of columns) {
22
+ const id = columnKey(column);
23
+ const targets = column.meta?.sortExpandsTo;
24
+ if (id && targets && targets.length > 0)
25
+ aliases.set(id, targets);
26
+ }
27
+ if (aliases.size === 0)
28
+ return sorting;
29
+ const seen = new Set();
30
+ const expanded = [];
31
+ let changed = false;
32
+ for (const entry of sorting) {
33
+ const targets = aliases.get(entry.id);
34
+ if (!targets) {
35
+ if (seen.has(entry.id)) {
36
+ changed = true;
37
+ continue;
38
+ }
39
+ seen.add(entry.id);
40
+ expanded.push(entry);
41
+ continue;
42
+ }
43
+ changed = true;
44
+ for (const id of targets) {
45
+ if (seen.has(id))
46
+ continue;
47
+ seen.add(id);
48
+ expanded.push({ id, desc: entry.desc });
49
+ }
50
+ }
51
+ return changed ? expanded : sorting;
52
+ }