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,17 @@
1
+ import type { SxProps, Theme } from "@mui/material";
2
+ import type { Column, RowData, Table } from "@tanstack/react-table";
3
+ /**
4
+ * 고정 컬럼의 sticky 스타일. 왼쪽은 `getStart("left")`, 오른쪽은 `getAfter("right")` 오프셋.
5
+ * 고정 그룹의 마지막 열에 경계 그림자를 넣어 어디까지 고정인지 보이게 한다.
6
+ * `zIndex` 는 헤더(sticky header 가 이미 2)와 겹치지 않게 호출자가 정한다.
7
+ */
8
+ export declare function pinnedSx<T extends RowData>(column: Column<T, unknown>, zIndex: number): SxProps<Theme> | undefined;
9
+ /**
10
+ * 셀 너비를 고정해야 하는가 — 고정하면 그 값, 아니면 `undefined`(내용에 맞게 자동).
11
+ *
12
+ * TanStack 은 미지정 컬럼에도 기본 size 150 을 채워 `column.getSize()` 가 항상 숫자다.
13
+ * 그 값을 그대로 셀에 박으면 참조번호 같은 짧은 컬럼이 150px 을 차지한다. 그래서
14
+ * **컬럼 정의에 size 를 적었거나, 사용자가 드래그해 columnSizing 상태가 생겼거나,
15
+ * 고정(pinned) 컬럼**일 때만 너비를 고정하고 나머지는 브라우저 표 레이아웃에 맡긴다.
16
+ */
17
+ export declare function explicitWidth<T extends RowData>(column: Column<T, unknown>, table: Table<T>): number | undefined;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 고정 컬럼의 sticky 스타일. 왼쪽은 `getStart("left")`, 오른쪽은 `getAfter("right")` 오프셋.
3
+ * 고정 그룹의 마지막 열에 경계 그림자를 넣어 어디까지 고정인지 보이게 한다.
4
+ * `zIndex` 는 헤더(sticky header 가 이미 2)와 겹치지 않게 호출자가 정한다.
5
+ */
6
+ export function pinnedSx(column, zIndex) {
7
+ const pinned = column.getIsPinned();
8
+ if (!pinned)
9
+ return undefined;
10
+ const isLeft = pinned === "left";
11
+ const boundary = isLeft ? column.getIsLastColumn("left") : column.getIsFirstColumn("right");
12
+ return {
13
+ position: "sticky",
14
+ zIndex,
15
+ backgroundColor: "background.paper",
16
+ ...(isLeft ? { left: column.getStart("left") } : { right: column.getAfter("right") }),
17
+ ...(boundary
18
+ ? {
19
+ boxShadow: isLeft
20
+ ? "inset -1px 0 0 var(--mui-palette-divider, rgba(0,0,0,0.12))"
21
+ : "inset 1px 0 0 var(--mui-palette-divider, rgba(0,0,0,0.12))",
22
+ }
23
+ : {}),
24
+ };
25
+ }
26
+ /** 컬럼 정의 배열 → `{ id: 선언된 size }` (배열 참조 단위로 캐시) */
27
+ const declaredSizeCache = new WeakMap();
28
+ function declaredSizes(columns) {
29
+ const cached = declaredSizeCache.get(columns);
30
+ if (cached)
31
+ return cached;
32
+ const map = new Map();
33
+ const visit = (defs) => {
34
+ for (const def of defs) {
35
+ const d = def;
36
+ const id = d.id ?? (d.accessorKey != null ? String(d.accessorKey) : undefined);
37
+ if (id && typeof d.size === "number")
38
+ map.set(id, d.size);
39
+ if (Array.isArray(d.columns))
40
+ visit(d.columns);
41
+ }
42
+ };
43
+ visit(columns);
44
+ declaredSizeCache.set(columns, map);
45
+ return map;
46
+ }
47
+ /**
48
+ * 셀 너비를 고정해야 하는가 — 고정하면 그 값, 아니면 `undefined`(내용에 맞게 자동).
49
+ *
50
+ * TanStack 은 미지정 컬럼에도 기본 size 150 을 채워 `column.getSize()` 가 항상 숫자다.
51
+ * 그 값을 그대로 셀에 박으면 참조번호 같은 짧은 컬럼이 150px 을 차지한다. 그래서
52
+ * **컬럼 정의에 size 를 적었거나, 사용자가 드래그해 columnSizing 상태가 생겼거나,
53
+ * 고정(pinned) 컬럼**일 때만 너비를 고정하고 나머지는 브라우저 표 레이아웃에 맡긴다.
54
+ */
55
+ export function explicitWidth(column, table) {
56
+ const size = column.getSize();
57
+ if (column.getIsPinned())
58
+ return size;
59
+ const sizing = table.getState().columnSizing;
60
+ if (sizing && sizing[column.id] !== undefined)
61
+ return size;
62
+ if (declaredSizes(table.options.columns).has(column.id))
63
+ return size;
64
+ return undefined;
65
+ }
@@ -0,0 +1 @@
1
+ export { useExportFormatters } from "./useExportFormatters";
@@ -0,0 +1 @@
1
+ export { useExportFormatters } from "./useExportFormatters";
@@ -0,0 +1,13 @@
1
+ import { type ExportFormatterDef, type Registry } from "../../../core/src/index";
2
+ /**
3
+ * 현재 Provider 의 export 포맷터 레지스트리 — core 전역 `exportFormatters`(내장 세트) 위에
4
+ * `registries.exportFormatters`(Provider `exportFormatters`) 를 얹은 것. core 는 Provider 를 모르므로 소비처가 이것을 넘긴다:
5
+ *
6
+ * ```ts
7
+ * const formatters = useExportFormatters();
8
+ * const exp = useTableExport(table, adapter, { formatters });
9
+ * exp.toCsv(); // Provider 포맷터로 찍힌다
10
+ * toCsv(data, { formatters }); // 훅 없이도 같다
11
+ * ```
12
+ */
13
+ export declare function useExportFormatters(): Registry<ExportFormatterDef>;
@@ -0,0 +1,17 @@
1
+ import { exportFormatters as defaultExportFormatters } from "../../../core/src/index";
2
+ import { useKokoTableConfig } from "../provider/KokoTableProvider";
3
+ import { layerRegistry, registriesOf } from "../content/cellRenderers";
4
+ /**
5
+ * 현재 Provider 의 export 포맷터 레지스트리 — core 전역 `exportFormatters`(내장 세트) 위에
6
+ * `registries.exportFormatters`(Provider `exportFormatters`) 를 얹은 것. core 는 Provider 를 모르므로 소비처가 이것을 넘긴다:
7
+ *
8
+ * ```ts
9
+ * const formatters = useExportFormatters();
10
+ * const exp = useTableExport(table, adapter, { formatters });
11
+ * exp.toCsv(); // Provider 포맷터로 찍힌다
12
+ * toCsv(data, { formatters }); // 훅 없이도 같다
13
+ * ```
14
+ */
15
+ export function useExportFormatters() {
16
+ return layerRegistry(defaultExportFormatters, registriesOf(useKokoTableConfig()).exportFormatters);
17
+ }
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from "react";
2
+ import type { RowData } from "@tanstack/react-table";
3
+ import type { AnyFilterSource } from "../../../core/src/index";
4
+ export type GridColCount = 1 | 2 | 3 | 4 | 6 | 12;
5
+ export interface GridColsConfig {
6
+ xs: GridColCount;
7
+ sm: GridColCount;
8
+ md: GridColCount;
9
+ lg: GridColCount;
10
+ xl: GridColCount;
11
+ }
12
+ /** 반응형 열 수 → MUI Grid size (12분할). 가상 컬럼 섹션 등과 공유한다 */
13
+ export declare function resolveFilterGridSize(gridCols?: Partial<GridColsConfig> | GridColCount): {
14
+ xs: number;
15
+ sm: number;
16
+ md: number;
17
+ lg: number;
18
+ xl: number;
19
+ };
20
+ export interface FilterAreaProps<T extends RowData> {
21
+ /**
22
+ * 테이블 인스턴스든 검색 상태든 상관없다.
23
+ * 기존 호출부는 `source={table}` 그대로, 카드 화면은 `source={search}`.
24
+ */
25
+ source: AnyFilterSource<T>;
26
+ /** column = 세로 일렬(사이드 패널·모달), grid = 반응형 다열(상단 필터) */
27
+ direction?: "column" | "grid";
28
+ gridCols?: Partial<GridColsConfig> | GridColCount;
29
+ /** @deprecated "상세 조건" 접이식 그룹은 제거됐다. 받아도 아무 효과가 없다 */
30
+ collapseDetail?: boolean;
31
+ /** 목록 끝에 덧붙일 항목 (가상 컬럼 섹션 등) */
32
+ children?: ReactNode;
33
+ }
34
+ /**
35
+ * 필터 구역.
36
+ *
37
+ * `getFilterFields()` 만 요구하므로 테이블 인스턴스가 없어도 그려진다 —
38
+ * 카드 화면·차트 화면에서 같은 컴포넌트를 그대로 쓴다.
39
+ */
40
+ export declare function FilterArea<T extends RowData>({ source, direction, gridCols, collapseDetail, children, }: FilterAreaProps<T>): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Grid, Stack, Typography } from "@mui/material";
3
+ import { toFilterSource } from "../../../core/src/index";
4
+ import { FilterInput } from "./FilterInput";
5
+ import { useKokoTableConfig } from "../provider/KokoTableProvider";
6
+ /** 반응형 열 수 → MUI Grid size (12분할). 가상 컬럼 섹션 등과 공유한다 */
7
+ export function resolveFilterGridSize(gridCols) {
8
+ let merged = { xs: 1, sm: 2, md: 3, lg: 4, xl: 6 };
9
+ if (typeof gridCols === "number") {
10
+ merged = { xs: gridCols, sm: gridCols, md: gridCols, lg: gridCols, xl: gridCols };
11
+ }
12
+ else if (gridCols) {
13
+ merged = { ...merged, ...gridCols };
14
+ }
15
+ const size = (cols) => Math.floor(12 / cols);
16
+ return { xs: size(merged.xs), sm: size(merged.sm), md: size(merged.md), lg: size(merged.lg), xl: size(merged.xl) };
17
+ }
18
+ /**
19
+ * 필터 구역.
20
+ *
21
+ * `getFilterFields()` 만 요구하므로 테이블 인스턴스가 없어도 그려진다 —
22
+ * 카드 화면·차트 화면에서 같은 컴포넌트를 그대로 쓴다.
23
+ */
24
+ export function FilterArea({ source, direction = "column", gridCols, collapseDetail = false, children, }) {
25
+ const { labels } = useKokoTableConfig();
26
+ const fields = toFilterSource(source).getFilterFields();
27
+ // "상세 조건" 접이식 그룹은 없앴다 — 표시를 접은 컬럼의 필터도 다른 필터와 같은 자리에 그린다.
28
+ // `collapseDetail`(구 옵트인) 과 `FilterField.detail` 은 받아만 두고 배치에는 쓰지 않는다.
29
+ void collapseDetail;
30
+ const primary = fields;
31
+ const renderField = (field) => (_jsxs(Box, { children: [_jsx(Typography, { variant: "body2", sx: { fontWeight: 500, color: "text.secondary", mb: 0.5 }, children: field.header }), _jsx(FilterInput, { field: field })] }, field.id));
32
+ if (direction === "grid") {
33
+ const size = resolveFilterGridSize(gridCols);
34
+ return (_jsxs(Grid, { container: true, spacing: 2, children: [primary.map((field) => (_jsx(Grid, { size: size, children: renderField(field) }, field.id))), children] }));
35
+ }
36
+ return (_jsxs(Stack, { spacing: 2, children: [primary.map(renderField), children] }));
37
+ }
@@ -0,0 +1,14 @@
1
+ import type { FilterField } from "../../../core/src/index";
2
+ /**
3
+ * 필드 하나를 그린다.
4
+ *
5
+ * 컬럼이 `filter.render` 를 선언했으면 **그것이 최우선**이다 — 사용처가 정의한
6
+ * 커스텀 필터가 기본 입력에 밀리면 선언한 의미가 없다.
7
+ * 그 다음은 Provider(`filterKinds[kind.id].editor` → `filterInputs[variant]`),
8
+ * 그 다음 `kind.editor`, 마지막이 내장 기본 입력이다 (`resolveFilterEditor`).
9
+ *
10
+ * `filterKinds` 는 Provider 가 아직 모르는 설정일 수 있어 옵셔널로 읽는다.
11
+ */
12
+ export declare function FilterInput({ field }: {
13
+ field: FilterField;
14
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,18 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useKokoTableConfig } from "../provider/KokoTableProvider";
3
+ import { resolveFilterEditor } from "./kinds";
4
+ /**
5
+ * 필드 하나를 그린다.
6
+ *
7
+ * 컬럼이 `filter.render` 를 선언했으면 **그것이 최우선**이다 — 사용처가 정의한
8
+ * 커스텀 필터가 기본 입력에 밀리면 선언한 의미가 없다.
9
+ * 그 다음은 Provider(`filterKinds[kind.id].editor` → `filterInputs[variant]`),
10
+ * 그 다음 `kind.editor`, 마지막이 내장 기본 입력이다 (`resolveFilterEditor`).
11
+ *
12
+ * `filterKinds` 는 Provider 가 아직 모르는 설정일 수 있어 옵셔널로 읽는다.
13
+ */
14
+ export function FilterInput({ field }) {
15
+ const config = useKokoTableConfig();
16
+ const editor = resolveFilterEditor(field, { filterKinds: config.filterKinds, filterInputs: config.filterInputs });
17
+ return editor ? _jsx(_Fragment, { children: editor(field) }) : null;
18
+ }
@@ -0,0 +1,14 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ type PeriodMode = "month" | "quarter" | "year";
3
+ /** 연/월(또는 분기) 선택 → `[start, end]` 날짜 문자열 (서버가 종료일을 그 날 전체로 해석하므로 날짜만) */
4
+ export declare function periodRange(mode: PeriodMode, year: number, unit: number): [string, string];
5
+ /**
6
+ * 날짜 필터 — 범위 두 칸 또는 상대 기간 토큰.
7
+ *
8
+ * 값은 `"2025-01-31"` 처럼 **날짜만** 보낸다. 서버가 이를 그 날 전체(반열림)로
9
+ * 해석하므로, 시각을 붙여 보내면 마지막 날이 통째로 빠진다.
10
+ */
11
+ export declare function DateFilterInput({ field }: {
12
+ field: FilterField;
13
+ }): import("react").JSX.Element;
14
+ export {};
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, MenuItem, Stack, TextField } from "@mui/material";
4
+ import { PERIOD_TOKENS } from "../../../../core/src/index";
5
+ import { OperatorMenu } from "./OperatorMenu";
6
+ const RANGE_OPERATORS = new Set(["between", "betweenInclusive", "dateBetween"]);
7
+ /** 상대 기간 토큰 라벨 — 백엔드 `PERIODS` 키와 1:1 */
8
+ const PERIOD_LABELS = {
9
+ today: "오늘", yesterday: "어제",
10
+ thisWeek: "이번 주", lastWeek: "지난 주",
11
+ thisMonth: "이번 달", lastMonth: "지난 달",
12
+ thisQuarter: "이번 분기", lastQuarter: "지난 분기",
13
+ thisYear: "올해", lastYear: "작년",
14
+ last7days: "최근 7일", last30days: "최근 30일",
15
+ last90days: "최근 90일", last12months: "최근 12개월",
16
+ };
17
+ const asText = (value) => value === null || value === undefined ? "" : String(value);
18
+ const PERIOD_MODES = new Set(["month", "quarter", "year"]);
19
+ const pad2 = (n) => String(n).padStart(2, "0");
20
+ const lastDay = (year, month) => new Date(year, month, 0).getDate();
21
+ /** 연/월(또는 분기) 선택 → `[start, end]` 날짜 문자열 (서버가 종료일을 그 날 전체로 해석하므로 날짜만) */
22
+ export function periodRange(mode, year, unit) {
23
+ if (mode === "year")
24
+ return [`${year}-01-01`, `${year}-12-31`];
25
+ const startMonth = mode === "quarter" ? (unit - 1) * 3 + 1 : unit;
26
+ const endMonth = mode === "quarter" ? startMonth + 2 : unit;
27
+ return [`${year}-${pad2(startMonth)}-01`, `${year}-${pad2(endMonth)}-${pad2(lastDay(year, endMonth))}`];
28
+ }
29
+ /** 현재 값(`[start, end]`)에서 연/단위를 되읽는다. 해석 불가면 null */
30
+ function parseSelection(mode, value) {
31
+ const start = Array.isArray(value) ? asText(value[0]) : "";
32
+ const match = /^(\d{4})-(\d{2})/.exec(start);
33
+ if (!match)
34
+ return null;
35
+ const year = Number(match[1]);
36
+ const month = Number(match[2]);
37
+ if (mode === "year")
38
+ return { year, unit: 1 };
39
+ if (mode === "quarter")
40
+ return { year, unit: Math.floor((month - 1) / 3) + 1 };
41
+ return { year, unit: month };
42
+ }
43
+ /**
44
+ * 월/분기/년 선택 — `meta.dateFilterMode` 가 `month | quarter | year` 일 때.
45
+ * MUI Select 두 개(연도 + 월/분기)로 고르면 그 기간의 `[start, end]` 를 값으로 넣는다.
46
+ */
47
+ function PeriodModeInput({ field, mode }) {
48
+ const now = new Date();
49
+ const selected = parseSelection(mode, field.value);
50
+ const year = selected?.year ?? "";
51
+ const unit = selected?.unit ?? "";
52
+ const years = [];
53
+ const base = now.getFullYear();
54
+ for (let y = base + 1; y >= base - 6; y -= 1)
55
+ years.push(y);
56
+ if (selected && !years.includes(selected.year))
57
+ years.push(selected.year);
58
+ const unitCount = mode === "quarter" ? 4 : 12;
59
+ const unitLabel = (u) => (mode === "quarter" ? `${u}분기` : `${u}월`);
60
+ const commit = (nextYear, nextUnit) => {
61
+ if (nextYear === "")
62
+ return field.onChange(undefined);
63
+ const resolvedUnit = mode === "year" ? 1 : nextUnit === "" ? 1 : nextUnit;
64
+ field.onChange(periodRange(mode, nextYear, resolvedUnit));
65
+ };
66
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsxs(TextField, { select: true, size: "small", variant: "standard", sx: { minWidth: 88 }, value: year, onChange: (event) => commit(event.target.value === "" ? "" : Number(event.target.value), unit), slotProps: { select: { displayEmpty: true, inputProps: { "aria-label": "연도" } } }, children: [_jsx(MenuItem, { value: "", children: _jsx("em", { children: "\uC5F0\uB3C4" }) }), years.map((y) => _jsxs(MenuItem, { value: y, children: [y, "\uB144"] }, y))] }), mode !== "year" && (_jsxs(TextField, { select: true, size: "small", variant: "standard", sx: { minWidth: 80 }, value: unit, disabled: year === "", onChange: (event) => commit(year, event.target.value === "" ? "" : Number(event.target.value)), slotProps: { select: { displayEmpty: true, inputProps: { "aria-label": mode === "quarter" ? "분기" : "월" } } }, children: [_jsx(MenuItem, { value: "", children: _jsx("em", { children: mode === "quarter" ? "분기" : "월" }) }), Array.from({ length: unitCount }, (_, i) => i + 1).map((u) => (_jsx(MenuItem, { value: u, children: unitLabel(u) }, u)))] }))] }));
67
+ }
68
+ /**
69
+ * 날짜 필터 — 범위 두 칸 또는 상대 기간 토큰.
70
+ *
71
+ * 값은 `"2025-01-31"` 처럼 **날짜만** 보낸다. 서버가 이를 그 날 전체(반열림)로
72
+ * 해석하므로, 시각을 붙여 보내면 마지막 날이 통째로 빠진다.
73
+ */
74
+ export function DateFilterInput({ field }) {
75
+ const isPeriod = field.operator === "inPeriod";
76
+ const isRange = RANGE_OPERATORS.has(field.operator);
77
+ const mode = field.meta?.dateFilterMode;
78
+ // 월/분기/년 모드는 범위 연산자에서만 의미가 있다 — equals 등으로 바꾸면 날짜 한 칸으로 떨어진다
79
+ const periodMode = isRange && mode && PERIOD_MODES.has(mode) ? mode : null;
80
+ const current = Array.isArray(field.value) ? field.value : [undefined, undefined];
81
+ const [from, setFrom] = useState(asText(current[0]));
82
+ const [to, setTo] = useState(asText(current[1]));
83
+ const [single, setSingle] = useState(Array.isArray(field.value) ? "" : asText(field.value));
84
+ useEffect(() => {
85
+ if (Array.isArray(field.value)) {
86
+ setFrom(asText(field.value[0]));
87
+ setTo(asText(field.value[1]));
88
+ }
89
+ else {
90
+ setSingle(asText(field.value));
91
+ }
92
+ }, [field.value]);
93
+ const operatorMenu = (_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }));
94
+ if (periodMode)
95
+ return _jsx(PeriodModeInput, { field: field, mode: periodMode });
96
+ if (isPeriod) {
97
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [operatorMenu, _jsxs(TextField, { select: true, size: "small", variant: "standard", fullWidth: true, value: PERIOD_TOKENS.includes(String(field.value)) ? String(field.value) : "", onChange: (event) => {
98
+ field.onChange(event.target.value || undefined);
99
+ field.apply?.();
100
+ }, children: [_jsx(MenuItem, { value: "", children: _jsx("em", { children: "\uC804\uCCB4 \uAE30\uAC04" }) }), PERIOD_TOKENS.map((token) => (_jsx(MenuItem, { value: token, children: PERIOD_LABELS[token] ?? token }, token)))] })] }));
101
+ }
102
+ if (!isRange) {
103
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [operatorMenu, _jsx(TextField, { type: "date", size: "small", variant: "standard", fullWidth: true, value: single, onChange: (event) => {
104
+ setSingle(event.target.value);
105
+ field.onChange(event.target.value || undefined);
106
+ }, slotProps: { inputLabel: { shrink: true } } })] }));
107
+ }
108
+ const commit = (nextFrom, nextTo) => {
109
+ field.onChange(!nextFrom && !nextTo ? undefined : [nextFrom || null, nextTo || null]);
110
+ };
111
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [operatorMenu, _jsx(TextField, { type: "date", size: "small", variant: "standard", value: from, sx: { flex: 1 }, onChange: (event) => { setFrom(event.target.value); commit(event.target.value, to); }, slotProps: { inputLabel: { shrink: true } } }), _jsx(Box, { sx: { pb: 0.5, color: "text.disabled" }, children: "~" }), _jsx(TextField, { type: "date", size: "small", variant: "standard", value: to, sx: { flex: 1 }, onChange: (event) => { setTo(event.target.value); commit(from, event.target.value); }, slotProps: { inputLabel: { shrink: true } } })] }));
112
+ }
@@ -0,0 +1,10 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ /**
3
+ * 선형결합 필터 — "무엇을 몇 개 이상" 조건.
4
+ *
5
+ * `linearExists` 는 수량을 무시하므로 수량 칸을 비활성화한다. 켜 두면
6
+ * 입력한 수량이 조용히 무시돼 사용자가 결과를 오해한다.
7
+ */
8
+ export declare function LinearFilterInput({ field }: {
9
+ field: FilterField;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Button, IconButton, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
3
+ import AddIcon from "@mui/icons-material/Add";
4
+ import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
5
+ import { OperatorMenu } from "./OperatorMenu";
6
+ const QUANTITY_OPS = [
7
+ { value: "gte", label: "≥" },
8
+ { value: "gt", label: ">" },
9
+ { value: "eq", label: "=" },
10
+ { value: "lte", label: "≤" },
11
+ { value: "lt", label: "<" },
12
+ ];
13
+ /**
14
+ * 선형결합 필터 — "무엇을 몇 개 이상" 조건.
15
+ *
16
+ * `linearExists` 는 수량을 무시하므로 수량 칸을 비활성화한다. 켜 두면
17
+ * 입력한 수량이 조용히 무시돼 사용자가 결과를 오해한다.
18
+ */
19
+ export function LinearFilterInput({ field }) {
20
+ const config = field.meta?.linearConfig ?? {};
21
+ const value = field.value ?? {
22
+ relation: config.relation ?? field.id,
23
+ mode: "contains",
24
+ terms: [],
25
+ match_all: true,
26
+ target_fk: config.target_fk,
27
+ quantity_field: config.quantity_field,
28
+ };
29
+ const ignoresQuantity = field.operator === "linearExists";
30
+ const commit = (next) => field.onChange(next.terms.length === 0 ? undefined : next);
31
+ const patchTerm = (index, patch) => commit({ ...value, terms: value.terms.map((term, i) => (i === index ? { ...term, ...patch } : term)) });
32
+ return (_jsxs(Stack, { spacing: 1, children: [_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsx(Typography, { variant: "caption", color: "text.secondary", children: value.match_all ? "모든 항목 충족" : "하나라도 충족" }), _jsx(Switch, { size: "small", checked: value.match_all, onChange: (event) => commit({ ...value, match_all: event.target.checked }), inputProps: { "aria-label": "모든 항목 충족" } })] }), value.terms.map((term, index) => (_jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "flex-end", children: [_jsx(TextField, { size: "small", variant: "standard", placeholder: "\uB300\uC0C1 idx", sx: { width: 90 }, value: String(term.target_idx ?? ""), onChange: (event) => patchTerm(index, { target_idx: Number(event.target.value) || 0 }) }), _jsx(TextField, { select: true, size: "small", variant: "standard", sx: { width: 60 }, disabled: ignoresQuantity, value: term.quantity_op ?? "gte", onChange: (event) => patchTerm(index, { quantity_op: event.target.value }), children: QUANTITY_OPS.map((option) => (_jsx(MenuItem, { value: option.value, children: option.label }, option.value))) }), _jsx(TextField, { size: "small", variant: "standard", placeholder: "\uC218\uB7C9", sx: { width: 70 }, disabled: ignoresQuantity, value: term.quantity ?? "", onChange: (event) => patchTerm(index, {
33
+ quantity: event.target.value === "" ? undefined : Number(event.target.value),
34
+ }) }), _jsx(IconButton, { size: "small", "aria-label": "\uD56D\uBAA9 \uC0AD\uC81C", onClick: () => commit({ ...value, terms: value.terms.filter((_, i) => i !== index) }), children: _jsx(DeleteOutlineIcon, { fontSize: "small" }) })] }, index))), ignoresQuantity && value.terms.length > 0 && (_jsx(Box, { sx: { fontSize: 11, color: "text.disabled" }, children: "\"\uC874\uC7AC\" \uB294 \uC218\uB7C9\uC744 \uBCF4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" })), _jsx(Button, { size: "small", startIcon: _jsx(AddIcon, {}), sx: { alignSelf: "flex-start" }, onClick: () => commit({ ...value, terms: [...value.terms, { target_idx: 0, quantity_op: "gte" }] }), children: "\uD56D\uBAA9 \uCD94\uAC00" })] }));
35
+ }
@@ -0,0 +1,12 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ /** "1, 2,3\n4" → [1, 2, 3, 4] (숫자가 아닌 토큰은 버린다) */
3
+ export declare function parseNumberList(text: string): number[];
4
+ /**
5
+ * 숫자 필터 — 범위 연산자면 두 칸, 아니면 한 칸.
6
+ *
7
+ * 두 경계를 **한 컴포넌트가 직접** 그린다. 경계마다 컴포넌트를 부르는 방식은
8
+ * 같은 입력이 두 번 렌더돼 UI 가 중복된다.
9
+ */
10
+ export declare function NumberFilterInput({ field }: {
11
+ field: FilterField;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Autocomplete, Box, Chip, Stack, TextField } from "@mui/material";
4
+ import { isValuelessOperator } from "../../../../core/src/index";
5
+ import { OperatorMenu } from "./OperatorMenu";
6
+ const RANGE_OPERATORS = new Set(["between", "betweenInclusive", "outsideRange"]);
7
+ /** 값이 목록인 연산자 — 번호 여러 개를 **쉼표**로 구분해 넣는다 (스프레드시트 붙여넣기의 줄바꿈도 허용) */
8
+ const LIST_OPERATORS = new Set(["inArray", "notInArray", "in", "notIn"]);
9
+ const SEPARATOR = /[,\n\r]+/;
10
+ /** "1, 2,3\n4" → [1, 2, 3, 4] (숫자가 아닌 토큰은 버린다) */
11
+ export function parseNumberList(text) {
12
+ const out = [];
13
+ for (const token of text.split(SEPARATOR)) {
14
+ const n = toNumber(token);
15
+ if (n !== null && !out.includes(n))
16
+ out.push(n);
17
+ }
18
+ return out;
19
+ }
20
+ /** 여러 숫자 — `inArray`/`notInArray` 용. 붙여 넣은 목록은 분리자로 쪼개 칩으로 만든다 */
21
+ function NumberListInput({ field }) {
22
+ const selected = Array.isArray(field.value) ? field.value.map(Number).filter((n) => !Number.isNaN(n)) : [];
23
+ const [text, setText] = useState("");
24
+ const commit = (next) => field.onChange(next.length === 0 ? undefined : next);
25
+ const addFromText = (raw) => {
26
+ const parsed = parseNumberList(raw);
27
+ if (parsed.length === 0)
28
+ return;
29
+ commit([...selected, ...parsed.filter((n) => !selected.includes(n))]);
30
+ setText("");
31
+ };
32
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsx(Autocomplete, { multiple: true, freeSolo: true, size: "small", fullWidth: true, options: [], value: selected, inputValue: text, onInputChange: (_e, next, reason) => {
33
+ // 쉼표(또는 붙여넣은 줄바꿈)가 들어오면 그 자리에서 칩으로 바꾼다
34
+ if (reason === "input" && SEPARATOR.test(next) && next.trim() !== "")
35
+ addFromText(next);
36
+ else
37
+ setText(next);
38
+ }, onChange: (_e, next) => {
39
+ const numbers = next.flatMap((v) => (typeof v === "number" ? [v] : parseNumberList(String(v))));
40
+ commit([...new Set(numbers)]);
41
+ }, onBlur: () => addFromText(text), getOptionLabel: (option) => String(option), renderTags: (tags, getTagProps) => tags.map((option, index) => {
42
+ const { key, ...rest } = getTagProps({ index });
43
+ return _jsx(Chip, { ...rest, size: "small", label: String(option) }, key);
44
+ }), renderInput: (params) => (_jsx(TextField, { ...params, variant: "standard", inputMode: "numeric", placeholder: selected.length ? "" : "번호 (쉼표로 구분)", onKeyDown: (event) => {
45
+ if (event.key === "Enter") {
46
+ event.preventDefault();
47
+ addFromText(text);
48
+ field.apply?.();
49
+ }
50
+ } })) })] }));
51
+ }
52
+ const toDisplay = (value) => value === null || value === undefined ? "" : String(value);
53
+ const toNumber = (text) => {
54
+ const trimmed = text.replace(/,/g, "").trim();
55
+ if (trimmed === "")
56
+ return null;
57
+ const parsed = Number(trimmed);
58
+ return Number.isNaN(parsed) ? null : parsed;
59
+ };
60
+ /**
61
+ * 숫자 필터 — 범위 연산자면 두 칸, 아니면 한 칸.
62
+ *
63
+ * 두 경계를 **한 컴포넌트가 직접** 그린다. 경계마다 컴포넌트를 부르는 방식은
64
+ * 같은 입력이 두 번 렌더돼 UI 가 중복된다.
65
+ */
66
+ export function NumberFilterInput({ field }) {
67
+ if (LIST_OPERATORS.has(field.operator))
68
+ return _jsx(NumberListInput, { field: field });
69
+ return _jsx(NumberScalarInput, { field: field });
70
+ }
71
+ function NumberScalarInput({ field }) {
72
+ const isRange = RANGE_OPERATORS.has(field.operator);
73
+ const current = Array.isArray(field.value) ? field.value : [undefined, undefined];
74
+ const [lo, setLo] = useState(toDisplay(current[0]));
75
+ const [hi, setHi] = useState(toDisplay(current[1]));
76
+ const [single, setSingle] = useState(toDisplay(Array.isArray(field.value) ? undefined : field.value));
77
+ useEffect(() => {
78
+ if (Array.isArray(field.value)) {
79
+ setLo(toDisplay(field.value[0]));
80
+ setHi(toDisplay(field.value[1]));
81
+ }
82
+ else {
83
+ setSingle(toDisplay(field.value));
84
+ }
85
+ }, [field.value]);
86
+ if (isValuelessOperator(field.operator)) {
87
+ return (_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsx(Box, { sx: { fontSize: 13, color: "text.secondary" }, children: "\uBE44\uC5B4 \uC788\uB294 \uAC12\uB9CC" })] }));
88
+ }
89
+ const commitRange = (nextLo, nextHi) => {
90
+ const low = toNumber(nextLo);
91
+ const high = toNumber(nextHi);
92
+ // 양쪽이 다 비면 필터를 지운다 — [null, null] 을 남기면 "조건 있음"으로 보인다
93
+ field.onChange(low === null && high === null ? undefined : [low, high]);
94
+ };
95
+ if (!isRange) {
96
+ // 연산자 버튼은 다른 입력들과 같이 필드 왼쪽에 둔다 (startAdornment 로 안쪽에 넣으면 혼자만 달라 보인다)
97
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsx(TextField, { size: "small", fullWidth: true, variant: "standard", value: single, placeholder: "\uC22B\uC790", inputMode: "numeric", onChange: (event) => setSingle(event.target.value), onBlur: () => field.onChange(toNumber(single) ?? undefined), onKeyDown: (event) => {
98
+ if (event.key === "Enter") {
99
+ field.onChange(toNumber(single) ?? undefined);
100
+ field.apply?.();
101
+ }
102
+ } })] }));
103
+ }
104
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsx(TextField, { size: "small", variant: "standard", placeholder: "\uCD5C\uC18C", value: lo, inputMode: "numeric", onChange: (event) => setLo(event.target.value), onBlur: () => commitRange(lo, hi), onKeyDown: (event) => { if (event.key === "Enter") {
105
+ commitRange(lo, hi);
106
+ field.apply?.();
107
+ } }, sx: { flex: 1 } }), _jsx(Box, { sx: { pb: 0.5, color: "text.disabled" }, children: "~" }), _jsx(TextField, { size: "small", variant: "standard", placeholder: "\uCD5C\uB300", value: hi, inputMode: "numeric", onChange: (event) => setHi(event.target.value), onBlur: () => commitRange(lo, hi), onKeyDown: (event) => { if (event.key === "Enter") {
108
+ commitRange(lo, hi);
109
+ field.apply?.();
110
+ } }, sx: { flex: 1 } })] }));
111
+ }
@@ -0,0 +1,14 @@
1
+ import type { FilterOperator } from "../../../../core/src/index";
2
+ export { OPERATOR_LABELS, operatorLabel } from "../../provider/labels";
3
+ export interface OperatorMenuProps {
4
+ operator: FilterOperator;
5
+ options: readonly FilterOperator[];
6
+ onChange: (operator: FilterOperator) => void;
7
+ }
8
+ /**
9
+ * 연산자 전환 버튼.
10
+ * 선택지가 1개 이하면 전환할 게 없으므로 **아이콘만** 보여준다(툴팁에 현재 연산자) —
11
+ * 필터마다 같은 자리에 같은 아이콘이 있어야 참조번호(equals 전용)도 필터로 읽힌다.
12
+ * 누를 수 있게 두면 빈 메뉴가 열려서 오히려 혼란스럽다.
13
+ */
14
+ export declare function OperatorMenu({ operator, options, onChange }: OperatorMenuProps): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { IconButton, ListItemText, Menu, MenuItem, Tooltip } from "@mui/material";
4
+ import FilterListIcon from "@mui/icons-material/FilterList";
5
+ import { useKokoTableConfig } from "../../provider/KokoTableProvider";
6
+ export { OPERATOR_LABELS, operatorLabel } from "../../provider/labels";
7
+ /**
8
+ * 연산자 전환 버튼.
9
+ * 선택지가 1개 이하면 전환할 게 없으므로 **아이콘만** 보여준다(툴팁에 현재 연산자) —
10
+ * 필터마다 같은 자리에 같은 아이콘이 있어야 참조번호(equals 전용)도 필터로 읽힌다.
11
+ * 누를 수 있게 두면 빈 메뉴가 열려서 오히려 혼란스럽다.
12
+ */
13
+ export function OperatorMenu({ operator, options, onChange }) {
14
+ const { labels } = useKokoTableConfig();
15
+ const [anchorEl, setAnchorEl] = useState(null);
16
+ const labelOf = (op) => labels.operators[op] ?? op;
17
+ const label = labelOf(operator);
18
+ if (options.length < 2) {
19
+ return (_jsx(Tooltip, { title: labels.ui.filterMode(label), children: _jsx(FilterListIcon, { fontSize: "small", sx: { color: "text.disabled", mb: 0.25 } }) }));
20
+ }
21
+ return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { title: labels.ui.filterMode(label), children: _jsx(IconButton, { size: "small", "aria-label": labels.ui.changeFilterMode, onClick: (event) => setAnchorEl(event.currentTarget), sx: { color: "text.secondary" }, children: _jsx(FilterListIcon, { fontSize: "small" }) }) }), _jsx(Menu, { anchorEl: anchorEl, open: !!anchorEl, onClose: () => setAnchorEl(null), children: options.map((option) => (_jsx(MenuItem, { selected: option === operator, onClick: () => {
22
+ onChange(option);
23
+ setAnchorEl(null);
24
+ }, children: _jsx(ListItemText, { primary: labelOf(option) }) }, option))) })] }));
25
+ }
@@ -0,0 +1,11 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ /**
3
+ * 관계(1:N) 필터 — 자식 행 조건을 그룹으로 쌓는다.
4
+ *
5
+ * **그룹 안은 AND, 그룹 사이는 연산자에 따라 OR(`hasAny`) 또는 AND(`hasAll`)** 다.
6
+ * 그룹 안의 조건들은 *같은 자식 행* 에서 모두 만족해야 한다 — 서로 다른 자식이
7
+ * 나눠 만족하는 것은 통과가 아니다.
8
+ */
9
+ export declare function RelationFilterInput({ field }: {
10
+ field: FilterField;
11
+ }): import("react").JSX.Element;