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,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Button, IconButton, MenuItem, Stack, 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 CONDITION_OPERATORS = [
7
+ { value: "equals", label: "같음" },
8
+ { value: "notEquals", label: "다름" },
9
+ { value: "contains", label: "포함" },
10
+ { value: "notContains", label: "미포함" },
11
+ { value: "greaterThan", label: "초과" },
12
+ { value: "greaterThanOrEqual", label: "이상" },
13
+ { value: "lessThan", label: "미만" },
14
+ { value: "lessThanOrEqual", label: "이하" },
15
+ { value: "isNotNull", label: "값 있음" },
16
+ ];
17
+ const emptyValue = { groups: [] };
18
+ /**
19
+ * 관계(1:N) 필터 — 자식 행 조건을 그룹으로 쌓는다.
20
+ *
21
+ * **그룹 안은 AND, 그룹 사이는 연산자에 따라 OR(`hasAny`) 또는 AND(`hasAll`)** 다.
22
+ * 그룹 안의 조건들은 *같은 자식 행* 에서 모두 만족해야 한다 — 서로 다른 자식이
23
+ * 나눠 만족하는 것은 통과가 아니다.
24
+ */
25
+ export function RelationFilterInput({ field }) {
26
+ const innerColumns = field.meta?.relationColumns ?? [];
27
+ const value = field.value ?? emptyValue;
28
+ const structural = field.operator === "hasChild" || field.operator === "hasNotChild";
29
+ const commit = (next) => {
30
+ const hasCondition = next.groups.some((group) => group.conditions.length > 0);
31
+ field.onChange(hasCondition ? next : undefined);
32
+ };
33
+ const update = (groupIndex, conditionIndex, patch) => {
34
+ const groups = value.groups.map((group, gi) => gi !== groupIndex
35
+ ? group
36
+ : {
37
+ conditions: group.conditions.map((condition, ci) => ci !== conditionIndex ? condition : { ...condition, ...patch }),
38
+ });
39
+ commit({ groups });
40
+ };
41
+ const header = (_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: structural
42
+ ? field.operator === "hasChild" ? "자식이 있는 행만" : "자식이 없는 행만"
43
+ : field.operator === "hasAll" ? "모든 조건 묶음을 만족" : "조건 묶음 중 하나라도 만족" })] }));
44
+ if (structural)
45
+ return header;
46
+ return (_jsxs(Stack, { spacing: 1, children: [header, value.groups.map((group, groupIndex) => (_jsx(Box, { sx: { border: "1px solid", borderColor: "divider", borderRadius: 1, p: 1 }, children: _jsxs(Stack, { spacing: 0.5, children: [group.conditions.map((condition, conditionIndex) => (_jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "flex-end", children: [_jsx(TextField, { select: true, size: "small", variant: "standard", sx: { minWidth: 90 }, value: condition.column, onChange: (event) => update(groupIndex, conditionIndex, { column: event.target.value }), children: innerColumns.map((column) => (_jsx(MenuItem, { value: column.key, children: column.label }, column.key))) }), _jsx(TextField, { select: true, size: "small", variant: "standard", sx: { minWidth: 80 }, value: condition.operator, onChange: (event) => update(groupIndex, conditionIndex, {
47
+ operator: event.target.value,
48
+ }), children: CONDITION_OPERATORS.map((option) => (_jsx(MenuItem, { value: option.value, children: option.label }, option.value))) }), _jsx(TextField, { size: "small", variant: "standard", placeholder: "\uAC12", sx: { flex: 1 }, value: String(condition.value ?? ""), onChange: (event) => update(groupIndex, conditionIndex, { value: event.target.value }) }), _jsx(IconButton, { size: "small", "aria-label": "\uC870\uAC74 \uC0AD\uC81C", onClick: () => commit({
49
+ groups: value.groups.map((g, gi) => gi !== groupIndex
50
+ ? g
51
+ : { conditions: g.conditions.filter((_, ci) => ci !== conditionIndex) }),
52
+ }), children: _jsx(DeleteOutlineIcon, { fontSize: "small" }) })] }, conditionIndex))), _jsx(Button, { size: "small", startIcon: _jsx(AddIcon, {}), sx: { alignSelf: "flex-start" }, onClick: () => commit({
53
+ groups: value.groups.map((g, gi) => gi !== groupIndex
54
+ ? g
55
+ : {
56
+ conditions: [
57
+ ...g.conditions,
58
+ { column: innerColumns[0]?.key ?? "", operator: "equals", value: "" },
59
+ ],
60
+ }),
61
+ }), children: "\uC870\uAC74 \uCD94\uAC00 (\uADF8\uB8F9 \uC548\uC740 AND)" })] }) }, groupIndex))), _jsx(Button, { size: "small", startIcon: _jsx(AddIcon, {}), sx: { alignSelf: "flex-start" }, onClick: () => commit({
62
+ groups: [
63
+ ...value.groups,
64
+ { conditions: [{ column: innerColumns[0]?.key ?? "", operator: "equals", value: "" }] },
65
+ ],
66
+ }), children: "\uC870\uAC74 \uBB36\uC74C \uCD94\uAC00" })] }));
67
+ }
@@ -0,0 +1,8 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ /**
3
+ * 일정 필터 — 예정(planned)과 실제(actual) 기간을 **동시에** 본다.
4
+ * 둘 중 하나라도 조건에 걸리면 통과다 (서버도 두 쌍을 OR 로 묶는다).
5
+ */
6
+ export declare function ScheduleFilterInput({ field }: {
7
+ field: FilterField;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Stack, TextField, Typography } from "@mui/material";
3
+ import { OperatorMenu } from "./OperatorMenu";
4
+ /**
5
+ * 일정 필터 — 예정(planned)과 실제(actual) 기간을 **동시에** 본다.
6
+ * 둘 중 하나라도 조건에 걸리면 통과다 (서버도 두 쌍을 OR 로 묶는다).
7
+ */
8
+ export function ScheduleFilterInput({ field }) {
9
+ const value = field.value ?? {};
10
+ const fields = field.meta?.scheduleFields;
11
+ const isOverlaps = field.operator === "scheduleOverlaps";
12
+ const commit = (next) => {
13
+ const filled = isOverlaps ? next.start && next.end : next.date;
14
+ field.onChange(filled ? { ...next, ...(fields ? { fields } : {}) } : undefined);
15
+ };
16
+ return (_jsxs(Stack, { spacing: 0.5, children: [_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), isOverlaps ? (_jsxs(_Fragment, { children: [_jsx(TextField, { type: "date", size: "small", variant: "standard", sx: { flex: 1 }, value: value.start ?? "", onChange: (event) => commit({ ...value, start: event.target.value }), slotProps: { inputLabel: { shrink: true } } }), _jsx(Box, { sx: { pb: 0.5, color: "text.disabled" }, children: "~" }), _jsx(TextField, { type: "date", size: "small", variant: "standard", sx: { flex: 1 }, value: value.end ?? "", onChange: (event) => commit({ ...value, end: event.target.value }), slotProps: { inputLabel: { shrink: true } } })] })) : (_jsx(TextField, { type: "date", size: "small", variant: "standard", fullWidth: true, value: value.date ?? "", onChange: (event) => commit({ ...value, date: event.target.value }), slotProps: { inputLabel: { shrink: true } } }))] }), _jsx(Typography, { variant: "caption", color: "text.disabled", children: "\uC608\uC815\u00B7\uC2E4\uC81C \uAE30\uAC04 \uC911 \uD558\uB098\uB77C\uB3C4 \uAC78\uB9AC\uBA74 \uD45C\uC2DC\uB429\uB2C8\uB2E4" })] }));
17
+ }
@@ -0,0 +1,16 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ /** 단일 선택 — "전체"를 고르면 필터를 지운다 */
3
+ export declare function SelectFilterInput({ field }: {
4
+ field: FilterField;
5
+ }): import("react").JSX.Element;
6
+ /**
7
+ * 다중 선택. 값은 **문자열 배열**로 나간다 — 서버 `ListFilter` 가 그 형태를 읽는다.
8
+ * 선택지를 다 지우면 `undefined` 로 되돌려 "조건 없음"이 되게 한다.
9
+ */
10
+ export declare function MultiSelectFilterInput({ field }: {
11
+ field: FilterField;
12
+ }): import("react").JSX.Element;
13
+ /** 불리언 — 선택지가 true/false 로 고정이라 단일 선택과 같다 */
14
+ export declare function BooleanFilterInput({ field }: {
15
+ field: FilterField;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Autocomplete, Checkbox, Chip, MenuItem, Stack, TextField } from "@mui/material";
3
+ import { ALL_FILTER_VALUE, EMPTY_FILTER_VALUE } from "../../../../core/src/index";
4
+ import { OperatorMenu } from "./OperatorMenu";
5
+ function options(field) {
6
+ return field.meta?.selectOptions ?? [];
7
+ }
8
+ /** "비어 있음"을 고를 수 있게 센티널을 덧붙인다 (백엔드 `EMPTY_FILTER_VALUE`) */
9
+ function withEmptyOption(list) {
10
+ return list.some((option) => option.value === EMPTY_FILTER_VALUE)
11
+ ? list
12
+ : [...list, { label: "(비어 있음)", value: EMPTY_FILTER_VALUE }];
13
+ }
14
+ /** 단일 선택 — "전체"를 고르면 필터를 지운다 */
15
+ export function SelectFilterInput({ field }) {
16
+ const list = withEmptyOption(options(field));
17
+ const value = field.value === undefined || field.value === null ? "" : String(field.value);
18
+ // 값이 없을 때 빈 칸이 아니라 "전체"(선택지에 있으면) 또는 "선택" 을 자리표시자로 보여준다
19
+ const allOption = list.find((option) => option.value === ALL_FILTER_VALUE);
20
+ const placeholder = allOption?.label ?? "선택";
21
+ const labelOf = (v) => list.find((option) => option.value === String(v))?.label ?? String(v ?? "");
22
+ return (_jsxs(Stack, { direction: "row", alignItems: "flex-end", spacing: 1, children: [_jsx(OperatorMenu, { operator: field.operator, options: field.operatorOptions, onChange: field.onOperatorChange }), _jsx(TextField, { select: true, size: "small", variant: "standard", fullWidth: true, value: value, slotProps: {
23
+ select: {
24
+ displayEmpty: true,
25
+ renderValue: (selected) => selected === "" || selected === ALL_FILTER_VALUE
26
+ ? _jsx("span", { style: { color: "var(--mui-palette-text-disabled, rgba(0,0,0,0.38))" }, children: placeholder })
27
+ : labelOf(selected),
28
+ },
29
+ }, onChange: (event) => {
30
+ const next = event.target.value;
31
+ field.onChange(next === "" || next === ALL_FILTER_VALUE ? undefined : next);
32
+ field.apply?.();
33
+ }, children: list.map((option) => (_jsx(MenuItem, { value: option.value, children: option.label }, option.value))) })] }));
34
+ }
35
+ /**
36
+ * 다중 선택. 값은 **문자열 배열**로 나간다 — 서버 `ListFilter` 가 그 형태를 읽는다.
37
+ * 선택지를 다 지우면 `undefined` 로 되돌려 "조건 없음"이 되게 한다.
38
+ */
39
+ export function MultiSelectFilterInput({ field }) {
40
+ const list = withEmptyOption(options(field).filter((option) => option.value !== ALL_FILTER_VALUE));
41
+ const selected = Array.isArray(field.value) ? field.value.map(String) : [];
42
+ const selectedOptions = list.filter((option) => selected.includes(option.value));
43
+ 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, disableCloseOnSelect: true, size: "small", fullWidth: true, options: list, value: selectedOptions, isOptionEqualToValue: (option, target) => option.value === target.value, getOptionLabel: (option) => option.label, onChange: (_event, next) => {
44
+ field.onChange(next.length === 0 ? undefined : next.map((option) => option.value));
45
+ field.apply?.();
46
+ }, renderOption: (props, option, { selected: isSelected }) => {
47
+ const { key, ...rest } = props;
48
+ return (_jsxs("li", { ...rest, children: [_jsx(Checkbox, { size: "small", checked: isSelected, sx: { mr: 1, p: 0.5 } }), option.label, option.ancestorChain && (_jsx("span", { style: { marginLeft: 6, fontSize: 11, opacity: 0.6 }, children: option.ancestorChain }))] }, key));
49
+ }, renderTags: (tags, getTagProps) => tags.map((option, index) => {
50
+ const { key, ...rest } = getTagProps({ index });
51
+ return _jsx(Chip, { ...rest, size: "small", label: option.label }, key);
52
+ }), renderInput: (params) => _jsx(TextField, { ...params, variant: "standard", placeholder: selected.length ? "" : "선택" }) })] }));
53
+ }
54
+ /** 불리언 — 선택지가 true/false 로 고정이라 단일 선택과 같다 */
55
+ export function BooleanFilterInput({ field }) {
56
+ return _jsx(SelectFilterInput, { field: field });
57
+ }
@@ -0,0 +1,13 @@
1
+ import type { FilterField } from "../../../../core/src/index";
2
+ /**
3
+ * 텍스트 필터 — 논리식(`배추 ∨ ¬김치`)을 지원한다.
4
+ *
5
+ * 값은 **항상 문자열 하나**로 전송된다. 서버 `TextLogicValue.parse` 가 같은
6
+ * 문법으로 읽으므로 글리프를 그대로 실어 보내면 된다.
7
+ *
8
+ * 체인 혼합(`∨` 와 `∧` 동시 사용)은 서버가 400 을 내는 문법 위반이라
9
+ * 입력 단계에서 막고 경고를 띄운다.
10
+ */
11
+ export declare function TextFilterInput({ field }: {
12
+ field: FilterField;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { Box, Chip, Stack, TextField, Tooltip } from "@mui/material";
4
+ import { OP_SYMBOLS, isValuelessOperator, parseTextLogic } from "../../../../core/src/index";
5
+ import { OperatorMenu } from "./OperatorMenu";
6
+ const OP_LABELS = { or: "또는", and: "그리고", not: "제외" };
7
+ /** 타이핑 단축 — `||` `&&` `~~` 를 글리프로 승격 */
8
+ const TYPING_TO_OP = { "||": "or", "&&": "and", "~~": "not" };
9
+ /**
10
+ * 텍스트 필터 — 논리식(`배추 ∨ ¬김치`)을 지원한다.
11
+ *
12
+ * 값은 **항상 문자열 하나**로 전송된다. 서버 `TextLogicValue.parse` 가 같은
13
+ * 문법으로 읽으므로 글리프를 그대로 실어 보내면 된다.
14
+ *
15
+ * 체인 혼합(`∨` 와 `∧` 동시 사용)은 서버가 400 을 내는 문법 위반이라
16
+ * 입력 단계에서 막고 경고를 띄운다.
17
+ */
18
+ export function TextFilterInput({ field }) {
19
+ const value = typeof field.value === "string" ? field.value : field.value == null ? "" : String(field.value);
20
+ const [draft, setDraft] = useState(value);
21
+ useEffect(() => setDraft(value), [value]);
22
+ const valueless = isValuelessOperator(field.operator);
23
+ const parsed = useMemo(() => parseTextLogic(draft), [draft]);
24
+ const mixedChain = draft.includes(OP_SYMBOLS.or) && draft.includes(OP_SYMBOLS.and);
25
+ const invalid = draft.length > 0 && parsed === null && (draft.includes(OP_SYMBOLS.or) || draft.includes(OP_SYMBOLS.and) || draft.includes(OP_SYMBOLS.not));
26
+ const commit = (next) => {
27
+ setDraft(next);
28
+ field.onChange(next === "" ? undefined : next);
29
+ };
30
+ const insertOp = (op) => {
31
+ const glyph = OP_SYMBOLS[op];
32
+ const next = op === "not" ? `${draft}${draft && !draft.endsWith(" ") ? " " : ""}${glyph}` : `${draft} ${glyph} `;
33
+ commit(next);
34
+ };
35
+ if (valueless) {
36
+ // 값을 안 쓰는 연산자는 입력창을 그리지 않는다 — 빈 칸을 두면 사용자가 값을 넣을 수 있다고 오해한다
37
+ 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: "\uAC12 \uC5C6\uC774 \uC801\uC6A9\uB429\uB2C8\uB2E4" })] }));
38
+ }
39
+ return (_jsxs(Stack, { spacing: 0.5, children: [_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: draft, placeholder: "\uAC80\uC0C9\uC5B4", error: invalid, helperText: mixedChain ? "또는(∨) 과 그리고(∧) 는 섞을 수 없습니다"
40
+ : invalid ? "논리식 문법이 올바르지 않습니다"
41
+ : undefined, onChange: (event) => {
42
+ let next = event.target.value;
43
+ for (const [sequence, op] of Object.entries(TYPING_TO_OP)) {
44
+ if (next.endsWith(sequence)) {
45
+ next = `${next.slice(0, -sequence.length)}${op === "not" ? "" : " "}${OP_SYMBOLS[op]}${op === "not" ? "" : " "}`;
46
+ }
47
+ }
48
+ setDraft(next);
49
+ }, onBlur: () => commit(draft), onKeyDown: (event) => {
50
+ if (event.key === "Enter") {
51
+ commit(draft);
52
+ field.apply?.();
53
+ }
54
+ } })] }), _jsx(Stack, { direction: "row", spacing: 0.5, children: Object.keys(OP_LABELS).map((op) => (_jsx(Tooltip, { title: `${OP_LABELS[op]} (${Object.keys(TYPING_TO_OP).find((k) => TYPING_TO_OP[k] === op)})`, children: _jsx(Chip, { label: `${OP_SYMBOLS[op]} ${OP_LABELS[op]}`, size: "small", variant: "outlined", onClick: () => insertOp(op), sx: { fontSize: 11, height: 20 } }) }, op))) })] }));
55
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 필터 kind 의 **표시 부분** — core 의 kind(판정·직렬화·문구·codec)에 editor·chip 을 붙인다.
3
+ *
4
+ * - `withEditor(kind, { editor, chip })` — 같은 id 의 새 kind 객체 (원본은 바꾸지 않는다)
5
+ * - `builtinFilterEditors` — 내장 kind id → 지금의 `TextFilterInput` 등 (정적 맵)
6
+ * - `registerBuiltinEditors()` — 내장 kind 를 editor 가 붙은 채로 레지스트리에 다시 등록
7
+ * - `resolveFilterEditor(field, config)` — `FilterInput` 이 쓰는 우선순위 해석
8
+ */
9
+ import type { ReactNode } from "react";
10
+ import type { AnyFilterKind, FilterField, FilterKind, FilterRender, FilterVariant } from "../../../core/src/index";
11
+ import { type FilterKindStore } from "../../../core/src/index";
12
+ export interface FilterKindDisplay<TValue = unknown, TOp extends string = string> {
13
+ editor?: FilterRender<TValue>;
14
+ chip?(value: TValue, operator: TOp): ReactNode;
15
+ }
16
+ /**
17
+ * Provider `filterKinds` 항목이 요구하는 느슨한 표시 모양. `withEditor` 의 결과가 kind 이면서 이 모양도 만족하도록
18
+ * 교차 타입으로 얹는다 — Provider 쪽 `chip: (value: unknown, operator: string)` 은 속성 함수 타입이라
19
+ * kind 의 좁은 `chip(value: TValue, operator: TOp)` 가 그대로는 들어가지 않는다(strictFunctionTypes).
20
+ */
21
+ export interface LooseFilterKindDisplay {
22
+ editor?: FilterRender<any>;
23
+ chip?(value: unknown, operator: string): ReactNode;
24
+ }
25
+ /** kind 에 editor/chip 을 붙인 **새** kind — Provider `filterKinds` 나 컬럼 `filter.kind` 에 넣는다 */
26
+ export declare function withEditor<Id extends string, TValue, TOp extends string, TRow>(kind: FilterKind<Id, TValue, TOp, TRow>, display: FilterKindDisplay<TValue, TOp>): FilterKind<Id, TValue, TOp, TRow> & LooseFilterKindDisplay;
27
+ /** 내장 kind id → 기본 입력. `kind.editor` 가 없을 때 `FilterInput` 이 여기서 찾는다 */
28
+ export declare const builtinFilterEditors: Readonly<Record<string, FilterRender>>;
29
+ /** variant 별 기본 입력 — kind 가 없는(variant 경로) 컬럼과 editor 없는 kind 의 마지막 폴백 */
30
+ export declare function defaultEditorForVariant(variant: FilterVariant): FilterRender | undefined;
31
+ /**
32
+ * 내장 kind 를 editor 가 붙은 채로 레지스트리에 다시 등록한다.
33
+ * `FilterInput` 은 이것 없이도 `builtinFilterEditors` 를 보므로 필수는 아니다 —
34
+ * `filterKinds.get("text").editor` 처럼 레지스트리에서 표시 부분까지 읽고 싶을 때 부른다.
35
+ */
36
+ export declare function registerBuiltinEditors(store?: FilterKindStore): void;
37
+ /** Provider 가 넘기는 `filterKinds` 항목 — kind 전체이거나 표시 부분만 */
38
+ export type ProvidedFilterKinds = Readonly<Record<string, (FilterKindDisplay & {
39
+ id?: string;
40
+ }) | AnyFilterKind>>;
41
+ export interface ResolveFilterEditorConfig {
42
+ /** Provider `filterKinds` — `{ [id]: withEditor(kind, …) | { editor, chip } }` */
43
+ filterKinds?: ProvidedFilterKinds;
44
+ /** Provider `filterInputs[variant]` (기존 통로) */
45
+ filterInputs?: Partial<Record<FilterVariant, FilterRender<any>>>;
46
+ }
47
+ /**
48
+ * 입력 렌더러 해석 — 우선순위는 **컬럼 > Provider > kind > 내장**:
49
+ * 1. `column.filter.render` (`field.render`)
50
+ * 2. `Provider.filterKinds[kind.id].editor`
51
+ * 3. `Provider.filterInputs[variant]`
52
+ * 4. `kind.editor` (`withEditor` 로 붙인 것)
53
+ * 5. 내장 kind 의 기본 입력 (`builtinFilterEditors[kind.id]`)
54
+ * 6. variant 별 기본 입력 (`custom` 이면 없음)
55
+ */
56
+ export declare function resolveFilterEditor(field: FilterField, config?: ResolveFilterEditorConfig): FilterRender | undefined;
57
+ /** 칩 렌더러 해석 — 우선순위는 editor 와 같다 (Provider > kind) */
58
+ export declare function resolveFilterChip(field: FilterField, config?: ResolveFilterEditorConfig): FilterKindDisplay["chip"] | undefined;
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { filterKinds, BUILTIN_FILTER_KINDS } from "../../../core/src/index";
3
+ import { TextFilterInput } from "./inputs/TextFilterInput";
4
+ import { NumberFilterInput } from "./inputs/NumberFilterInput";
5
+ import { DateFilterInput } from "./inputs/DateFilterInput";
6
+ import { BooleanFilterInput, MultiSelectFilterInput, SelectFilterInput } from "./inputs/SelectFilterInput";
7
+ import { RelationFilterInput } from "./inputs/RelationFilterInput";
8
+ import { LinearFilterInput } from "./inputs/LinearFilterInput";
9
+ import { ScheduleFilterInput } from "./inputs/ScheduleFilterInput";
10
+ /** kind 에 editor/chip 을 붙인 **새** kind — Provider `filterKinds` 나 컬럼 `filter.kind` 에 넣는다 */
11
+ export function withEditor(kind, display) {
12
+ return {
13
+ ...kind,
14
+ ...(display.editor ? { editor: display.editor } : {}),
15
+ ...(display.chip ? { chip: display.chip } : {}),
16
+ };
17
+ }
18
+ /** select / multiSelect 는 같은 enum kind — `field.variant` 로 가른다 */
19
+ const enumEditor = (field) => field.variant === "multiSelect" ? _jsx(MultiSelectFilterInput, { field: field }) : _jsx(SelectFilterInput, { field: field });
20
+ /** 내장 kind id → 기본 입력. `kind.editor` 가 없을 때 `FilterInput` 이 여기서 찾는다 */
21
+ export const builtinFilterEditors = {
22
+ text: (field) => _jsx(TextFilterInput, { field: field }),
23
+ number: (field) => _jsx(NumberFilterInput, { field: field }),
24
+ date: (field) => _jsx(DateFilterInput, { field: field }),
25
+ period: (field) => _jsx(DateFilterInput, { field: field }),
26
+ boolean: (field) => _jsx(BooleanFilterInput, { field: field }),
27
+ enum: enumEditor,
28
+ commonCode: enumEditor,
29
+ cascade: enumEditor,
30
+ relation: (field) => _jsx(RelationFilterInput, { field: field }),
31
+ linear: (field) => _jsx(LinearFilterInput, { field: field }),
32
+ schedule: (field) => _jsx(ScheduleFilterInput, { field: field }),
33
+ };
34
+ /** variant 별 기본 입력 — kind 가 없는(variant 경로) 컬럼과 editor 없는 kind 의 마지막 폴백 */
35
+ export function defaultEditorForVariant(variant) {
36
+ switch (variant) {
37
+ case "number": return builtinFilterEditors.number;
38
+ case "date":
39
+ case "datetime": return builtinFilterEditors.date;
40
+ case "boolean": return builtinFilterEditors.boolean;
41
+ case "select":
42
+ case "multiSelect": return enumEditor;
43
+ case "relation": return builtinFilterEditors.relation;
44
+ case "linear": return builtinFilterEditors.linear;
45
+ case "schedule": return builtinFilterEditors.schedule;
46
+ case "custom":
47
+ // custom 인데 render 가 없으면 그릴 방법이 없다. 텍스트로 떨어뜨리면
48
+ // 값 모양이 안 맞아 조용히 0건이 되므로 아무것도 그리지 않는다.
49
+ return undefined;
50
+ case "text":
51
+ default:
52
+ return builtinFilterEditors.text;
53
+ }
54
+ }
55
+ /**
56
+ * 내장 kind 를 editor 가 붙은 채로 레지스트리에 다시 등록한다.
57
+ * `FilterInput` 은 이것 없이도 `builtinFilterEditors` 를 보므로 필수는 아니다 —
58
+ * `filterKinds.get("text").editor` 처럼 레지스트리에서 표시 부분까지 읽고 싶을 때 부른다.
59
+ */
60
+ export function registerBuiltinEditors(store = filterKinds) {
61
+ for (const kind of BUILTIN_FILTER_KINDS) {
62
+ const editor = builtinFilterEditors[kind.id];
63
+ if (editor)
64
+ store.register(withEditor(kind, { editor }));
65
+ }
66
+ }
67
+ /**
68
+ * 입력 렌더러 해석 — 우선순위는 **컬럼 > Provider > kind > 내장**:
69
+ * 1. `column.filter.render` (`field.render`)
70
+ * 2. `Provider.filterKinds[kind.id].editor`
71
+ * 3. `Provider.filterInputs[variant]`
72
+ * 4. `kind.editor` (`withEditor` 로 붙인 것)
73
+ * 5. 내장 kind 의 기본 입력 (`builtinFilterEditors[kind.id]`)
74
+ * 6. variant 별 기본 입력 (`custom` 이면 없음)
75
+ */
76
+ export function resolveFilterEditor(field, config = {}) {
77
+ if (field.render)
78
+ return field.render;
79
+ const kind = field.kind;
80
+ if (kind) {
81
+ const provided = config.filterKinds?.[kind.id]?.editor;
82
+ if (provided)
83
+ return provided;
84
+ }
85
+ const byVariant = config.filterInputs?.[field.variant];
86
+ if (byVariant)
87
+ return byVariant;
88
+ if (kind) {
89
+ if (kind.editor)
90
+ return kind.editor;
91
+ const builtin = builtinFilterEditors[kind.id];
92
+ if (builtin)
93
+ return builtin;
94
+ }
95
+ return defaultEditorForVariant(field.variant);
96
+ }
97
+ /** 칩 렌더러 해석 — 우선순위는 editor 와 같다 (Provider > kind) */
98
+ export function resolveFilterChip(field, config = {}) {
99
+ const kind = field.kind;
100
+ if (!kind)
101
+ return undefined;
102
+ return (config.filterKinds?.[kind.id]?.chip ?? kind.chip);
103
+ }
@@ -0,0 +1,55 @@
1
+ import { type ComponentType, type ReactNode } from "react";
2
+ import { type SvgIconProps, type SxProps, type Theme } from "@mui/material";
3
+ import type { AggFnKey, AggregationDef, AggregationId, Registry } from "../../../core/src/index";
4
+ /**
5
+ * 현재 Provider 의 집계 레지스트리 — core 전역 `aggregations`(내장 세트) 위에 `registries.aggregations`(Provider `aggregations`) 를 얹은 것.
6
+ * `useGrouping({ aggregations })`·`AggregationChips`·그룹 행 칩이 같은 것을 보게 이 결과를 넘긴다.
7
+ */
8
+ export declare function useAggregations(): Registry<AggregationDef>;
9
+ /** MUI `Chip` 의 색 키 */
10
+ export type AggChipColor = "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning";
11
+ /**
12
+ * 집계 함수별 칩 색 — 집계 성격으로 구분한다.
13
+ * 합계는 주 집계(primary), 평균·중간값은 대표값(info), 최소/최대는 하한/상한(success/warning),
14
+ * 표준편차·점유율은 산포/비율(secondary), 개수·고유·최빈값은 default.
15
+ */
16
+ export declare const AGG_CHIP_COLORS: Record<AggFnKey, AggChipColor>;
17
+ type AggIconComponent = ComponentType<SvgIconProps>;
18
+ /** 집계 함수의 아이콘 컴포넌트 (`@mui/icons-material`). 모르는 키(사용자 집계)는 합계 아이콘 */
19
+ export declare function getAggIcon(fn: AggregationId): AggIconComponent;
20
+ /** 칩 색 — 사용자 집계는 default */
21
+ export declare function aggChipColor(fn: AggregationId): AggChipColor;
22
+ /**
23
+ * 칩에 찍을 값 문자열.
24
+ * 정의에 `format` 이 있으면 그것(내장 `ratio` 는 퍼센트). 없으면 정수는 천 단위 구분, 소수는 최대 2자리, 그 외는 문자열 그대로.
25
+ */
26
+ export declare function formatAggValue(value: unknown, fn?: AggregationId, registry?: Registry<AggregationDef>): string;
27
+ export interface AggregateChipProps {
28
+ /** 현재 집계 함수 (내장 키 또는 사용자 집계 id) */
29
+ aggFn: AggregationId;
30
+ /** 표시할 집계 값. 숫자는 `formatAggValue` 로 찍고, ReactNode 는 그대로 넣는다 */
31
+ value: ReactNode;
32
+ /**
33
+ * 함수 변경 콜백. 없으면 읽기 전용 — 칩을 눌러도 아무 일도 없다.
34
+ * 있으면 칩을 눌렀을 때 `options` 메뉴(아이콘 + 라벨)가 뜬다.
35
+ * (메서드 시그니처 — `(fn: AggFnKey) => void` 처럼 좁은 콜백도 받는다)
36
+ */
37
+ onChangeAggFn?(fn: AggregationId): void;
38
+ /** 메뉴에 올릴 함수. 미지정 시 레지스트리 전체 */
39
+ options?: AggregationId[];
40
+ /** 집계 레지스트리 — 미지정 시 Provider(`useAggregations()`) */
41
+ registry?: Registry<AggregationDef>;
42
+ /** 접근성 라벨 접두 — "금액 합계: 1,234" 처럼 컬럼 이름을 앞에 붙인다 */
43
+ columnLabel?: string;
44
+ size?: "small" | "medium";
45
+ sx?: SxProps<Theme>;
46
+ }
47
+ /**
48
+ * 집계 칩 — `"합계: 1,234"` 를 outlined 칩으로 그린다. 함수별 색(`AGG_CHIP_COLORS`),
49
+ * `tabular-nums`. `onChangeAggFn` 이 있으면 칩을 눌러 다른 함수로 바꿀 수 있다.
50
+ *
51
+ * 그룹 행 안에 놓이므로 클릭·키보드 이벤트가 행(펼치기 토글)으로 번지지 않게 막는다 —
52
+ * 메뉴는 포털이지만 React 이벤트는 트리를 따라 올라오기 때문에 메뉴에서도 막는다.
53
+ */
54
+ export declare function AggregateChip({ aggFn, value, onChangeAggFn, options, columnLabel, size, sx, registry: givenRegistry, }: AggregateChipProps): import("react").JSX.Element;
55
+ export {};
@@ -0,0 +1,128 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Chip, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip, } from "@mui/material";
4
+ import FunctionsIcon from "@mui/icons-material/Functions";
5
+ import ShowChartIcon from "@mui/icons-material/ShowChart";
6
+ import AlignVerticalCenterIcon from "@mui/icons-material/AlignVerticalCenter";
7
+ import VerticalAlignBottomIcon from "@mui/icons-material/VerticalAlignBottom";
8
+ import VerticalAlignTopIcon from "@mui/icons-material/VerticalAlignTop";
9
+ import ScatterPlotIcon from "@mui/icons-material/ScatterPlot";
10
+ import FingerprintIcon from "@mui/icons-material/Fingerprint";
11
+ import TagIcon from "@mui/icons-material/Tag";
12
+ import PercentIcon from "@mui/icons-material/Percent";
13
+ import BarChartIcon from "@mui/icons-material/BarChart";
14
+ import { aggregations as defaultAggregations, aggregationLabel } from "../../../core/src/index";
15
+ import { useKokoTableConfig } from "../provider/KokoTableProvider";
16
+ import { layerRegistry, registriesOf } from "../content/cellRenderers";
17
+ /**
18
+ * 현재 Provider 의 집계 레지스트리 — core 전역 `aggregations`(내장 세트) 위에 `registries.aggregations`(Provider `aggregations`) 를 얹은 것.
19
+ * `useGrouping({ aggregations })`·`AggregationChips`·그룹 행 칩이 같은 것을 보게 이 결과를 넘긴다.
20
+ */
21
+ export function useAggregations() {
22
+ return layerRegistry(defaultAggregations, registriesOf(useKokoTableConfig()).aggregations);
23
+ }
24
+ /**
25
+ * 집계 함수별 칩 색 — 집계 성격으로 구분한다.
26
+ * 합계는 주 집계(primary), 평균·중간값은 대표값(info), 최소/최대는 하한/상한(success/warning),
27
+ * 표준편차·점유율은 산포/비율(secondary), 개수·고유·최빈값은 default.
28
+ */
29
+ export const AGG_CHIP_COLORS = {
30
+ sum: "primary",
31
+ avg: "info",
32
+ median: "info",
33
+ min: "success",
34
+ max: "warning",
35
+ stddev: "secondary",
36
+ count_distinct: "default",
37
+ count_true: "default",
38
+ ratio: "secondary",
39
+ mode: "default",
40
+ };
41
+ const AGG_ICONS = {
42
+ sum: FunctionsIcon,
43
+ avg: ShowChartIcon,
44
+ median: AlignVerticalCenterIcon,
45
+ min: VerticalAlignBottomIcon,
46
+ max: VerticalAlignTopIcon,
47
+ stddev: ScatterPlotIcon,
48
+ count_distinct: FingerprintIcon,
49
+ count_true: TagIcon,
50
+ ratio: PercentIcon,
51
+ mode: BarChartIcon,
52
+ };
53
+ /** 집계 함수의 아이콘 컴포넌트 (`@mui/icons-material`). 모르는 키(사용자 집계)는 합계 아이콘 */
54
+ export function getAggIcon(fn) {
55
+ return AGG_ICONS[fn] ?? FunctionsIcon;
56
+ }
57
+ /** 칩 색 — 사용자 집계는 default */
58
+ export function aggChipColor(fn) {
59
+ return AGG_CHIP_COLORS[fn] ?? "default";
60
+ }
61
+ /**
62
+ * 칩에 찍을 값 문자열.
63
+ * 정의에 `format` 이 있으면 그것(내장 `ratio` 는 퍼센트). 없으면 정수는 천 단위 구분, 소수는 최대 2자리, 그 외는 문자열 그대로.
64
+ */
65
+ export function formatAggValue(value, fn, registry = defaultAggregations) {
66
+ if (value === null || value === undefined)
67
+ return "";
68
+ const format = fn ? registry.get(fn)?.format : undefined;
69
+ if (format)
70
+ return format(value);
71
+ if (typeof value === "number") {
72
+ if (!Number.isFinite(value))
73
+ return String(value);
74
+ if (fn === "ratio")
75
+ return `${(value * 100).toLocaleString("ko-KR", { maximumFractionDigits: 1 })}%`;
76
+ return Number.isInteger(value)
77
+ ? value.toLocaleString("ko-KR")
78
+ : value.toLocaleString("ko-KR", { maximumFractionDigits: 2 });
79
+ }
80
+ if (typeof value === "string")
81
+ return value;
82
+ return String(value);
83
+ }
84
+ /**
85
+ * 집계 칩 — `"합계: 1,234"` 를 outlined 칩으로 그린다. 함수별 색(`AGG_CHIP_COLORS`),
86
+ * `tabular-nums`. `onChangeAggFn` 이 있으면 칩을 눌러 다른 함수로 바꿀 수 있다.
87
+ *
88
+ * 그룹 행 안에 놓이므로 클릭·키보드 이벤트가 행(펼치기 토글)으로 번지지 않게 막는다 —
89
+ * 메뉴는 포털이지만 React 이벤트는 트리를 따라 올라오기 때문에 메뉴에서도 막는다.
90
+ */
91
+ export function AggregateChip({ aggFn, value, onChangeAggFn, options, columnLabel, size = "small", sx, registry: givenRegistry, }) {
92
+ const { labels } = useKokoTableConfig();
93
+ const provided = useAggregations();
94
+ const registry = givenRegistry ?? provided;
95
+ const aggLabels = labels.aggregations;
96
+ const [anchorEl, setAnchorEl] = useState(null);
97
+ const interactive = !!onChangeAggFn;
98
+ // 라벨: Provider `labels.aggregations`(다국어) → 레지스트리 정의 `label` → id
99
+ const labelOf = (fn) => aggLabels[fn] ?? aggregationLabel(fn, registry);
100
+ const fnLabel = labelOf(aggFn);
101
+ const displayValue = typeof value === "string" || typeof value === "number" || value === null || value === undefined
102
+ ? formatAggValue(value, aggFn, registry)
103
+ : value;
104
+ const label = displayValue === "" ? fnLabel : (_jsxs(_Fragment, { children: [fnLabel, ": ", typeof displayValue === "string" ? displayValue : _jsx(Box, { component: "span", children: displayValue })] }));
105
+ const ariaLabel = `${columnLabel ? `${columnLabel} ` : ""}${fnLabel}${typeof displayValue === "string" && displayValue !== "" ? `: ${displayValue}` : ""}`;
106
+ const stop = (event) => event.stopPropagation();
107
+ const open = (event) => {
108
+ if (!onChangeAggFn)
109
+ return;
110
+ event.stopPropagation();
111
+ setAnchorEl(event.currentTarget);
112
+ };
113
+ const close = () => setAnchorEl(null);
114
+ const select = (fn) => {
115
+ onChangeAggFn?.(fn);
116
+ close();
117
+ };
118
+ const menuOptions = options ?? registry.list().map((def) => def.id);
119
+ const chip = (_jsx(Chip, { size: size, variant: "outlined", color: aggChipColor(aggFn), label: label, "aria-label": ariaLabel, "aria-haspopup": interactive ? "menu" : undefined, "aria-expanded": interactive ? !!anchorEl : undefined, "data-agg-fn": aggFn, onClick: interactive ? open : undefined, onKeyDown: interactive ? stop : undefined, sx: [
120
+ { fontVariantNumeric: "tabular-nums", maxWidth: "100%" },
121
+ interactive && { cursor: "pointer" },
122
+ ...(Array.isArray(sx) ? sx : sx ? [sx] : []),
123
+ ] }));
124
+ return (_jsxs(Box, { component: "span", sx: { display: "inline-flex", alignItems: "center", maxWidth: "100%" }, children: [interactive ? _jsx(Tooltip, { title: `${fnLabel} 변경`, enterDelay: 600, children: chip }) : chip, interactive && (_jsx(Menu, { anchorEl: anchorEl, open: !!anchorEl, onClose: close, onClick: stop, onKeyDown: stop, slotProps: { paper: { sx: { minWidth: 140 } } }, children: menuOptions.map((fn) => {
125
+ const Icon = getAggIcon(fn);
126
+ return (_jsxs(MenuItem, { dense: true, selected: fn === aggFn, onClick: () => select(fn), children: [_jsx(ListItemIcon, { children: _jsx(Icon, { fontSize: "small" }) }), _jsx(ListItemText, { children: labelOf(fn) })] }, fn));
127
+ }) }))] }));
128
+ }