@carto/ps-react-ui 4.3.3 → 4.3.5

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 (301) hide show
  1. package/dist/components.js +3 -3
  2. package/dist/components.js.map +1 -1
  3. package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
  4. package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
  5. package/dist/types/components/common-types.d.ts +41 -0
  6. package/dist/types/components/types.d.ts +1 -1
  7. package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
  8. package/dist/types/widgets/echart/types.d.ts +4 -0
  9. package/dist/widgets/actions.js +1 -1
  10. package/dist/widgets/bar.js +1 -1
  11. package/dist/widgets/category.js +1 -1
  12. package/dist/widgets/echart.js +96 -85
  13. package/dist/widgets/echart.js.map +1 -1
  14. package/dist/widgets/formula.js +1 -1
  15. package/dist/widgets/histogram.js +1 -1
  16. package/dist/widgets/markdown.js +1 -1
  17. package/dist/widgets/pie.js +1 -1
  18. package/dist/widgets/scatterplot.js +1 -1
  19. package/dist/widgets/spread.js +1 -1
  20. package/dist/widgets/table.js +1 -1
  21. package/dist/widgets/timeseries.js +1 -1
  22. package/dist/widgets/toolbar-actions.js.map +1 -1
  23. package/dist/widgets/wrapper.js +1 -1
  24. package/package.json +8 -3
  25. package/src/components/basemaps/basemaps.test.tsx +196 -0
  26. package/src/components/basemaps/basemaps.tsx +128 -0
  27. package/src/components/basemaps/const.ts +13 -0
  28. package/src/components/basemaps/group-wrapper.test.tsx +38 -0
  29. package/src/components/basemaps/group-wrapper.tsx +28 -0
  30. package/src/components/basemaps/group.test.tsx +52 -0
  31. package/src/components/basemaps/group.tsx +42 -0
  32. package/src/components/basemaps/header.test.tsx +54 -0
  33. package/src/components/basemaps/header.tsx +36 -0
  34. package/src/components/basemaps/styles.ts +76 -0
  35. package/src/components/basemaps/types.ts +30 -0
  36. package/src/components/common-types.ts +1 -0
  37. package/src/components/geolocation-controls/const.ts +6 -0
  38. package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
  39. package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
  40. package/src/components/geolocation-controls/types.ts +17 -0
  41. package/src/components/index.ts +64 -0
  42. package/src/components/lasso-tool/chip.tsx +37 -0
  43. package/src/components/lasso-tool/const.tsx +70 -0
  44. package/src/components/lasso-tool/icons.tsx +91 -0
  45. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
  46. package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
  47. package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
  48. package/src/components/lasso-tool/lasso-tool.tsx +479 -0
  49. package/src/components/lasso-tool/styles.ts +143 -0
  50. package/src/components/lasso-tool/types.ts +114 -0
  51. package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
  52. package/src/components/list-data/list-data-skeleton.tsx +40 -0
  53. package/src/components/list-data/list-data.test.tsx +94 -0
  54. package/src/components/list-data/list-data.tsx +106 -0
  55. package/src/components/list-data/styles.ts +37 -0
  56. package/src/components/list-data/types.ts +25 -0
  57. package/src/components/measurement-tools/const.tsx +108 -0
  58. package/src/components/measurement-tools/icons.tsx +54 -0
  59. package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
  60. package/src/components/measurement-tools/measurement-tools.tsx +443 -0
  61. package/src/components/measurement-tools/styles.ts +91 -0
  62. package/src/components/measurement-tools/types.ts +77 -0
  63. package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
  64. package/src/components/no-data-alert/no-data-alert.tsx +59 -0
  65. package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
  66. package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
  67. package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
  68. package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
  69. package/src/components/tooltip/tooltip.test.tsx +86 -0
  70. package/src/components/tooltip/tooltip.tsx +30 -0
  71. package/src/components/types.ts +1 -0
  72. package/src/components/zoom-controls/styles.ts +27 -0
  73. package/src/components/zoom-controls/types.ts +19 -0
  74. package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
  75. package/src/components/zoom-controls/zoom-controls.tsx +114 -0
  76. package/src/hooks/index.ts +2 -0
  77. package/src/hooks/use-debounce.ts +55 -0
  78. package/src/hooks/use-skeleton.test.tsx +32 -0
  79. package/src/hooks/use-skeleton.ts +19 -0
  80. package/src/hooks/use-widget-ref.ts +33 -0
  81. package/src/widgets/README.md +277 -0
  82. package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
  83. package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
  84. package/src/widgets/_shared/chart-config/index.ts +21 -0
  85. package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
  86. package/src/widgets/_shared/skeleton/index.ts +5 -0
  87. package/src/widgets/_shared/skeleton/styles.ts +20 -0
  88. package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
  89. package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
  90. package/src/widgets/actions/change-column/change-column.tsx +141 -0
  91. package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
  92. package/src/widgets/actions/change-column/types.ts +20 -0
  93. package/src/widgets/actions/download/download.test.tsx +322 -0
  94. package/src/widgets/actions/download/download.tsx +118 -0
  95. package/src/widgets/actions/download/exports.test.tsx +275 -0
  96. package/src/widgets/actions/download/exports.tsx +103 -0
  97. package/src/widgets/actions/download/types.ts +21 -0
  98. package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
  99. package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
  100. package/src/widgets/actions/fullscreen/styles.ts +17 -0
  101. package/src/widgets/actions/fullscreen/types.ts +27 -0
  102. package/src/widgets/actions/index.ts +51 -0
  103. package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
  104. package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
  105. package/src/widgets/actions/lock-selection/types.ts +41 -0
  106. package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
  107. package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
  108. package/src/widgets/actions/relative-data/style.ts +9 -0
  109. package/src/widgets/actions/relative-data/types.ts +31 -0
  110. package/src/widgets/actions/relative-data/utils.test.ts +223 -0
  111. package/src/widgets/actions/relative-data/utils.ts +58 -0
  112. package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
  113. package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
  114. package/src/widgets/actions/searcher/searcher.tsx +205 -0
  115. package/src/widgets/actions/searcher/types.ts +72 -0
  116. package/src/widgets/actions/shared/styles.ts +12 -0
  117. package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
  118. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
  119. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
  120. package/src/widgets/actions/stack-toggle/types.ts +29 -0
  121. package/src/widgets/actions/zoom-toggle/index.ts +2 -0
  122. package/src/widgets/actions/zoom-toggle/style.ts +14 -0
  123. package/src/widgets/actions/zoom-toggle/types.ts +44 -0
  124. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
  125. package/src/widgets/bar/config.ts +122 -0
  126. package/src/widgets/bar/index.ts +9 -0
  127. package/src/widgets/bar/skeleton.tsx +60 -0
  128. package/src/widgets/bar/style.ts +33 -0
  129. package/src/widgets/bar/types.ts +16 -0
  130. package/src/widgets/category/category-ui.test.tsx +399 -0
  131. package/src/widgets/category/category-ui.tsx +156 -0
  132. package/src/widgets/category/components/category-bar.tsx +28 -0
  133. package/src/widgets/category/components/category-legend.tsx +30 -0
  134. package/src/widgets/category/components/category-row-multi.tsx +50 -0
  135. package/src/widgets/category/components/category-row-other.tsx +23 -0
  136. package/src/widgets/category/components/category-row-single.tsx +47 -0
  137. package/src/widgets/category/components/index.ts +14 -0
  138. package/src/widgets/category/config.ts +85 -0
  139. package/src/widgets/category/index.ts +30 -0
  140. package/src/widgets/category/skeleton.tsx +24 -0
  141. package/src/widgets/category/style.ts +133 -0
  142. package/src/widgets/category/types.ts +40 -0
  143. package/src/widgets/echart/const.ts +1 -0
  144. package/src/widgets/echart/echart-ui.test.tsx +537 -0
  145. package/src/widgets/echart/echart-ui.tsx +92 -0
  146. package/src/widgets/echart/echart.test.tsx +562 -0
  147. package/src/widgets/echart/echart.tsx +68 -0
  148. package/src/widgets/echart/index.ts +16 -0
  149. package/src/widgets/echart/options.ts +53 -0
  150. package/src/widgets/echart/types.ts +45 -0
  151. package/src/widgets/echart/utils.ts +169 -0
  152. package/src/widgets/error/error.test.tsx +331 -0
  153. package/src/widgets/error/error.tsx +40 -0
  154. package/src/widgets/error/index.ts +2 -0
  155. package/src/widgets/error/types.ts +14 -0
  156. package/src/widgets/formula/components/item.test.tsx +249 -0
  157. package/src/widgets/formula/components/item.tsx +18 -0
  158. package/src/widgets/formula/components/prefix.test.tsx +341 -0
  159. package/src/widgets/formula/components/prefix.tsx +18 -0
  160. package/src/widgets/formula/components/row.test.tsx +364 -0
  161. package/src/widgets/formula/components/row.tsx +21 -0
  162. package/src/widgets/formula/components/series.tsx +34 -0
  163. package/src/widgets/formula/components/suffix.test.tsx +383 -0
  164. package/src/widgets/formula/components/suffix.tsx +28 -0
  165. package/src/widgets/formula/components/value.test.tsx +329 -0
  166. package/src/widgets/formula/components/value.tsx +29 -0
  167. package/src/widgets/formula/config.ts +27 -0
  168. package/src/widgets/formula/formula-ui.test.tsx +399 -0
  169. package/src/widgets/formula/formula-ui.tsx +27 -0
  170. package/src/widgets/formula/index.ts +24 -0
  171. package/src/widgets/formula/serializer.test.tsx +144 -0
  172. package/src/widgets/formula/serializer.ts +28 -0
  173. package/src/widgets/formula/skeleton.tsx +10 -0
  174. package/src/widgets/formula/style.ts +23 -0
  175. package/src/widgets/formula/types.ts +50 -0
  176. package/src/widgets/histogram/config.ts +143 -0
  177. package/src/widgets/histogram/index.ts +8 -0
  178. package/src/widgets/histogram/skeleton.tsx +52 -0
  179. package/src/widgets/histogram/style.ts +8 -0
  180. package/src/widgets/histogram/types.ts +17 -0
  181. package/src/widgets/index.ts +25 -0
  182. package/src/widgets/loader/index.ts +4 -0
  183. package/src/widgets/loader/loader.tsx +70 -0
  184. package/src/widgets/loader/types.ts +11 -0
  185. package/src/widgets/loader/utils.test.ts +112 -0
  186. package/src/widgets/loader/utils.ts +35 -0
  187. package/src/widgets/markdown/config.ts +18 -0
  188. package/src/widgets/markdown/index.ts +14 -0
  189. package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
  190. package/src/widgets/markdown/markdown-ui.tsx +52 -0
  191. package/src/widgets/markdown/markdown.tsx +20 -0
  192. package/src/widgets/markdown/skeleton.tsx +12 -0
  193. package/src/widgets/markdown/style.ts +28 -0
  194. package/src/widgets/markdown/types.ts +28 -0
  195. package/src/widgets/no-data/index.ts +2 -0
  196. package/src/widgets/no-data/no-data.test.tsx +447 -0
  197. package/src/widgets/no-data/no-data.tsx +116 -0
  198. package/src/widgets/no-data/style.ts +18 -0
  199. package/src/widgets/no-data/types.ts +72 -0
  200. package/src/widgets/note/index.ts +2 -0
  201. package/src/widgets/note/note.test.tsx +391 -0
  202. package/src/widgets/note/note.tsx +114 -0
  203. package/src/widgets/note/style.ts +29 -0
  204. package/src/widgets/note/types.ts +9 -0
  205. package/src/widgets/pie/config.ts +177 -0
  206. package/src/widgets/pie/index.ts +8 -0
  207. package/src/widgets/pie/skeleton.tsx +70 -0
  208. package/src/widgets/pie/style.ts +8 -0
  209. package/src/widgets/pie/types.ts +16 -0
  210. package/src/widgets/range/components/range-item.tsx +213 -0
  211. package/src/widgets/range/config.ts +10 -0
  212. package/src/widgets/range/index.ts +16 -0
  213. package/src/widgets/range/range-ui.test.tsx +203 -0
  214. package/src/widgets/range/range-ui.tsx +11 -0
  215. package/src/widgets/range/serializer.test.ts +70 -0
  216. package/src/widgets/range/serializer.ts +27 -0
  217. package/src/widgets/range/skeleton.tsx +14 -0
  218. package/src/widgets/range/style.ts +37 -0
  219. package/src/widgets/range/types.ts +39 -0
  220. package/src/widgets/scatterplot/config.ts +138 -0
  221. package/src/widgets/scatterplot/index.ts +8 -0
  222. package/src/widgets/scatterplot/skeleton.tsx +59 -0
  223. package/src/widgets/scatterplot/style.ts +21 -0
  224. package/src/widgets/scatterplot/types.ts +17 -0
  225. package/src/widgets/selection-summary/index.ts +6 -0
  226. package/src/widgets/selection-summary/selection-summary.tsx +46 -0
  227. package/src/widgets/selection-summary/style.ts +10 -0
  228. package/src/widgets/selection-summary/types.ts +14 -0
  229. package/src/widgets/skeleton-loader/index.ts +2 -0
  230. package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
  231. package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
  232. package/src/widgets/skeleton-loader/types.ts +8 -0
  233. package/src/widgets/spread/components/max-value.tsx +29 -0
  234. package/src/widgets/spread/components/min-value.tsx +29 -0
  235. package/src/widgets/spread/components/separator.tsx +6 -0
  236. package/src/widgets/spread/config.ts +34 -0
  237. package/src/widgets/spread/index.ts +23 -0
  238. package/src/widgets/spread/skeleton.tsx +10 -0
  239. package/src/widgets/spread/spread-ui.test.tsx +368 -0
  240. package/src/widgets/spread/spread-ui.tsx +29 -0
  241. package/src/widgets/spread/style.ts +22 -0
  242. package/src/widgets/spread/types.ts +47 -0
  243. package/src/widgets/stores/index.ts +9 -0
  244. package/src/widgets/stores/types.ts +192 -0
  245. package/src/widgets/stores/widget-store.test.ts +601 -0
  246. package/src/widgets/stores/widget-store.ts +239 -0
  247. package/src/widgets/subheader/index.ts +3 -0
  248. package/src/widgets/subheader/style.ts +20 -0
  249. package/src/widgets/subheader/subheader.test.tsx +45 -0
  250. package/src/widgets/subheader/subheader.tsx +16 -0
  251. package/src/widgets/subheader/types.ts +11 -0
  252. package/src/widgets/table/components/cell-header.tsx +58 -0
  253. package/src/widgets/table/components/cell.tsx +80 -0
  254. package/src/widgets/table/components/index.ts +4 -0
  255. package/src/widgets/table/components/pagination-actions.tsx +67 -0
  256. package/src/widgets/table/components/pagination.tsx +41 -0
  257. package/src/widgets/table/components/row.tsx +60 -0
  258. package/src/widgets/table/config.ts +71 -0
  259. package/src/widgets/table/helpers.test.ts +244 -0
  260. package/src/widgets/table/helpers.ts +107 -0
  261. package/src/widgets/table/hooks/index.ts +7 -0
  262. package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
  263. package/src/widgets/table/hooks/use-pagination.ts +155 -0
  264. package/src/widgets/table/hooks/use-selection.test.ts +504 -0
  265. package/src/widgets/table/hooks/use-selection.ts +189 -0
  266. package/src/widgets/table/hooks/use-sort.test.ts +296 -0
  267. package/src/widgets/table/hooks/use-sort.ts +138 -0
  268. package/src/widgets/table/index.ts +53 -0
  269. package/src/widgets/table/serializer.ts +54 -0
  270. package/src/widgets/table/skeleton.tsx +48 -0
  271. package/src/widgets/table/style.ts +34 -0
  272. package/src/widgets/table/table-ui.tsx +64 -0
  273. package/src/widgets/table/types.ts +223 -0
  274. package/src/widgets/timeseries/config.ts +135 -0
  275. package/src/widgets/timeseries/index.ts +8 -0
  276. package/src/widgets/timeseries/skeleton.tsx +55 -0
  277. package/src/widgets/timeseries/style.ts +36 -0
  278. package/src/widgets/timeseries/types.ts +17 -0
  279. package/src/widgets/toolbar-actions/index.ts +6 -0
  280. package/src/widgets/toolbar-actions/styles.ts +38 -0
  281. package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
  282. package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
  283. package/src/widgets/toolbar-actions/types.ts +60 -0
  284. package/src/widgets/wrapper/components/actions.test.tsx +101 -0
  285. package/src/widgets/wrapper/components/actions.tsx +30 -0
  286. package/src/widgets/wrapper/components/options.test.tsx +323 -0
  287. package/src/widgets/wrapper/components/options.tsx +73 -0
  288. package/src/widgets/wrapper/components/title.test.tsx +126 -0
  289. package/src/widgets/wrapper/components/title.tsx +32 -0
  290. package/src/widgets/wrapper/index.ts +16 -0
  291. package/src/widgets/wrapper/styles.ts +98 -0
  292. package/src/widgets/wrapper/types.ts +55 -0
  293. package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
  294. package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
  295. package/src/widgets/wrapper/wrapper.test.tsx +365 -0
  296. package/src/widgets/wrapper/wrapper.tsx +50 -0
  297. package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
  298. package/dist/types/common/common.d.ts +0 -3
  299. package/dist/types/common/index.d.ts +0 -26
  300. package/dist/types/common/lasso-tools.d.ts +0 -36
  301. package/dist/types/common/measurement-tools.d.ts +0 -65
