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,197 @@
1
+ import { columnKey, resolveVariant } from "../columns/deriveOperators";
2
+ import { ALL_FILTER_VALUE, EMPTY_FILTER_VALUE } from "../filters/list";
3
+ // ─────────────────────────────────────────────────────────────
4
+ // koko 컬럼 → AI 계약
5
+ // ─────────────────────────────────────────────────────────────
6
+ /**
7
+ * 값을 가진 데이터 컬럼인가.
8
+ *
9
+ * 기존 함수는 `accessorKey` 유무로 display 컬럼을 걸렀다. koko 는 `id` + `accessorFn`
10
+ * 이므로 접근자가 하나라도 있으면 데이터 컬럼이고, 접근자가 없어도 필터/타입 선언이
11
+ * 있으면(필터 전용 컬럼) 데이터 컬럼으로 본다. `koko-`/`mrt-` 접두 id 는 합성 display 컬럼.
12
+ */
13
+ function isDataColumnDef(column, key) {
14
+ if (key.startsWith("koko-") || key.startsWith("mrt-"))
15
+ return false;
16
+ const withAccessor = column;
17
+ if (typeof withAccessor.accessorFn === "function" || withAccessor.accessorKey != null)
18
+ return true;
19
+ return column.filter !== undefined || column.meta?.dataType !== undefined;
20
+ }
21
+ function headerText(column, fallback) {
22
+ const header = column.header;
23
+ return typeof header === "string" ? header : fallback;
24
+ }
25
+ /** meta.selectOptions → AI 용 {value,label} */
26
+ function toAiOptions(options, stripSentinels) {
27
+ if (!Array.isArray(options))
28
+ return undefined;
29
+ const list = stripSentinels
30
+ ? options.filter((o) => o.value !== ALL_FILTER_VALUE && o.value !== EMPTY_FILTER_VALUE)
31
+ : options;
32
+ return list.map((o) => ({ value: String(o.value), label: o.label ?? String(o.value) }));
33
+ }
34
+ /** true/false 두 개짜리 선택지는 boolean 으로 본다 (기존 fromVariant 규칙) */
35
+ function isBooleanOptions(options) {
36
+ if (!Array.isArray(options))
37
+ return false;
38
+ const values = options
39
+ .map((o) => String(o.value))
40
+ .filter((v) => v !== ALL_FILTER_VALUE && v !== EMPTY_FILTER_VALUE);
41
+ return values.length === 2 && values.includes("true") && values.includes("false");
42
+ }
43
+ /** `filter.variant`(없으면 dataType 유추) → AI 계약 타입 */
44
+ export function toAiColumnType(column) {
45
+ if (column.meta?.relationColumns?.length)
46
+ return "relation";
47
+ const variant = resolveVariant(column);
48
+ switch (variant) {
49
+ case "number": return "number";
50
+ case "date":
51
+ case "datetime": return "date";
52
+ case "boolean": return "boolean";
53
+ case "multiSelect": return "multiSelect";
54
+ case "select":
55
+ if (column.meta?.dataType === "boolean" || isBooleanOptions(column.meta?.selectOptions)) {
56
+ return "boolean";
57
+ }
58
+ return "select";
59
+ case "relation": return "relation";
60
+ default:
61
+ // text / linear / schedule / custom — 자유 텍스트로 취급
62
+ return column.meta?.dataType === "number" ? "number"
63
+ : column.meta?.dataType === "date" || column.meta?.dataType === "datetime" ? "date"
64
+ : column.meta?.dataType === "boolean" ? "boolean"
65
+ : "text";
66
+ }
67
+ }
68
+ function innerType(type) {
69
+ return type === "number" ? "number" : type === "date" || type === "datetime" ? "date" : "text";
70
+ }
71
+ /**
72
+ * koko 컬럼 정의에서 AI 용 메타데이터를 추출한다.
73
+ *
74
+ * 기존 규칙(`extract-column-meta.ts`)을 그대로 따른다:
75
+ * - 키가 없는 컬럼(display 등)은 건너뛴다
76
+ * - `meta.forceHidden` 은 값 보관 전용이라 AI 컨텍스트에 올리지 않는다
77
+ * - 통합 cascade 는 가상 컬럼 대신 `cascadeFilterLevels` 의 실제 레벨 키를 올린다
78
+ * (연산자가 equals/notEquals 면 select, 아니면 multiSelect)
79
+ * - `meta.relationColumns` 가 있으면 relation + inner
80
+ * - 그 외는 `filter.variant`/`meta.dataType` 으로 타입, select/multiSelect 는 옵션 동봉
81
+ */
82
+ export function extractColumnMeta(columns, options = {}) {
83
+ const stripSentinels = options.stripSentinels ?? false;
84
+ const result = [];
85
+ for (const column of columns) {
86
+ const key = columnKey(column);
87
+ if (!key || !isDataColumnDef(column, key))
88
+ continue;
89
+ if (column.meta?.forceHidden)
90
+ continue;
91
+ const cascadeLevels = column.meta?.cascadeFilterLevels;
92
+ if (cascadeLevels?.length) {
93
+ const header = column.header;
94
+ for (const level of cascadeLevels) {
95
+ result.push({
96
+ key: level.key,
97
+ header: typeof header === "string" ? `${header}:${level.key}` : level.key,
98
+ type: level.operator === "equals" || level.operator === "notEquals" ? "select" : "multiSelect",
99
+ });
100
+ }
101
+ continue;
102
+ }
103
+ const header = headerText(column, key);
104
+ const relationColumns = column.meta?.relationColumns;
105
+ if (relationColumns?.length) {
106
+ const inner = relationColumns.map((c) => ({
107
+ key: c.key,
108
+ header: c.label,
109
+ type: innerType(c.type),
110
+ }));
111
+ result.push({ key, header, type: "relation", inner });
112
+ continue;
113
+ }
114
+ const type = toAiColumnType(column);
115
+ const aiOptions = type === "select" || type === "multiSelect"
116
+ ? toAiOptions(column.meta?.selectOptions, stripSentinels)
117
+ : undefined;
118
+ result.push({ key, header, type, options: aiOptions });
119
+ }
120
+ return result;
121
+ }
122
+ /**
123
+ * koko `filter.variant` → 기존 `meta.columnType`.
124
+ *
125
+ * 기존 columnType ≈ koko variant 이고, relation 은 기존이 `custom` 으로 찍었다
126
+ * (server.md D-3/D-4 실측). linear/schedule/custom 도 기존에 대응이 없어 `custom`.
127
+ */
128
+ export function toLegacyColumnType(column) {
129
+ const variant = resolveVariant(column);
130
+ switch (variant) {
131
+ case "text": return "text";
132
+ case "number": return "number";
133
+ case "date": return "date";
134
+ case "datetime": return "datetime";
135
+ case "boolean": return "boolean";
136
+ case "select": return "select";
137
+ case "multiSelect": return "multiSelect";
138
+ case "relation":
139
+ case "linear":
140
+ case "schedule":
141
+ case "custom":
142
+ default:
143
+ return "custom";
144
+ }
145
+ }
146
+ /** 기존 `getFilterVariant(columnType)` 표 */
147
+ export function toLegacyFilterVariant(columnType) {
148
+ switch (columnType) {
149
+ case "text": return "autocomplete";
150
+ case "boolean":
151
+ case "select": return "select";
152
+ case "multiSelect": return "multi-select";
153
+ default: return "text";
154
+ }
155
+ }
156
+ function toLegacyCascadeLevels(levels) {
157
+ if (!levels)
158
+ return undefined;
159
+ return levels.map((level) => ({
160
+ ...level,
161
+ filterFn: level.operator,
162
+ columnFilterModeOptions: [...(level.operatorOptions ?? [])],
163
+ }));
164
+ }
165
+ /**
166
+ * koko 컬럼 배열 → 기존 `extractColumnMeta` 가 그대로 읽을 수 있는 사본.
167
+ *
168
+ * 키가 없는 컬럼(display)은 결과에서 제외한다 — 기존 함수도 accessorKey 없는 컬럼은
169
+ * 건너뛰므로 결과는 같다. 원본 객체는 변형하지 않는다.
170
+ */
171
+ export function toLegacyColumnDefs(columns) {
172
+ const result = [];
173
+ for (const column of columns) {
174
+ const key = columnKey(column);
175
+ if (!key || !isDataColumnDef(column, key))
176
+ continue;
177
+ const columnType = toLegacyColumnType(column);
178
+ const { cascadeFilterLevels, ...restMeta } = column.meta ?? {};
179
+ const legacyLevels = toLegacyCascadeLevels(cascadeFilterLevels);
180
+ const meta = {
181
+ ...restMeta,
182
+ columnType,
183
+ ...(legacyLevels ? { cascadeFilterLevels: legacyLevels } : {}),
184
+ };
185
+ result.push({
186
+ ...column,
187
+ id: key,
188
+ accessorKey: key,
189
+ filterVariant: toLegacyFilterVariant(columnType),
190
+ ...(column.meta?.selectOptions
191
+ ? { filterSelectOptions: column.meta.selectOptions.map((o) => ({ ...o })) }
192
+ : {}),
193
+ meta,
194
+ });
195
+ }
196
+ return result;
197
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 컬럼 선언 요약 — ERP 요청 R11 이 원한 모양.
3
+ *
4
+ * 왜 필요한가: ERP `REQUESTS-from-erp.md` R11 은 AI 검색 외에도 어댑터가 컬럼의
5
+ * 필터 타입·연산자·선택지·관계 하위 컬럼을 **한 번에** 읽는 함수를 원했다.
6
+ * `extractColumnMeta` 는 AI 계약 모양(type 7종)으로 좁혀 내보내므로, 연산자 목록이나
7
+ * `dataType` 이 필요한 어댑터(기존 `ColumnMetaData.columnType` 기반 분기, 연산자
8
+ * 라벨 매핑, 통합 컬럼 정렬 펼침 등)는 이쪽을 쓴다.
9
+ */
10
+ import type { RowData } from "@tanstack/react-table";
11
+ import type { ColumnDataType, SelectOption, TableColumnDefs } from "../types/column";
12
+ import type { FilterOperator, FilterVariant } from "../types/operators";
13
+ import { type LegacyColumnType } from "./aiColumnMeta";
14
+ export interface ColumnDescription {
15
+ /** 컬럼 id (= 서버 요청의 필터·정렬 키) */
16
+ id: string;
17
+ header: string;
18
+ /** 기존 `ColumnMetaData.columnType` 과 같은 어휘 (relation/linear/schedule 은 `custom`) */
19
+ columnType: LegacyColumnType;
20
+ /** koko 필터 UI 종류 — `columnType` 보다 세밀하다 (relation/linear/schedule/cascade …) */
21
+ variant: FilterVariant;
22
+ dataType?: ColumnDataType;
23
+ /** 이 컬럼이 허용하는 연산자 */
24
+ operators: readonly FilterOperator[];
25
+ /** 컬럼 정의에서 파생된 기본 연산자 */
26
+ defaultOperator: FilterOperator;
27
+ selectOptions?: SelectOption[];
28
+ relationColumns?: Array<{
29
+ key: string;
30
+ label: string;
31
+ type?: string;
32
+ }>;
33
+ sortExpandsTo?: string[];
34
+ cascadeFilterLevels?: Array<{
35
+ key: string;
36
+ operator: string;
37
+ operatorOptions: string[];
38
+ }>;
39
+ /** 필터 UI 에서 빠지는 컬럼인가 (`filter.hidden` 또는 `meta.forceHidden`) */
40
+ filterHidden: boolean;
41
+ /** 값 보관 전용 컬럼 (표시·토글·AI 컨텍스트 모두 제외 대상) */
42
+ forceHidden: boolean;
43
+ }
44
+ /**
45
+ * 선언된 컬럼 전부를 요약한다. display 컬럼(`koko-`/`mrt-` 접두)은 제외한다.
46
+ * 필터가 숨겨진 컬럼도 포함하되 `filterHidden` 으로 표시한다 — 걸러내는 기준은 호출부가 정한다.
47
+ */
48
+ export declare function describeColumns<T extends RowData>(columns: TableColumnDefs<T>): ColumnDescription[];
@@ -0,0 +1,40 @@
1
+ import { columnKey, deriveOperators, resolveOperatorOptions, resolveVariant } from "../columns/deriveOperators";
2
+ import { toLegacyColumnType } from "./aiColumnMeta";
3
+ function headerText(column, fallback) {
4
+ const header = column.header;
5
+ if (typeof header === "string")
6
+ return header;
7
+ const label = column.filterLabel;
8
+ return typeof label === "string" ? label : fallback;
9
+ }
10
+ /**
11
+ * 선언된 컬럼 전부를 요약한다. display 컬럼(`koko-`/`mrt-` 접두)은 제외한다.
12
+ * 필터가 숨겨진 컬럼도 포함하되 `filterHidden` 으로 표시한다 — 걸러내는 기준은 호출부가 정한다.
13
+ */
14
+ export function describeColumns(columns) {
15
+ const defaults = deriveOperators(columns);
16
+ const result = [];
17
+ for (const column of columns) {
18
+ const id = columnKey(column);
19
+ if (!id || id.startsWith("koko-") || id.startsWith("mrt-"))
20
+ continue;
21
+ const meta = column.meta;
22
+ const operators = resolveOperatorOptions(column);
23
+ result.push({
24
+ id,
25
+ header: headerText(column, id),
26
+ columnType: toLegacyColumnType(column),
27
+ variant: resolveVariant(column),
28
+ dataType: meta?.dataType,
29
+ operators,
30
+ defaultOperator: defaults[id] ?? operators[0] ?? "fuzzy",
31
+ ...(meta?.selectOptions ? { selectOptions: meta.selectOptions } : {}),
32
+ ...(meta?.relationColumns ? { relationColumns: meta.relationColumns } : {}),
33
+ ...(meta?.sortExpandsTo ? { sortExpandsTo: meta.sortExpandsTo } : {}),
34
+ ...(meta?.cascadeFilterLevels ? { cascadeFilterLevels: meta.cascadeFilterLevels } : {}),
35
+ filterHidden: column.filter?.hidden === true || meta?.forceHidden === true,
36
+ forceHidden: meta?.forceHidden === true,
37
+ });
38
+ }
39
+ return result;
40
+ }
@@ -0,0 +1,150 @@
1
+ import type { Column, Row, RowData, Table } from "@tanstack/react-table";
2
+ import type { ColumnDataType, ColumnMeta, TableColumnDefs } from "../types/column";
3
+ import type { DataTable } from "../types/table";
4
+ import { type Registry } from "../registry";
5
+ /** 포맷터가 보는 컬럼 — TanStack 인스턴스·컬럼 정의 어느 쪽에서 왔든 같은 모양 */
6
+ export interface ExportColumn {
7
+ id: string;
8
+ header: string;
9
+ dataType?: ColumnDataType;
10
+ meta?: ColumnMeta;
11
+ }
12
+ /** 기존 `export/types.ts ExtractedTableData` 와 필드 동일 (+ 포맷터용 `columns`) */
13
+ export interface ExtractedTableData<T> {
14
+ headers: string[];
15
+ columnKeys: string[];
16
+ rows: T[];
17
+ /** `columnKeys` 순서의 컬럼 설명 — 포맷터 해석용. 직접 만든 데이터에는 없을 수 있다 */
18
+ columns?: ExportColumn[];
19
+ }
20
+ export interface ExtractTableDataOptions<T extends RowData> {
21
+ /** 선택된 행 — 있으면 이 행들만 내보낸다 (기존 두 번째 인자) */
22
+ selectedRows?: Row<T>[];
23
+ /** 행 모델 대신 쓸 원본 행 — 서버 모드에서 전체 조회 결과를 넘길 때 */
24
+ rows?: T[];
25
+ /** 보이는 컬럼만 (기본 true). false 면 숨김 컬럼도 포함 */
26
+ visibleOnly?: boolean;
27
+ /** 행 번호·선택·액션 같은 display 컬럼 제외 (기본 true) */
28
+ excludeDisplayColumns?: boolean;
29
+ }
30
+ /** koko/MRT 가 합성하는 display 컬럼 id 접두사 */
31
+ export declare const DISPLAY_COLUMN_PREFIXES: readonly ["koko-", "mrt-"];
32
+ /**
33
+ * display 컬럼인가 — id 가 `koko-`/`mrt-` 로 시작하거나 accessor 가 없는 컬럼.
34
+ * 값이 없으니 내보내기에서 뺀다.
35
+ */
36
+ export declare function isDisplayColumn<T extends RowData>(column: Column<T, unknown>): boolean;
37
+ /**
38
+ * TanStack 테이블에서 내보내기용 데이터를 추출한다.
39
+ *
40
+ * 기본값은 기존과 같다: 보이는 leaf 컬럼 중 display 컬럼을 뺀 것, 행은 선택 행이
41
+ * 있으면 그것만 아니면 `getPrePaginationRowModel().rows`.
42
+ */
43
+ export declare function extractTableData<T extends RowData>(table: Table<T>, options?: ExtractTableDataOptions<T>): ExtractedTableData<T>;
44
+ export interface ExtractExportDataSource<T extends RowData> {
45
+ columns: TableColumnDefs<T>;
46
+ rows: T[];
47
+ /** `table.getState().columnVisibility` — 주면 false 인 컬럼을 뺀다 */
48
+ columnVisibility?: Record<string, boolean>;
49
+ /** `table.getState().columnOrder` — 주면 이 순서로 정렬(없는 id 는 정의 순서로 뒤에) */
50
+ columnOrder?: string[];
51
+ }
52
+ /**
53
+ * 인스턴스 없이 컬럼 정의 + 행 배열로 추출한다.
54
+ *
55
+ * 서버 모드에서 "전체 내보내기"는 별도 조회(예: `pageSize: PAGE_SIZE_ALL`)로 받은
56
+ * 행을 여기에 넘긴다. `meta.forceHidden` 컬럼과 키 없는 컬럼은 항상 뺀다.
57
+ */
58
+ export declare function extractExportData<T extends RowData>(source: ExtractExportDataSource<T>): ExtractedTableData<T>;
59
+ /** 행에서 컬럼 키 순서대로 값을 뽑는다 — 점 경로(`item.name`) 지원 (기존과 동일) */
60
+ export declare function extractRowValues<T>(row: T, columnKeys: string[]): unknown[];
61
+ /** 값을 내보내기용 문자열로 (기존 `formatValueForExport` 와 같은 규칙) — 포맷터가 없을 때의 기본 */
62
+ export declare function formatValueForExport(value: unknown): string;
63
+ /**
64
+ * export 포맷터 정의. `id` 가 dataType 과 같으면 그 타입 컬럼에 쓰이고,
65
+ * `when(column)` 을 주면 타입과 무관하게 조건이 참인 컬럼에 먼저 쓰인다 (등록 순서).
66
+ */
67
+ export interface ExportFormatterDef {
68
+ id: ColumnDataType | (string & {});
69
+ when?: (column: ExportColumn) => boolean;
70
+ format: (value: unknown, column: ExportColumn) => string;
71
+ }
72
+ /** 정의 헬퍼 — 타입만 고정한다 */
73
+ export declare function defineExportFormatter(def: ExportFormatterDef): ExportFormatterDef;
74
+ /** 배열·레지스트리 어느 쪽으로도 받는다 */
75
+ export type ExportFormatters = ExportFormatterDef[] | Registry<ExportFormatterDef>;
76
+ /**
77
+ * 내장 포맷터 — 기존 `formatValueForExport` 규칙을 dataType 별로 나눈 것 (dogfooding).
78
+ * 값 문자열은 예전과 같다: 문자열 날짜는 그대로, `Date` 객체만 ko-KR 로. `select` 는 `enum` 별칭.
79
+ */
80
+ export declare const BUILTIN_EXPORT_FORMATTERS: readonly ExportFormatterDef[];
81
+ /**
82
+ * 전역 기본 레지스트리. `exportFormatters.register(def)` 로 앱 전체에 추가하고,
83
+ * 화면 단위는 `exportFormatters.with({ add })` 나 mui `KokoTableProvider exportFormatters` 로.
84
+ */
85
+ export declare const exportFormatters: Registry<ExportFormatterDef>;
86
+ /**
87
+ * 컬럼에 쓸 포맷터. `when` 이 참인 정의(등록 순서) → `id === dataType` → 없음(`undefined`).
88
+ * `id` 가 dataType 과 같아도 `when` 이 있으면 조건으로만 판단한다.
89
+ */
90
+ export declare function resolveExportFormatter(column: ExportColumn, registry?: Registry<ExportFormatterDef>): ExportFormatterDef | undefined;
91
+ /** 값 하나를 포맷한다 — 포맷터가 없으면 `formatValueForExport` */
92
+ export declare function formatExportValue(value: unknown, column: ExportColumn, registry?: Registry<ExportFormatterDef>): string;
93
+ /** `data.columns` 가 없으면 키만 있는 컬럼으로 (dataType 없음 → 기본 규칙) */
94
+ export declare function exportColumnsOf<T>(data: ExtractedTableData<T>): ExportColumn[];
95
+ /** 행 하나를 컬럼 순서의 문자열 배열로 — XLSX/PDF 어댑터가 셀 값을 만들 때 쓴다 */
96
+ export declare function formatRowForExport<T>(row: T, data: ExtractedTableData<T>, formatters?: ExportFormatters): string[];
97
+ export interface CsvOptions {
98
+ /** 구분자 (기본 `,`) */
99
+ fieldSeparator?: string;
100
+ /** 모든 필드를 따옴표로 감싼다 (기본 true — 기존 export-to-csv 설정과 동일) */
101
+ quoteStrings?: boolean;
102
+ /** 헤더 행 포함 (기본 true) */
103
+ showColumnHeaders?: boolean;
104
+ /** UTF-8 BOM 접두 — Excel 이 한글을 바로 읽게 (기본 true) */
105
+ bom?: boolean;
106
+ /** 줄바꿈 (기본 `\r\n`) */
107
+ lineEnding?: string;
108
+ /** 값 포맷터 — 주면 `formatters` 보다 우선한다 (기본은 포맷터 레지스트리 → `formatValueForExport`) */
109
+ format?: (value: unknown, columnKey: string) => string;
110
+ /** 포맷터 레지스트리 또는 정의 배열(내장 세트 위에 add). 미지정 시 전역 `exportFormatters` */
111
+ formatters?: ExportFormatters;
112
+ }
113
+ /** `ExtractedTableData` → CSV 문자열 */
114
+ export declare function toCsv<T>(data: ExtractedTableData<T>, options?: CsvOptions): string;
115
+ export interface ExportAdapter<T> {
116
+ /** exceljs 등으로 워크북을 만들어 저장/반환 */
117
+ toXlsx?: (data: ExtractedTableData<T>, options?: Record<string, unknown>) => Promise<unknown>;
118
+ /** jspdf 등 */
119
+ toPdf?: (data: ExtractedTableData<T>, options?: Record<string, unknown>) => Promise<unknown>;
120
+ /** 새 창 인쇄 */
121
+ print?: (data: ExtractedTableData<T>, options?: Record<string, unknown>) => void | Promise<void>;
122
+ }
123
+ export interface TableExport<T extends RowData> {
124
+ /** 행·컬럼 추출 — 기본은 보이는 컬럼 + 현재 행 모델 */
125
+ getData: (options?: ExtractTableDataOptions<T>) => ExtractedTableData<T>;
126
+ /** 선택된 행만 */
127
+ getSelectedData: (options?: Omit<ExtractTableDataOptions<T>, "selectedRows">) => ExtractedTableData<T>;
128
+ /** CSV 문자열. `data` 미지정이면 `getData()`. 훅의 `formatters` 가 기본으로 들어간다 */
129
+ toCsv: (data?: ExtractedTableData<T>, options?: CsvOptions) => string;
130
+ /** 행 하나를 문자열 배열로 — 훅의 `formatters` 로 포맷한다 (XLSX/PDF 어댑터용) */
131
+ formatRow: (row: T, data: ExtractedTableData<T>) => string[];
132
+ /** 주입된 어댑터 (없으면 빈 객체) */
133
+ adapter: ExportAdapter<T>;
134
+ /** 이 훅이 쓰는 포맷터 레지스트리 */
135
+ formatters: Registry<ExportFormatterDef>;
136
+ }
137
+ export interface UseTableExportOptions {
138
+ /** 포맷터 레지스트리 또는 정의 배열. mui 는 `useExportFormatters()` 결과를 넣는다 */
139
+ formatters?: ExportFormatters;
140
+ }
141
+ /**
142
+ * 내보내기 훅 — 계약을 테이블 인스턴스에 묶어 준다.
143
+ *
144
+ * ```ts
145
+ * const exp = useTableExport(table, { toXlsx: (d) => import("./xlsx").then(m => m.write(d)) });
146
+ * download("a.csv", exp.toCsv());
147
+ * exp.adapter.toXlsx?.(exp.getSelectedData());
148
+ * ```
149
+ */
150
+ export declare function useTableExport<T extends RowData>(table: Table<T> | DataTable<T>, adapter?: ExportAdapter<T>, options?: UseTableExportOptions): TableExport<T>;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * 내보내기(export) 데이터 계약.
3
+ *
4
+ * 왜 필요한가: ERP `packages/table/export/{exportCsv,exportXlsx,exportPdf,printTable}.ts`
5
+ * 는 모두 `export/extractTableData.ts` 의 `extractTableData(table, selectedRows)` →
6
+ * `{ headers, columnKeys, rows }` 위에서 동작하고, 이 함수만 MRT 인스턴스
7
+ * (`getVisibleLeafColumns` / `getPrePaginationRowModel`, `mrt-` 접두 컬럼)에 의존한다
8
+ * (gap.md 1절 11). CSV/XLSX/PDF 변환 라이브러리는 소비 앱이 동적 import 로 들고 있으므로
9
+ * koko 는 **행·컬럼을 내주는 계약**과 라이브러리 없이 가능한 CSV 문자열 생성만 제공한다.
10
+ *
11
+ * - `extractTableData(table, options)` — TanStack `Table` 기준. 기존과 같은 반환 모양.
12
+ * - `extractExportData({ columns, rows })` — 인스턴스 없이 컬럼 정의 + 행 배열로.
13
+ * 서버 모드에서는 `getPrePaginationRowModel()` 이 현재 페이지뿐이라, 전체를 따로
14
+ * 조회한 뒤 이 오버로드(또는 `options.rows`)로 넘긴다.
15
+ * - `extractRowValues` / `formatValueForExport` — 기존과 같은 규칙.
16
+ * - `toCsv(data)` — RFC 4180 CSV 문자열. `useTableExport(table)` 훅이 셋을 묶는다.
17
+ * - `exportFormatters` — 값 → 문자열 포맷터 레지스트리(설계 문서 4절). dataType 별 내장 세트가 들어 있고,
18
+ * `when(column)` 이 참인 정의가 먼저, 그다음 `id === dataType`, 없으면 `formatValueForExport` 다.
19
+ * `toCsv`/`useTableExport` 는 `formatters` 옵션(배열·레지스트리)으로 받는다 — mui `useExportFormatters()` 가 Provider 것을 준다.
20
+ */
21
+ import { useCallback, useMemo } from "react";
22
+ import { columnKey, columnMetaOf } from "../columns/deriveOperators";
23
+ import { getValue } from "../utils/getValue";
24
+ import { createRegistry, toRegistry } from "../registry";
25
+ /** koko/MRT 가 합성하는 display 컬럼 id 접두사 */
26
+ export const DISPLAY_COLUMN_PREFIXES = ["koko-", "mrt-"];
27
+ /**
28
+ * display 컬럼인가 — id 가 `koko-`/`mrt-` 로 시작하거나 accessor 가 없는 컬럼.
29
+ * 값이 없으니 내보내기에서 뺀다.
30
+ */
31
+ export function isDisplayColumn(column) {
32
+ if (DISPLAY_COLUMN_PREFIXES.some((prefix) => column.id.startsWith(prefix)))
33
+ return true;
34
+ return typeof column.accessorFn !== "function";
35
+ }
36
+ function headerOf(header, id) {
37
+ return (typeof header === "string" ? header : id) || id;
38
+ }
39
+ /**
40
+ * TanStack 테이블에서 내보내기용 데이터를 추출한다.
41
+ *
42
+ * 기본값은 기존과 같다: 보이는 leaf 컬럼 중 display 컬럼을 뺀 것, 행은 선택 행이
43
+ * 있으면 그것만 아니면 `getPrePaginationRowModel().rows`.
44
+ */
45
+ export function extractTableData(table, options = {}) {
46
+ const { selectedRows, rows: rowsOverride, visibleOnly = true, excludeDisplayColumns = true } = options;
47
+ const leafColumns = visibleOnly ? table.getVisibleLeafColumns() : table.getAllLeafColumns();
48
+ const columns = excludeDisplayColumns ? leafColumns.filter((c) => !isDisplayColumn(c)) : leafColumns;
49
+ const headers = columns.map((c) => headerOf(c.columnDef.header, c.id));
50
+ const columnKeys = columns.map((c) => c.id);
51
+ const exportColumns = columns.map((c, i) => {
52
+ const meta = columnMetaOf(c.columnDef);
53
+ return { id: c.id, header: headers[i], dataType: meta?.dataType, meta };
54
+ });
55
+ const rows = rowsOverride
56
+ ? rowsOverride
57
+ : selectedRows?.length
58
+ ? selectedRows.map((r) => r.original)
59
+ : table.getPrePaginationRowModel().rows.map((r) => r.original);
60
+ return { headers, columnKeys, rows, columns: exportColumns };
61
+ }
62
+ /**
63
+ * 인스턴스 없이 컬럼 정의 + 행 배열로 추출한다.
64
+ *
65
+ * 서버 모드에서 "전체 내보내기"는 별도 조회(예: `pageSize: PAGE_SIZE_ALL`)로 받은
66
+ * 행을 여기에 넘긴다. `meta.forceHidden` 컬럼과 키 없는 컬럼은 항상 뺀다.
67
+ */
68
+ export function extractExportData(source) {
69
+ const { columns, rows, columnVisibility, columnOrder } = source;
70
+ const entries = [];
71
+ for (const column of columns) {
72
+ const id = columnKey(column);
73
+ if (!id)
74
+ continue;
75
+ if (column.meta?.forceHidden)
76
+ continue;
77
+ if (DISPLAY_COLUMN_PREFIXES.some((prefix) => id.startsWith(prefix)))
78
+ continue;
79
+ if (columnVisibility && columnVisibility[id] === false)
80
+ continue;
81
+ if (!columnVisibility && column.meta?.isVisible === false)
82
+ continue;
83
+ entries.push({
84
+ id, header: headerOf(column.header, id),
85
+ dataType: column.meta?.dataType, meta: column.meta,
86
+ });
87
+ }
88
+ if (columnOrder?.length) {
89
+ const rank = new Map(columnOrder.map((id, i) => [id, i]));
90
+ entries.sort((a, b) => {
91
+ const ra = rank.get(a.id) ?? Number.MAX_SAFE_INTEGER;
92
+ const rb = rank.get(b.id) ?? Number.MAX_SAFE_INTEGER;
93
+ return ra - rb;
94
+ });
95
+ }
96
+ return {
97
+ headers: entries.map((e) => e.header),
98
+ columnKeys: entries.map((e) => e.id),
99
+ rows,
100
+ columns: entries,
101
+ };
102
+ }
103
+ /** 행에서 컬럼 키 순서대로 값을 뽑는다 — 점 경로(`item.name`) 지원 (기존과 동일) */
104
+ export function extractRowValues(row, columnKeys) {
105
+ return columnKeys.map((key) => getValue(row, key));
106
+ }
107
+ /** 값을 내보내기용 문자열로 (기존 `formatValueForExport` 와 같은 규칙) — 포맷터가 없을 때의 기본 */
108
+ export function formatValueForExport(value) {
109
+ if (value == null)
110
+ return "";
111
+ if (value instanceof Date)
112
+ return value.toLocaleDateString("ko-KR");
113
+ if (typeof value === "object")
114
+ return JSON.stringify(value);
115
+ return String(value);
116
+ }
117
+ /** 정의 헬퍼 — 타입만 고정한다 */
118
+ export function defineExportFormatter(def) {
119
+ return def;
120
+ }
121
+ const toDate = (value) => {
122
+ if (value instanceof Date)
123
+ return value;
124
+ if (typeof value === "string" || typeof value === "number") {
125
+ const date = new Date(value);
126
+ return Number.isNaN(date.getTime()) ? undefined : date;
127
+ }
128
+ return undefined;
129
+ };
130
+ /**
131
+ * 내장 포맷터 — 기존 `formatValueForExport` 규칙을 dataType 별로 나눈 것 (dogfooding).
132
+ * 값 문자열은 예전과 같다: 문자열 날짜는 그대로, `Date` 객체만 ko-KR 로. `select` 는 `enum` 별칭.
133
+ */
134
+ export const BUILTIN_EXPORT_FORMATTERS = [
135
+ defineExportFormatter({ id: "text", format: (value) => formatValueForExport(value) }),
136
+ defineExportFormatter({ id: "number", format: (value) => formatValueForExport(value) }),
137
+ defineExportFormatter({
138
+ id: "date",
139
+ format: (value) => (value instanceof Date ? value.toLocaleDateString("ko-KR") : formatValueForExport(value)),
140
+ }),
141
+ defineExportFormatter({
142
+ id: "datetime",
143
+ format: (value) => (value instanceof Date ? value.toLocaleString("ko-KR") : formatValueForExport(value)),
144
+ }),
145
+ defineExportFormatter({ id: "boolean", format: (value) => formatValueForExport(value) }),
146
+ defineExportFormatter({ id: "enum", format: (value) => formatValueForExport(value) }),
147
+ defineExportFormatter({ id: "select", format: (value) => formatValueForExport(value) }),
148
+ ];
149
+ /**
150
+ * 전역 기본 레지스트리. `exportFormatters.register(def)` 로 앱 전체에 추가하고,
151
+ * 화면 단위는 `exportFormatters.with({ add })` 나 mui `KokoTableProvider exportFormatters` 로.
152
+ */
153
+ export const exportFormatters = createRegistry([...BUILTIN_EXPORT_FORMATTERS]);
154
+ /**
155
+ * 컬럼에 쓸 포맷터. `when` 이 참인 정의(등록 순서) → `id === dataType` → 없음(`undefined`).
156
+ * `id` 가 dataType 과 같아도 `when` 이 있으면 조건으로만 판단한다.
157
+ */
158
+ export function resolveExportFormatter(column, registry = exportFormatters) {
159
+ const defs = registry.list();
160
+ for (const def of defs)
161
+ if (def.when && def.when(column))
162
+ return def;
163
+ if (column.dataType) {
164
+ const byType = defs.find((def) => def.id === column.dataType && !def.when);
165
+ if (byType)
166
+ return byType;
167
+ }
168
+ return undefined;
169
+ }
170
+ /** 값 하나를 포맷한다 — 포맷터가 없으면 `formatValueForExport` */
171
+ export function formatExportValue(value, column, registry = exportFormatters) {
172
+ const def = resolveExportFormatter(column, registry);
173
+ return def ? def.format(value, column) : formatValueForExport(value);
174
+ }
175
+ /** `data.columns` 가 없으면 키만 있는 컬럼으로 (dataType 없음 → 기본 규칙) */
176
+ export function exportColumnsOf(data) {
177
+ if (data.columns && data.columns.length === data.columnKeys.length)
178
+ return data.columns;
179
+ return data.columnKeys.map((id, i) => ({ id, header: data.headers[i] ?? id }));
180
+ }
181
+ /** 행 하나를 컬럼 순서의 문자열 배열로 — XLSX/PDF 어댑터가 셀 값을 만들 때 쓴다 */
182
+ export function formatRowForExport(row, data, formatters) {
183
+ const registry = formatters ? toRegistry(formatters, [...BUILTIN_EXPORT_FORMATTERS]) : exportFormatters;
184
+ const columns = exportColumnsOf(data);
185
+ return extractRowValues(row, data.columnKeys).map((value, i) => formatExportValue(value, columns[i], registry));
186
+ }
187
+ function csvField(text, separator, quoteAll) {
188
+ const needsQuote = quoteAll || text.includes(separator) || /["\r\n]/.test(text);
189
+ return needsQuote ? `"${text.replace(/"/g, '""')}"` : text;
190
+ }
191
+ /** `ExtractedTableData` → CSV 문자열 */
192
+ export function toCsv(data, options = {}) {
193
+ const { fieldSeparator = ",", quoteStrings = true, showColumnHeaders = true, bom = true, lineEnding = "\r\n", format, formatters, } = options;
194
+ const registry = formatters ? toRegistry(formatters, [...BUILTIN_EXPORT_FORMATTERS]) : exportFormatters;
195
+ const columns = exportColumnsOf(data);
196
+ const formatCell = format
197
+ ? (value, i) => format(value, data.columnKeys[i])
198
+ : (value, i) => formatExportValue(value, columns[i], registry);
199
+ const lines = [];
200
+ if (showColumnHeaders) {
201
+ lines.push(data.headers.map((h) => csvField(h, fieldSeparator, quoteStrings)).join(fieldSeparator));
202
+ }
203
+ for (const row of data.rows) {
204
+ const values = extractRowValues(row, data.columnKeys);
205
+ lines.push(values
206
+ .map((v, i) => csvField(formatCell(v, i), fieldSeparator, quoteStrings))
207
+ .join(fieldSeparator));
208
+ }
209
+ return (bom ? "\uFEFF" : "") + lines.join(lineEnding);
210
+ }
211
+ /**
212
+ * 내보내기 훅 — 계약을 테이블 인스턴스에 묶어 준다.
213
+ *
214
+ * ```ts
215
+ * const exp = useTableExport(table, { toXlsx: (d) => import("./xlsx").then(m => m.write(d)) });
216
+ * download("a.csv", exp.toCsv());
217
+ * exp.adapter.toXlsx?.(exp.getSelectedData());
218
+ * ```
219
+ */
220
+ export function useTableExport(table, adapter = {}, options = {}) {
221
+ const { formatters: given } = options;
222
+ const formatters = useMemo(() => (given ? toRegistry(given, [...BUILTIN_EXPORT_FORMATTERS]) : exportFormatters), [given]);
223
+ const getData = useCallback((options) => extractTableData(table, options), [table]);
224
+ const getSelectedData = useCallback((options) => extractTableData(table, { ...options, selectedRows: table.getSelectedRowModel().rows }), [table]);
225
+ const csv = useCallback((data, csvOptions) => toCsv(data ?? getData(), { formatters, ...csvOptions }), [getData, formatters]);
226
+ const formatRow = useCallback((row, data) => formatRowForExport(row, data, formatters), [formatters]);
227
+ return useMemo(() => ({ getData, getSelectedData, toCsv: csv, formatRow, adapter, formatters }), [getData, getSelectedData, csv, formatRow, adapter, formatters]);
228
+ }