@@ -0,0 +1,71 @@
1
+ import { downloadToCSV, downloadToPNG } from '../actions'
2
+ import type { ConfigProps } from '../loader/types'
3
+ import type {
4
+ TableDownloadConfig,
5
+ TableWidgetConfig,
6
+ TableColumn,
7
+ Mode,
8
+ SortDirection,
9
+ } from './types'
10
+
11
+ export const DEFAULT_MODE: Mode = 'local'
12
+ export const DEFAULT_PAGE = 0
13
+ export const DEFAULT_ROWS_PER_PAGE = 10
14
+ export const DEFAULT_ROWS_PER_PAGE_OPTIONS = [5, 10, 25, 50]
15
+
16
+ export const DEFAULT_COLUMN_ID = null
17
+ export const DEFAULT_DIRECTION: SortDirection = 'asc'
18
+
19
+ /**
20
+ * Default table download configuration
21
+ * Supports PNG export (screenshot) and CSV export (data)
22
+ */
23
+ export function tableDownloadConfig({
24
+ refUI,
25
+ }: ConfigProps): TableDownloadConfig {
26
+ return [
27
+ {
28
+ ...downloadToPNG,
29
+ modifier: () => downloadToPNG.modifier(refUI),
30
+ },
31
+ {
32
+ ...downloadToCSV,
33
+ modifier: async (data) => {
34
+ if (!data?.length) {
35
+ return downloadToCSV.modifier([])
36
+ }
37
+
38
+ // Convert table data to CSV format
39
+ // First row contains headers (column keys)
40
+ const firstRow = data[0]
41
+ if (!firstRow) {
42
+ return downloadToCSV.modifier([])
43
+ }
44
+
45
+ const headers = Object.keys(firstRow).filter((key) => key !== 'id')
46
+ const rows = data.map((row) =>
47
+ headers.map((header) => {
48
+ const value = row[header]
49
+ if (value === null || value === undefined) return ''
50
+ if (typeof value === 'object') return JSON.stringify(value)
51
+ return value
52
+ }),
53
+ )
54
+
55
+ return downloadToCSV.modifier([headers, ...rows])
56
+ },
57
+ },
58
+ ]
59
+ }
60
+
61
+ /**
62
+ * Default table widget configuration
63
+ */
64
+ export function tableConfig(columns: TableColumn[] = []): TableWidgetConfig {
65
+ return {
66
+ columns,
67
+ selectable: false,
68
+ selected: [],
69
+ mode: DEFAULT_MODE,
70
+ }
71
+ }
@@ -0,0 +1,244 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getCellValue, compareValues, sortData, paginateData } from './helpers'
3
+
4
+ describe('helpers', () => {
5
+ describe('getCellValue', () => {
6
+ it('should return empty string for null', () => {
7
+ expect(getCellValue(null)).toBe('')
8
+ })
9
+
10
+ it('should return empty string for undefined', () => {
11
+ expect(getCellValue(undefined)).toBe('')
12
+ })
13
+
14
+ it('should return string as-is', () => {
15
+ expect(getCellValue('hello')).toBe('hello')
16
+ })
17
+
18
+ it('should return number as-is', () => {
19
+ expect(getCellValue(42)).toBe(42)
20
+ })
21
+
22
+ it('should return boolean as-is', () => {
23
+ expect(getCellValue(true)).toBe(true)
24
+ expect(getCellValue(false)).toBe(false)
25
+ })
26
+
27
+ it('should format array of strings with quotes', () => {
28
+ expect(getCellValue(['a', 'b', 'c'])).toBe('["a", "b", "c"]')
29
+ })
30
+
31
+ it('should format array of numbers without quotes', () => {
32
+ expect(getCellValue([1, 2, 3])).toBe('[1, 2, 3]')
33
+ })
34
+
35
+ it('should format mixed array', () => {
36
+ expect(getCellValue(['hello', 42, true])).toBe('["hello", 42, true]')
37
+ })
38
+
39
+ it('should format empty array', () => {
40
+ expect(getCellValue([])).toBe('[]')
41
+ })
42
+
43
+ it('should stringify object', () => {
44
+ expect(getCellValue({ key: 'value' })).toBe('{"key":"value"}')
45
+ })
46
+
47
+ it('should stringify nested object', () => {
48
+ expect(getCellValue({ a: { b: 'c' } })).toBe('{"a":{"b":"c"}}')
49
+ })
50
+ })
51
+
52
+ describe('compareValues', () => {
53
+ describe('null/undefined handling', () => {
54
+ it('should sort null to end (return 1)', () => {
55
+ expect(compareValues(null, 'a', 'asc')).toBe(1)
56
+ })
57
+
58
+ it('should sort undefined to end (return 1)', () => {
59
+ expect(compareValues(undefined, 'a', 'asc')).toBe(1)
60
+ })
61
+
62
+ it('should sort non-null before null (return -1)', () => {
63
+ expect(compareValues('a', null, 'asc')).toBe(-1)
64
+ })
65
+
66
+ it('should sort non-undefined before undefined (return -1)', () => {
67
+ expect(compareValues('a', undefined, 'asc')).toBe(-1)
68
+ })
69
+ })
70
+
71
+ describe('string comparison', () => {
72
+ it('should compare strings ascending', () => {
73
+ expect(compareValues('apple', 'banana', 'asc')).toBeLessThan(0)
74
+ expect(compareValues('banana', 'apple', 'asc')).toBeGreaterThan(0)
75
+ expect(compareValues('apple', 'apple', 'asc')).toBe(0)
76
+ })
77
+
78
+ it('should compare strings descending', () => {
79
+ expect(compareValues('apple', 'banana', 'desc')).toBeGreaterThan(0)
80
+ expect(compareValues('banana', 'apple', 'desc')).toBeLessThan(0)
81
+ // When values are equal, result is 0 (could be -0 or +0, both equal 0)
82
+ expect(compareValues('apple', 'apple', 'desc') === 0).toBe(true)
83
+ })
84
+ })
85
+
86
+ describe('number comparison', () => {
87
+ it('should compare numbers ascending', () => {
88
+ expect(compareValues(1, 2, 'asc')).toBeLessThan(0)
89
+ expect(compareValues(2, 1, 'asc')).toBeGreaterThan(0)
90
+ expect(compareValues(1, 1, 'asc')).toBe(0)
91
+ })
92
+
93
+ it('should compare numbers descending', () => {
94
+ expect(compareValues(1, 2, 'desc')).toBeGreaterThan(0)
95
+ expect(compareValues(2, 1, 'desc')).toBeLessThan(0)
96
+ // When values are equal, result is 0 (could be -0 or +0, both equal 0)
97
+ expect(compareValues(1, 1, 'desc') === 0).toBe(true)
98
+ })
99
+
100
+ it('should handle negative numbers', () => {
101
+ expect(compareValues(-5, 5, 'asc')).toBeLessThan(0)
102
+ expect(compareValues(5, -5, 'asc')).toBeGreaterThan(0)
103
+ })
104
+
105
+ it('should handle decimal numbers', () => {
106
+ expect(compareValues(1.5, 2.5, 'asc')).toBeLessThan(0)
107
+ expect(compareValues(2.5, 1.5, 'asc')).toBeGreaterThan(0)
108
+ })
109
+ })
110
+
111
+ describe('boolean comparison', () => {
112
+ it('should compare booleans ascending (false before true)', () => {
113
+ expect(compareValues(false, true, 'asc')).toBeLessThan(0)
114
+ expect(compareValues(true, false, 'asc')).toBeGreaterThan(0)
115
+ expect(compareValues(true, true, 'asc')).toBe(0)
116
+ expect(compareValues(false, false, 'asc')).toBe(0)
117
+ })
118
+
119
+ it('should compare booleans descending (true before false)', () => {
120
+ expect(compareValues(false, true, 'desc')).toBeGreaterThan(0)
121
+ expect(compareValues(true, false, 'desc')).toBeLessThan(0)
122
+ })
123
+ })
124
+
125
+ describe('object comparison', () => {
126
+ it('should compare objects as JSON strings', () => {
127
+ const objA = { a: 1 }
128
+ const objB = { b: 2 }
129
+ // Compares '{"a":1}' vs '{"b":2}'
130
+ expect(compareValues(objA, objB, 'asc')).toBeLessThan(0)
131
+ })
132
+
133
+ it('should handle array comparison', () => {
134
+ const arrA = [1, 2]
135
+ const arrB = [3, 4]
136
+ // Compares '[1,2]' vs '[3,4]'
137
+ expect(compareValues(arrA, arrB, 'asc')).toBeLessThan(0)
138
+ })
139
+ })
140
+ })
141
+
142
+ describe('sortData', () => {
143
+ const testData = [
144
+ { id: 1, name: 'Charlie', age: 30 },
145
+ { id: 2, name: 'Alice', age: 25 },
146
+ { id: 3, name: 'Bob', age: 35 },
147
+ ]
148
+
149
+ it('should sort by string column ascending', () => {
150
+ const sorted = sortData(testData, 'name', 'asc')
151
+ expect(sorted.map((r) => r.name)).toEqual(['Alice', 'Bob', 'Charlie'])
152
+ })
153
+
154
+ it('should sort by string column descending', () => {
155
+ const sorted = sortData(testData, 'name', 'desc')
156
+ expect(sorted.map((r) => r.name)).toEqual(['Charlie', 'Bob', 'Alice'])
157
+ })
158
+
159
+ it('should sort by number column ascending', () => {
160
+ const sorted = sortData(testData, 'age', 'asc')
161
+ expect(sorted.map((r) => r.age)).toEqual([25, 30, 35])
162
+ })
163
+
164
+ it('should sort by number column descending', () => {
165
+ const sorted = sortData(testData, 'age', 'desc')
166
+ expect(sorted.map((r) => r.age)).toEqual([35, 30, 25])
167
+ })
168
+
169
+ it('should not mutate original array', () => {
170
+ const original = [...testData]
171
+ sortData(testData, 'name', 'asc')
172
+ expect(testData).toEqual(original)
173
+ })
174
+
175
+ it('should handle empty array', () => {
176
+ expect(sortData([], 'name', 'asc')).toEqual([])
177
+ })
178
+
179
+ it('should handle single item array', () => {
180
+ const single = [{ id: 1, name: 'Alice' }]
181
+ expect(sortData(single, 'name', 'asc')).toEqual(single)
182
+ })
183
+
184
+ it('should handle null values in data', () => {
185
+ const dataWithNull = [
186
+ { id: 1, name: 'Bob' },
187
+ { id: 2, name: null },
188
+ { id: 3, name: 'Alice' },
189
+ ]
190
+ const sorted = sortData(dataWithNull, 'name', 'asc')
191
+ // Null should be sorted to end
192
+ expect(sorted.map((r) => r.name)).toEqual(['Alice', 'Bob', null])
193
+ })
194
+ })
195
+
196
+ describe('paginateData', () => {
197
+ const testData = [
198
+ { id: 1 },
199
+ { id: 2 },
200
+ { id: 3 },
201
+ { id: 4 },
202
+ { id: 5 },
203
+ { id: 6 },
204
+ { id: 7 },
205
+ { id: 8 },
206
+ { id: 9 },
207
+ { id: 10 },
208
+ ]
209
+
210
+ it('should return first page', () => {
211
+ const result = paginateData(testData, 0, 3)
212
+ expect(result.map((r) => r.id)).toEqual([1, 2, 3])
213
+ })
214
+
215
+ it('should return second page', () => {
216
+ const result = paginateData(testData, 1, 3)
217
+ expect(result.map((r) => r.id)).toEqual([4, 5, 6])
218
+ })
219
+
220
+ it('should return partial last page', () => {
221
+ const result = paginateData(testData, 3, 3)
222
+ expect(result.map((r) => r.id)).toEqual([10])
223
+ })
224
+
225
+ it('should return empty array for page beyond data', () => {
226
+ const result = paginateData(testData, 10, 3)
227
+ expect(result).toEqual([])
228
+ })
229
+
230
+ it('should handle page size larger than data', () => {
231
+ const result = paginateData(testData, 0, 100)
232
+ expect(result.map((r) => r.id)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
233
+ })
234
+
235
+ it('should handle empty array', () => {
236
+ expect(paginateData([], 0, 10)).toEqual([])
237
+ })
238
+
239
+ it('should handle single item', () => {
240
+ const result = paginateData([{ id: 1 }], 0, 10)
241
+ expect(result.map((r) => r.id)).toEqual([1])
242
+ })
243
+ })
244
+ })
@@ -0,0 +1,107 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ /**
4
+ * Get formatted cell value for display
5
+ * Handles primitives, arrays, objects, and custom formatters
6
+ *
7
+ * @param cellValue - Raw cell value
8
+ * @param formatter - Optional formatter function
9
+ * @returns Formatted value for display
10
+ */
11
+ export function getCellValue<T>(cellValue: T): ReactNode {
12
+ // Handle null/undefined
13
+ if (cellValue === null || cellValue === undefined) {
14
+ return ''
15
+ }
16
+
17
+ // Handle arrays - format as [item1, item2, ...]
18
+ if (Array.isArray(cellValue)) {
19
+ const formatted = cellValue
20
+ .map((item) => (typeof item === 'string' ? `"${item}"` : String(item)))
21
+ .join(', ')
22
+ return `[${formatted}]`
23
+ }
24
+
25
+ // Handle objects - stringify to JSON
26
+ if (typeof cellValue === 'object') {
27
+ return JSON.stringify(cellValue)
28
+ }
29
+
30
+ // Return primitives as-is (string, number, boolean)
31
+ return cellValue as ReactNode
32
+ }
33
+
34
+ /**
35
+ * Compare two values for sorting
36
+ * Handles strings, numbers, and other types
37
+ *
38
+ * @param a - First value
39
+ * @param b - Second value
40
+ * @param direction - Sort direction
41
+ * @returns Comparison result (-1, 0, 1)
42
+ */
43
+ export function compareValues(
44
+ a: unknown,
45
+ b: unknown,
46
+ direction: 'asc' | 'desc',
47
+ ): number {
48
+ // Handle null/undefined - always sort to end
49
+ if (a === null || a === undefined) return 1
50
+ if (b === null || b === undefined) return -1
51
+
52
+ let comparison = 0
53
+
54
+ if (typeof a === 'string' && typeof b === 'string') {
55
+ comparison = a.localeCompare(b)
56
+ } else if (typeof a === 'number' && typeof b === 'number') {
57
+ comparison = a - b
58
+ } else if (typeof a === 'boolean' && typeof b === 'boolean') {
59
+ comparison = a === b ? 0 : a ? 1 : -1
60
+ } else if (typeof a === 'object' || typeof b === 'object') {
61
+ // Compare objects/arrays as JSON strings
62
+ const strA = JSON.stringify(a)
63
+ const strB = JSON.stringify(b)
64
+ comparison = strA.localeCompare(strB)
65
+ } else {
66
+ // Fallback for other primitives (bigint, symbol, etc.)
67
+ comparison = 0
68
+ }
69
+
70
+ return direction === 'asc' ? comparison : -comparison
71
+ }
72
+
73
+ /**
74
+ * Sort data array by column
75
+ *
76
+ * @param data - Data array to sort
77
+ * @param columnId - Column ID to sort by
78
+ * @param direction - Sort direction
79
+ * @returns Sorted data array (new array, does not mutate original)
80
+ */
81
+ export function sortData<T extends Record<string, unknown>>(
82
+ data: T[],
83
+ columnId: string,
84
+ direction: 'asc' | 'desc',
85
+ ): T[] {
86
+ return [...data].sort((a, b) =>
87
+ compareValues(a[columnId], b[columnId], direction),
88
+ )
89
+ }
90
+
91
+ /**
92
+ * Paginate data array
93
+ *
94
+ * @param data - Data array to paginate
95
+ * @param page - Current page (0-indexed)
96
+ * @param rowsPerPage - Number of rows per page
97
+ * @returns Paginated data slice
98
+ */
99
+ export function paginateData<T>(
100
+ data: T[],
101
+ page: number,
102
+ rowsPerPage: number,
103
+ ): T[] {
104
+ const start = page * rowsPerPage
105
+ const end = start + rowsPerPage
106
+ return data.slice(start, end)
107
+ }
@@ -0,0 +1,7 @@
1
+ export { usePagination } from './use-pagination'
2
+ export { useSort } from './use-sort'
3
+ export { useSelection } from './use-selection'
4
+
5
+ export type { UsePaginationResult } from './use-pagination'
6
+ export type { UseSortResult } from './use-sort'
7
+ export type { UseSelectionOptions, UseSelectionResult } from './use-selection'