vastgrid 0.1.0 → 0.2.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 (312) hide show
  1. package/README.md +175 -13
  2. package/dist/canvas/geometry.d.ts +92 -0
  3. package/dist/canvas/geometry.d.ts.map +1 -0
  4. package/dist/canvas/geometry.js +197 -0
  5. package/dist/canvas/geometry.js.map +1 -0
  6. package/dist/canvas/grid-canvas.d.ts +161 -0
  7. package/dist/canvas/grid-canvas.d.ts.map +1 -0
  8. package/dist/canvas/grid-canvas.js +1146 -0
  9. package/dist/canvas/grid-canvas.js.map +1 -0
  10. package/dist/canvas/hooks.d.ts +61 -0
  11. package/dist/canvas/hooks.d.ts.map +1 -0
  12. package/dist/canvas/hooks.js +2 -0
  13. package/dist/canvas/hooks.js.map +1 -0
  14. package/dist/canvas/keys.d.ts +26 -0
  15. package/dist/canvas/keys.d.ts.map +1 -0
  16. package/dist/canvas/keys.js +133 -0
  17. package/dist/canvas/keys.js.map +1 -0
  18. package/dist/canvas/layout.d.ts +26 -0
  19. package/dist/canvas/layout.d.ts.map +1 -0
  20. package/dist/canvas/layout.js +73 -0
  21. package/dist/canvas/layout.js.map +1 -0
  22. package/dist/canvas/scroll.d.ts +31 -0
  23. package/dist/canvas/scroll.d.ts.map +1 -0
  24. package/dist/canvas/scroll.js +111 -0
  25. package/dist/canvas/scroll.js.map +1 -0
  26. package/dist/canvas/text.d.ts +23 -0
  27. package/dist/canvas/text.d.ts.map +1 -0
  28. package/dist/canvas/text.js +142 -0
  29. package/dist/canvas/text.js.map +1 -0
  30. package/dist/{theme.d.ts → canvas/theme.d.ts} +8 -0
  31. package/dist/canvas/theme.d.ts.map +1 -0
  32. package/dist/canvas/theme.js +62 -0
  33. package/dist/canvas/theme.js.map +1 -0
  34. package/dist/core/cache.d.ts +48 -0
  35. package/dist/core/cache.d.ts.map +1 -0
  36. package/dist/core/cache.js +222 -0
  37. package/dist/core/cache.js.map +1 -0
  38. package/dist/core/checked.d.ts +20 -0
  39. package/dist/core/checked.d.ts.map +1 -0
  40. package/dist/core/checked.js +61 -0
  41. package/dist/core/checked.js.map +1 -0
  42. package/dist/core/client.d.ts +33 -0
  43. package/dist/core/client.d.ts.map +1 -0
  44. package/dist/core/client.js +103 -0
  45. package/dist/core/client.js.map +1 -0
  46. package/dist/core/conditions.d.ts +48 -0
  47. package/dist/core/conditions.d.ts.map +1 -0
  48. package/dist/core/conditions.js +105 -0
  49. package/dist/core/conditions.js.map +1 -0
  50. package/dist/core/evaluate.d.ts +20 -0
  51. package/dist/core/evaluate.d.ts.map +1 -0
  52. package/dist/core/evaluate.js +210 -0
  53. package/dist/core/evaluate.js.map +1 -0
  54. package/dist/core/filters.d.ts +51 -0
  55. package/dist/core/filters.d.ts.map +1 -0
  56. package/dist/core/filters.js +303 -0
  57. package/dist/core/filters.js.map +1 -0
  58. package/dist/core/format.d.ts +14 -0
  59. package/dist/core/format.d.ts.map +1 -0
  60. package/dist/core/format.js +303 -0
  61. package/dist/core/format.js.map +1 -0
  62. package/dist/core/measures.d.ts +26 -0
  63. package/dist/core/measures.d.ts.map +1 -0
  64. package/dist/core/measures.js +105 -0
  65. package/dist/core/measures.js.map +1 -0
  66. package/dist/core/request-ids.d.ts +20 -0
  67. package/dist/core/request-ids.d.ts.map +1 -0
  68. package/dist/core/request-ids.js +55 -0
  69. package/dist/core/request-ids.js.map +1 -0
  70. package/dist/core/rows.d.ts +79 -0
  71. package/dist/core/rows.d.ts.map +1 -0
  72. package/dist/core/rows.js +173 -0
  73. package/dist/core/rows.js.map +1 -0
  74. package/dist/core/store.d.ts +233 -0
  75. package/dist/core/store.d.ts.map +1 -0
  76. package/dist/core/store.js +1162 -0
  77. package/dist/core/store.js.map +1 -0
  78. package/dist/core/totals.d.ts +24 -0
  79. package/dist/core/totals.d.ts.map +1 -0
  80. package/dist/core/totals.js +49 -0
  81. package/dist/core/totals.js.map +1 -0
  82. package/dist/core/trace.d.ts +7 -0
  83. package/dist/core/trace.d.ts.map +1 -0
  84. package/dist/core/trace.js +26 -0
  85. package/dist/core/trace.js.map +1 -0
  86. package/dist/core/transports.d.ts +50 -0
  87. package/dist/core/transports.d.ts.map +1 -0
  88. package/dist/core/transports.js +160 -0
  89. package/dist/core/transports.js.map +1 -0
  90. package/dist/core/types.d.ts +150 -0
  91. package/dist/core/types.d.ts.map +1 -0
  92. package/dist/core/types.js +45 -0
  93. package/dist/core/types.js.map +1 -0
  94. package/dist/core.d.ts +12 -0
  95. package/dist/core.d.ts.map +1 -0
  96. package/dist/core.js +11 -0
  97. package/dist/core.js.map +1 -0
  98. package/dist/index.d.ts +15 -6
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +13 -7
  101. package/dist/index.js.map +1 -1
  102. package/dist/react/accessible-rows.d.ts +13 -0
  103. package/dist/react/accessible-rows.d.ts.map +1 -0
  104. package/dist/react/accessible-rows.js +320 -0
  105. package/dist/react/accessible-rows.js.map +1 -0
  106. package/dist/react/cell-editor.d.ts +22 -0
  107. package/dist/react/cell-editor.d.ts.map +1 -0
  108. package/dist/react/cell-editor.js +431 -0
  109. package/dist/react/cell-editor.js.map +1 -0
  110. package/dist/react/cell-menu.d.ts +18 -0
  111. package/dist/react/cell-menu.d.ts.map +1 -0
  112. package/dist/react/cell-menu.js +299 -0
  113. package/dist/react/cell-menu.js.map +1 -0
  114. package/dist/react/column-menu.d.ts +16 -0
  115. package/dist/react/column-menu.d.ts.map +1 -0
  116. package/dist/react/column-menu.js +1311 -0
  117. package/dist/react/column-menu.js.map +1 -0
  118. package/dist/react/columns-menu.d.ts +7 -0
  119. package/dist/react/columns-menu.d.ts.map +1 -0
  120. package/dist/react/columns-menu.js +249 -0
  121. package/dist/react/columns-menu.js.map +1 -0
  122. package/dist/react/conditions-menu.d.ts +10 -0
  123. package/dist/react/conditions-menu.d.ts.map +1 -0
  124. package/dist/react/conditions-menu.js +795 -0
  125. package/dist/react/conditions-menu.js.map +1 -0
  126. package/dist/react/context.d.ts +29 -0
  127. package/dist/react/context.d.ts.map +1 -0
  128. package/dist/react/context.js +18 -0
  129. package/dist/react/context.js.map +1 -0
  130. package/dist/react/data-grid.d.ts +79 -0
  131. package/dist/react/data-grid.d.ts.map +1 -0
  132. package/dist/react/data-grid.js +653 -0
  133. package/dist/react/data-grid.js.map +1 -0
  134. package/dist/react/detail-panel.d.ts +22 -0
  135. package/dist/react/detail-panel.d.ts.map +1 -0
  136. package/dist/react/detail-panel.js +242 -0
  137. package/dist/react/detail-panel.js.map +1 -0
  138. package/dist/react/export-menu.d.ts +11 -0
  139. package/dist/react/export-menu.d.ts.map +1 -0
  140. package/dist/react/export-menu.js +304 -0
  141. package/dist/react/export-menu.js.map +1 -0
  142. package/dist/react/find-bar.d.ts +11 -0
  143. package/dist/react/find-bar.d.ts.map +1 -0
  144. package/dist/react/find-bar.js +303 -0
  145. package/dist/react/find-bar.js.map +1 -0
  146. package/dist/react/format-form.d.ts +12 -0
  147. package/dist/react/format-form.d.ts.map +1 -0
  148. package/dist/react/format-form.js +863 -0
  149. package/dist/react/format-form.js.map +1 -0
  150. package/dist/react/formula-menu.d.ts +6 -0
  151. package/dist/react/formula-menu.d.ts.map +1 -0
  152. package/dist/react/formula-menu.js +343 -0
  153. package/dist/react/formula-menu.js.map +1 -0
  154. package/dist/react/grid-body.d.ts +33 -0
  155. package/dist/react/grid-body.d.ts.map +1 -0
  156. package/dist/react/grid-body.js +1389 -0
  157. package/dist/react/grid-body.js.map +1 -0
  158. package/dist/react/group-menu.d.ts +7 -0
  159. package/dist/react/group-menu.d.ts.map +1 -0
  160. package/dist/react/group-menu.js +508 -0
  161. package/dist/react/group-menu.js.map +1 -0
  162. package/dist/react/hooks.d.ts +33 -0
  163. package/dist/react/hooks.d.ts.map +1 -0
  164. package/dist/react/hooks.js +161 -0
  165. package/dist/react/hooks.js.map +1 -0
  166. package/dist/react/messages.d.ts +180 -0
  167. package/dist/react/messages.d.ts.map +1 -0
  168. package/dist/react/messages.js +414 -0
  169. package/dist/react/messages.js.map +1 -0
  170. package/dist/react/save.d.ts +9 -0
  171. package/dist/react/save.d.ts.map +1 -0
  172. package/dist/react/save.js +102 -0
  173. package/dist/react/save.js.map +1 -0
  174. package/dist/react/static-rows.d.ts +9 -0
  175. package/dist/react/static-rows.d.ts.map +1 -0
  176. package/dist/react/static-rows.js +176 -0
  177. package/dist/react/static-rows.js.map +1 -0
  178. package/dist/react/status-bar.d.ts +12 -0
  179. package/dist/react/status-bar.d.ts.map +1 -0
  180. package/dist/react/status-bar.js +413 -0
  181. package/dist/react/status-bar.js.map +1 -0
  182. package/dist/react/toolbar-popover.d.ts +14 -0
  183. package/dist/react/toolbar-popover.d.ts.map +1 -0
  184. package/dist/react/toolbar-popover.js +143 -0
  185. package/dist/react/toolbar-popover.js.map +1 -0
  186. package/dist/react/toolbar.d.ts +13 -0
  187. package/dist/react/toolbar.d.ts.map +1 -0
  188. package/dist/react/toolbar.js +344 -0
  189. package/dist/react/toolbar.js.map +1 -0
  190. package/dist/server.d.ts +22 -0
  191. package/dist/server.d.ts.map +1 -0
  192. package/dist/server.js +45 -0
  193. package/dist/server.js.map +1 -0
  194. package/dist/styles.css +496 -316
  195. package/package.json +43 -18
  196. package/src/canvas/geometry.ts +197 -0
  197. package/src/canvas/grid-canvas.ts +1222 -0
  198. package/src/canvas/hooks.ts +61 -0
  199. package/src/canvas/keys.ts +96 -0
  200. package/src/canvas/layout.ts +77 -0
  201. package/src/canvas/scroll.ts +120 -0
  202. package/src/canvas/text.ts +155 -0
  203. package/src/{theme.ts → canvas/theme.ts} +14 -1
  204. package/src/core/cache.ts +247 -0
  205. package/src/core/checked.ts +64 -0
  206. package/src/core/client.ts +103 -0
  207. package/src/core/conditions.ts +89 -0
  208. package/src/core/evaluate.ts +221 -0
  209. package/src/core/filters.ts +268 -0
  210. package/src/core/format.ts +309 -0
  211. package/src/core/measures.ts +99 -0
  212. package/src/core/request-ids.ts +54 -0
  213. package/src/core/rows.ts +213 -0
  214. package/src/core/store.ts +1122 -0
  215. package/src/core/totals.ts +59 -0
  216. package/src/core/trace.ts +44 -0
  217. package/src/core/transports.ts +177 -0
  218. package/src/core/types.ts +168 -0
  219. package/src/core.ts +39 -0
  220. package/src/index.ts +24 -12
  221. package/src/react/accessible-rows.tsx +242 -0
  222. package/src/react/cell-editor.tsx +216 -0
  223. package/src/react/cell-menu.tsx +166 -0
  224. package/src/react/column-menu.tsx +619 -0
  225. package/src/react/columns-menu.tsx +116 -0
  226. package/src/react/conditions-menu.tsx +289 -0
  227. package/src/react/context.ts +33 -0
  228. package/src/react/data-grid.tsx +286 -0
  229. package/src/react/detail-panel.tsx +92 -0
  230. package/src/react/export-menu.tsx +112 -0
  231. package/src/react/find-bar.tsx +112 -0
  232. package/src/react/format-form.tsx +421 -0
  233. package/src/react/formula-menu.tsx +163 -0
  234. package/src/react/grid-body.tsx +744 -0
  235. package/src/react/group-menu.tsx +214 -0
  236. package/src/react/hooks.ts +104 -0
  237. package/src/react/messages.ts +556 -0
  238. package/src/react/save.ts +42 -0
  239. package/src/react/static-rows.tsx +59 -0
  240. package/src/react/status-bar.tsx +175 -0
  241. package/src/react/toolbar-popover.tsx +69 -0
  242. package/src/react/toolbar.tsx +152 -0
  243. package/src/server.ts +58 -0
  244. package/src/styles.css +496 -316
  245. package/dist/components/bars.d.ts +0 -14
  246. package/dist/components/bars.d.ts.map +0 -1
  247. package/dist/components/bars.js +0 -67
  248. package/dist/components/bars.js.map +0 -1
  249. package/dist/components/cell-inspector.d.ts +0 -11
  250. package/dist/components/cell-inspector.d.ts.map +0 -1
  251. package/dist/components/cell-inspector.js +0 -37
  252. package/dist/components/cell-inspector.js.map +0 -1
  253. package/dist/components/columns-panel.d.ts +0 -12
  254. package/dist/components/columns-panel.d.ts.map +0 -1
  255. package/dist/components/columns-panel.js +0 -11
  256. package/dist/components/columns-panel.js.map +0 -1
  257. package/dist/components/filter-panel.d.ts +0 -17
  258. package/dist/components/filter-panel.d.ts.map +0 -1
  259. package/dist/components/filter-panel.js +0 -165
  260. package/dist/components/filter-panel.js.map +0 -1
  261. package/dist/components/find-bar.d.ts +0 -9
  262. package/dist/components/find-bar.d.ts.map +0 -1
  263. package/dist/components/find-bar.js +0 -46
  264. package/dist/components/find-bar.js.map +0 -1
  265. package/dist/components/popover.d.ts +0 -38
  266. package/dist/components/popover.d.ts.map +0 -1
  267. package/dist/components/popover.js +0 -104
  268. package/dist/components/popover.js.map +0 -1
  269. package/dist/components/stats-panel.d.ts +0 -12
  270. package/dist/components/stats-panel.d.ts.map +0 -1
  271. package/dist/components/stats-panel.js +0 -40
  272. package/dist/components/stats-panel.js.map +0 -1
  273. package/dist/controller.d.ts +0 -218
  274. package/dist/controller.d.ts.map +0 -1
  275. package/dist/controller.js +0 -788
  276. package/dist/controller.js.map +0 -1
  277. package/dist/data-grid.d.ts +0 -51
  278. package/dist/data-grid.d.ts.map +0 -1
  279. package/dist/data-grid.js +0 -698
  280. package/dist/data-grid.js.map +0 -1
  281. package/dist/labels.d.ts +0 -9
  282. package/dist/labels.d.ts.map +0 -1
  283. package/dist/labels.js +0 -38
  284. package/dist/labels.js.map +0 -1
  285. package/dist/messages.d.ts +0 -99
  286. package/dist/messages.d.ts.map +0 -1
  287. package/dist/messages.js +0 -276
  288. package/dist/messages.js.map +0 -1
  289. package/dist/renderer.d.ts +0 -52
  290. package/dist/renderer.d.ts.map +0 -1
  291. package/dist/renderer.js +0 -522
  292. package/dist/renderer.js.map +0 -1
  293. package/dist/text.d.ts +0 -14
  294. package/dist/text.d.ts.map +0 -1
  295. package/dist/text.js +0 -63
  296. package/dist/text.js.map +0 -1
  297. package/dist/theme.d.ts.map +0 -1
  298. package/dist/theme.js +0 -54
  299. package/dist/theme.js.map +0 -1
  300. package/src/components/bars.tsx +0 -179
  301. package/src/components/cell-inspector.tsx +0 -70
  302. package/src/components/columns-panel.tsx +0 -59
  303. package/src/components/filter-panel.tsx +0 -290
  304. package/src/components/find-bar.tsx +0 -104
  305. package/src/components/popover.tsx +0 -194
  306. package/src/components/stats-panel.tsx +0 -153
  307. package/src/controller.ts +0 -887
  308. package/src/data-grid.tsx +0 -1029
  309. package/src/labels.ts +0 -46
  310. package/src/messages.ts +0 -390
  311. package/src/renderer.ts +0 -535
  312. package/src/text.ts +0 -55
package/dist/theme.js DELETED
@@ -1,54 +0,0 @@
1
- const colorTokens = {
2
- background: "--vg-bg",
3
- text: "--vg-fg",
4
- muted: "--vg-muted",
5
- headerBackground: "--vg-header-bg",
6
- headerText: "--vg-header-fg",
7
- headerActive: "--vg-header-active-bg",
8
- gutterBackground: "--vg-gutter-bg",
9
- gutterText: "--vg-gutter-fg",
10
- gutterViewText: "--vg-gutter-view-fg",
11
- line: "--vg-line",
12
- lineStrong: "--vg-line-strong",
13
- selection: "--vg-selection-bg",
14
- selectionBorder: "--vg-selection-border",
15
- activeBorder: "--vg-active-border",
16
- accent: "--vg-accent",
17
- nullText: "--vg-null-fg",
18
- placeholder: "--vg-placeholder",
19
- match: "--vg-match-bg",
20
- error: "--vg-error-fg",
21
- };
22
- /** Resolves the theme of `root`. Colors given as `light-dark()` or system colors resolve to concrete values. */
23
- export function resolveTheme(root) {
24
- const probe = document.createElement("span");
25
- probe.style.display = "none";
26
- root.appendChild(probe);
27
- try {
28
- const color = (token) => {
29
- probe.style.color = `var(${token})`;
30
- return getComputedStyle(probe).color;
31
- };
32
- const style = getComputedStyle(root);
33
- const fontFamily = style.getPropertyValue("--vg-font-family").trim() || style.fontFamily || "system-ui, sans-serif";
34
- const fontSize = Number.parseFloat(style.getPropertyValue("--vg-font-size")) || Number.parseFloat(style.fontSize) || 13;
35
- const colors = Object.fromEntries(Object.entries(colorTokens).map(([key, token]) => [key, color(token)]));
36
- return {
37
- ...colors,
38
- fontFamily,
39
- fontSize,
40
- font: `${fontSize}px ${fontFamily}`,
41
- headerFont: `600 ${fontSize}px ${fontFamily}`,
42
- smallFont: `${Math.max(9, fontSize - 2)}px ${fontFamily}`,
43
- };
44
- }
45
- finally {
46
- probe.remove();
47
- }
48
- }
49
- /** Reads a length custom property in pixels, or `fallback`. */
50
- export function cssLength(root, token, fallback) {
51
- const value = Number.parseFloat(getComputedStyle(root).getPropertyValue(token));
52
- return Number.isFinite(value) && value > 0 ? value : fallback;
53
- }
54
- //# sourceMappingURL=theme.js.map
package/dist/theme.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,GAGb;IACF,UAAU,EAAE,SAAS;IACrB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,YAAY;IACnB,gBAAgB,EAAE,gBAAgB;IAClC,UAAU,EAAE,gBAAgB;IAC5B,YAAY,EAAE,uBAAuB;IACrC,gBAAgB,EAAE,gBAAgB;IAClC,UAAU,EAAE,gBAAgB;IAC5B,cAAc,EAAE,qBAAqB;IACrC,IAAI,EAAE,WAAW;IACjB,UAAU,EAAE,kBAAkB;IAC9B,SAAS,EAAE,mBAAmB;IAC9B,eAAe,EAAE,uBAAuB;IACxC,YAAY,EAAE,oBAAoB;IAClC,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,cAAc;IACxB,WAAW,EAAE,kBAAkB;IAC/B,KAAK,EAAE,eAAe;IACtB,KAAK,EAAE,eAAe;CACvB,CAAC;AAEF,gHAAgH;AAChH,MAAM,UAAU,YAAY,CAAC,IAAiB;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IAC7B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE;YAC9B,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,KAAK,GAAG,CAAC;YACpC,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;QACvC,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,UAAU,GAAG,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,UAAU,IAAI,uBAAuB,CAAC;QACpH,MAAM,QAAQ,GACZ,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACzG,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAGvG,CAAC;QACF,OAAO;YACL,GAAG,MAAM;YACT,UAAU;YACV,QAAQ;YACR,IAAI,EAAE,GAAG,QAAQ,MAAM,UAAU,EAAE;YACnC,UAAU,EAAE,OAAO,QAAQ,MAAM,UAAU,EAAE;YAC7C,SAAS,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,MAAM,UAAU,EAAE;SAC1D,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,MAAM,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,SAAS,CAAC,IAAiB,EAAE,KAAa,EAAE,QAAgB;IAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IAChF,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChE,CAAC"}
@@ -1,179 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
- import type { GridController } from "../controller.js";
3
- import { countFormat, filterLabel, numberFormat } from "../labels.js";
4
- import type { Messages } from "../messages.js";
5
-
6
- /** Search box, active sort and filters, row count and the columns button. */
7
- export function Toolbar({
8
- controller,
9
- messages,
10
- onColumns,
11
- }: {
12
- controller: GridController;
13
- messages: Messages;
14
- onColumns: (button: HTMLButtonElement) => void;
15
- }) {
16
- const query = controller.query;
17
- const [search, setSearch] = useState(query.search ?? "");
18
- const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
19
- const applied = query.search ?? "";
20
- const lastApplied = useRef(applied);
21
- // Follow changes made elsewhere (for example "Clear all").
22
- if (applied !== lastApplied.current) {
23
- lastApplied.current = applied;
24
- if (search !== applied) setSearch(applied);
25
- }
26
- useEffect(() => () => clearTimeout(timer.current), []);
27
-
28
- const ready = controller.info?.status === "ready";
29
- const capabilities = controller.info?.capabilities;
30
- const count = countFormat(controller.options.locale);
31
- const filters = query.filters ?? [];
32
- const sort = query.sort ?? [];
33
- const total = controller.totalRows;
34
- const filtered = controller.view.id !== null && (filters.length > 0 || Boolean(query.search?.trim()));
35
- let rows: string;
36
- if (controller.info?.status === "loading") {
37
- const progress = controller.info.progress;
38
- rows =
39
- progress !== null && progress !== undefined && progress > 0
40
- ? messages.loadingProgress(`${Math.round(progress * 100)}%`)
41
- : messages.loadingRows(count(controller.readableRows));
42
- } else if (filtered && total !== null) {
43
- rows = messages.rowsOf(count(controller.rowCount), count(total));
44
- } else {
45
- rows = messages.rows(count(controller.rowCount));
46
- }
47
-
48
- function change(value: string) {
49
- setSearch(value);
50
- clearTimeout(timer.current);
51
- timer.current = setTimeout(() => controller.setSearch(value), 350);
52
- }
53
-
54
- return (
55
- <div className="vg-toolbar">
56
- <input
57
- type="search"
58
- className="vg-input vg-toolbar__search"
59
- placeholder={messages.searchPlaceholder}
60
- aria-label={messages.search}
61
- value={search}
62
- disabled={!ready || !capabilities?.search}
63
- onChange={(event) => change(event.target.value)}
64
- onKeyDown={(event) => {
65
- if (event.key === "Enter") {
66
- clearTimeout(timer.current);
67
- controller.setSearch(search);
68
- }
69
- }}
70
- />
71
- {sort.length > 0 && (
72
- <span className="vg-chip">
73
- <span>
74
- {messages.sortedBy(
75
- sort
76
- .map(
77
- (key) =>
78
- `${controller.schema[key.column]?.name ?? key.column} ${key.direction === "asc" ? "↑" : "↓"}`,
79
- )
80
- .join(", "),
81
- )}
82
- </span>
83
- <button
84
- type="button"
85
- className="vg-chip__remove"
86
- aria-label={messages.clearSort}
87
- title={messages.clearSort}
88
- onClick={() => controller.clearSort()}
89
- >
90
- ×
91
- </button>
92
- </span>
93
- )}
94
- {filters.map((filter) => {
95
- const label = filterLabel(filter, controller.schema[filter.column], controller.formatter, messages);
96
- return (
97
- <span className="vg-chip vg-chip--filter" key={filter.column}>
98
- <span title={label}>{label}</span>
99
- <button
100
- type="button"
101
- className="vg-chip__remove"
102
- aria-label={messages.removeFilter(label)}
103
- title={messages.removeFilter(label)}
104
- onClick={() => controller.setFilter(filter.column, null)}
105
- >
106
- ×
107
- </button>
108
- </span>
109
- );
110
- })}
111
- {(filters.length > 0 || Boolean(query.search?.trim())) && (
112
- <button type="button" className="vg-button vg-button--small" onClick={() => controller.clearFilters()}>
113
- {messages.clearFilters}
114
- </button>
115
- )}
116
- <span className="vg-toolbar__spacer" />
117
- <span className="vg-toolbar__count" aria-live="polite">
118
- {controller.viewPending ? messages.buildingView : rows}
119
- </span>
120
- <button
121
- type="button"
122
- className="vg-button vg-button--small"
123
- onClick={(event) => onColumns(event.currentTarget)}
124
- disabled={!controller.info}
125
- >
126
- {messages.columns}
127
- </button>
128
- </div>
129
- );
130
- }
131
-
132
- /** The active cell and, for multi-cell selections, count, sum, average, minimum and maximum. */
133
- export function StatusBar({ controller, messages }: { controller: GridController; messages: Messages }) {
134
- const count = countFormat(controller.options.locale);
135
- const number = numberFormat(controller.options.locale);
136
- const active = controller.selection.active;
137
- const schema = controller.columnSchema(active.col);
138
- const summary = controller.summary;
139
- const label = controller.rowLabel(active.row);
140
- const notes = (controller.info?.notes ?? []).map((note) => messages.notes[note] ?? note);
141
- return (
142
- <div className="vg-statusbar">
143
- {controller.rowCount > 0 && schema && (
144
- <span className="vg-statusbar__cell">
145
- {messages.cell(count(active.row + 1), schema.name)}
146
- {controller.view.id !== null && label !== null && (
147
- <span className="vg-statusbar__muted"> · {messages.originalRow(count(label))}</span>
148
- )}
149
- </span>
150
- )}
151
- {notes.length > 0 && <span className="vg-statusbar__note">{notes.join(" ")}</span>}
152
- <span className="vg-toolbar__spacer" />
153
- {summary && (
154
- <span className="vg-statusbar__summary">
155
- <span>
156
- {messages.summary.count}: {count(summary.count)}
157
- </span>
158
- {summary.numericCount > 0 && summary.sum !== null && (
159
- <>
160
- <span>
161
- {messages.summary.average}: {number(summary.average ?? 0)}
162
- </span>
163
- <span>
164
- {messages.summary.min}: {number(summary.min ?? 0)}
165
- </span>
166
- <span>
167
- {messages.summary.max}: {number(summary.max ?? 0)}
168
- </span>
169
- <span>
170
- {messages.summary.sum}: {number(summary.sum)}
171
- </span>
172
- </>
173
- )}
174
- </span>
175
- )}
176
- {!summary && controller.summaryPending && <span className="vg-statusbar__muted">…</span>}
177
- </div>
178
- );
179
- }
@@ -1,70 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { type CellValue, type ColumnSchema, isAbortError, plainText } from "vastgrid-core";
3
- import type { Messages } from "../messages.js";
4
-
5
- /** Pretty-prints JSON text; returns null when the text is not a JSON object or array. */
6
- function prettyJson(text: string) {
7
- const trimmed = text.trim();
8
- if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) return null;
9
- try {
10
- return JSON.stringify(JSON.parse(trimmed), null, 2);
11
- } catch {
12
- return null;
13
- }
14
- }
15
-
16
- /** The full value of a cell, with line breaks, JSON formatting and a copy button. */
17
- export function CellInspector({
18
- schema,
19
- messages,
20
- location,
21
- load,
22
- onCopy,
23
- }: {
24
- schema: ColumnSchema;
25
- messages: Messages;
26
- location: string;
27
- load: (signal: AbortSignal) => Promise<CellValue>;
28
- onCopy: (text: string) => void;
29
- }) {
30
- const [value, setValue] = useState<{ text: string; isNull: boolean } | null>(null);
31
- const [error, setError] = useState<string | null>(null);
32
-
33
- useEffect(() => {
34
- const controller = new AbortController();
35
- load(controller.signal)
36
- .then((result) => {
37
- if (!controller.signal.aborted) setValue({ text: plainText(result), isNull: result === null });
38
- })
39
- .catch((cause: unknown) => {
40
- if (controller.signal.aborted || isAbortError(cause)) return;
41
- setError(cause instanceof Error ? cause.message : String(cause));
42
- });
43
- return () => controller.abort();
44
- }, [load]);
45
-
46
- const json = value ? prettyJson(value.text) : null;
47
- return (
48
- <div className="vg-inspector">
49
- <p className="vg-panel__title">
50
- {schema.name}
51
- <span className="vg-panel__muted">
52
- {" "}
53
- · {location} · {messages.types[schema.type] ?? schema.type}
54
- </span>
55
- </p>
56
- {error && <p className="vg-panel__error">{error}</p>}
57
- {!value && !error && <p className="vg-panel__muted">{messages.loading}</p>}
58
- {value && (
59
- <pre className="vg-inspector__value" data-null={value.isNull || undefined}>
60
- {value.isNull ? "NULL" : (json ?? value.text)}
61
- </pre>
62
- )}
63
- <div className="vg-panel__actions">
64
- <button type="button" className="vg-button" disabled={!value} onClick={() => value && onCopy(value.text)}>
65
- {messages.copy}
66
- </button>
67
- </div>
68
- </div>
69
- );
70
- }
@@ -1,59 +0,0 @@
1
- import { useState } from "react";
2
- import type { ColumnSchema } from "vastgrid-core";
3
- import type { ColumnState } from "../controller.js";
4
- import type { Messages } from "../messages.js";
5
-
6
- /** Shows and hides columns. */
7
- export function ColumnsPanel({
8
- columns,
9
- schema,
10
- messages,
11
- onToggle,
12
- onShowAll,
13
- }: {
14
- columns: ColumnState[];
15
- schema: ColumnSchema[];
16
- messages: Messages;
17
- onToggle: (index: number, hidden: boolean) => void;
18
- onShowAll: () => void;
19
- }) {
20
- const [search, setSearch] = useState("");
21
- const needle = search.trim().toLowerCase();
22
- const visibleCount = columns.filter((column) => !column.hidden).length;
23
- const shown = columns.filter(
24
- (column) => !needle || (schema[column.index]?.name ?? "").toLowerCase().includes(needle),
25
- );
26
- return (
27
- <div className="vg-columns">
28
- <p className="vg-panel__title">{messages.columns}</p>
29
- <input
30
- type="search"
31
- className="vg-input"
32
- placeholder={messages.search}
33
- aria-label={messages.search}
34
- value={search}
35
- data-autofocus
36
- onChange={(event) => setSearch(event.target.value)}
37
- />
38
- <div className="vg-filter__values">
39
- {shown.map((column) => (
40
- <label className="vg-check" key={column.index}>
41
- <input
42
- type="checkbox"
43
- checked={!column.hidden}
44
- disabled={!column.hidden && visibleCount <= 1}
45
- onChange={(event) => onToggle(column.index, !event.target.checked)}
46
- />
47
- <span className="vg-check__label">{schema[column.index]?.name}</span>
48
- <span className="vg-check__count">{messages.types[schema[column.index]?.type ?? "unknown"]}</span>
49
- </label>
50
- ))}
51
- </div>
52
- <div className="vg-panel__actions">
53
- <button type="button" className="vg-button" onClick={onShowAll} disabled={visibleCount === columns.length}>
54
- {messages.showAllColumns}
55
- </button>
56
- </div>
57
- </div>
58
- );
59
- }
@@ -1,290 +0,0 @@
1
- import { type FormEvent, useEffect, useId, useMemo, useState } from "react";
2
- import {
3
- type CellValue,
4
- type ColumnSchema,
5
- type DistinctValues,
6
- type Filter,
7
- isAbortError,
8
- isNumericType,
9
- isTemporalType,
10
- plainText,
11
- type ValueFormatter,
12
- } from "vastgrid-core";
13
- import type { Messages } from "../messages.js";
14
-
15
- type Operator = keyof Messages["operators"] | "none";
16
-
17
- const numericOperators: Operator[] = ["none", "eq", "ne", "gt", "ge", "lt", "le", "between", "blank", "notBlank"];
18
- const textOperators: Operator[] = [
19
- "none",
20
- "contains",
21
- "notContains",
22
- "startsWith",
23
- "endsWith",
24
- "equals",
25
- "notEquals",
26
- "blank",
27
- "notBlank",
28
- ];
29
-
30
- const NULL_KEY = "\u0000null";
31
-
32
- function keyOf(value: CellValue) {
33
- return value === null ? NULL_KEY : plainText(value);
34
- }
35
-
36
- function initialCondition(filter: Filter | null): { operator: Operator; first: string; second: string } {
37
- if (!filter) return { operator: "none", first: "", second: "" };
38
- switch (filter.type) {
39
- case "compare":
40
- return { operator: filter.op, first: plainText(filter.value), second: "" };
41
- case "between":
42
- return {
43
- operator: "between",
44
- first: filter.min === undefined ? "" : plainText(filter.min),
45
- second: filter.max === undefined ? "" : plainText(filter.max),
46
- };
47
- case "text":
48
- return { operator: filter.op, first: filter.value, second: "" };
49
- case "blank":
50
- return { operator: filter.blank ? "blank" : "notBlank", first: "", second: "" };
51
- default:
52
- return { operator: "none", first: "", second: "" };
53
- }
54
- }
55
-
56
- /** Converts typed text for a numeric column into a number when it is one. */
57
- function comparable(text: string, numeric: boolean): string | number {
58
- if (numeric) {
59
- const number = Number(text.replaceAll(",", ""));
60
- if (text.trim() !== "" && Number.isFinite(number)) return number;
61
- }
62
- return text;
63
- }
64
-
65
- /**
66
- * The filter for one column, in the manner of spreadsheet filters: a searchable list of values with
67
- * check boxes, or a condition (comparison, text match, range, blank).
68
- */
69
- export function FilterPanel({
70
- column,
71
- schema,
72
- filter,
73
- messages,
74
- formatter,
75
- count,
76
- load,
77
- onApply,
78
- }: {
79
- column: number;
80
- schema: ColumnSchema;
81
- filter: Filter | null;
82
- messages: Messages;
83
- formatter: ValueFormatter;
84
- count: (value: number) => string;
85
- load: (search: string, signal: AbortSignal) => Promise<DistinctValues> | null;
86
- onApply: (filter: Filter | null) => void;
87
- }) {
88
- const id = useId();
89
- const numeric = isNumericType(schema.type) || isTemporalType(schema.type);
90
- const operators = numeric
91
- ? numericOperators
92
- : schema.type === "boolean"
93
- ? (["none", "blank", "notBlank"] as Operator[])
94
- : textOperators;
95
- const [search, setSearch] = useState("");
96
- const [values, setValues] = useState<DistinctValues | null>(null);
97
- const [loading, setLoading] = useState(true);
98
- const [error, setError] = useState<string | null>(null);
99
- // Untouched values follow `base`; `toggled` holds the values the user changed.
100
- const [base, setBase] = useState(() => !(filter?.type === "values" && !filter.exclude));
101
- const [toggled, setToggled] = useState<Map<string, CellValue>>(() => {
102
- if (filter?.type !== "values") return new Map();
103
- return new Map(filter.values.map((value) => [keyOf(value), value]));
104
- });
105
- const [condition, setCondition] = useState(() => initialCondition(filter));
106
-
107
- useEffect(() => {
108
- const controller = new AbortController();
109
- const timer = setTimeout(
110
- () => {
111
- setLoading(true);
112
- const request = load(search, controller.signal);
113
- if (!request) {
114
- setLoading(false);
115
- return;
116
- }
117
- request
118
- .then((result) => {
119
- if (controller.signal.aborted) return;
120
- setValues(result);
121
- setError(null);
122
- setLoading(false);
123
- })
124
- .catch((cause: unknown) => {
125
- if (controller.signal.aborted || isAbortError(cause)) return;
126
- setError(cause instanceof Error ? cause.message : String(cause));
127
- setLoading(false);
128
- });
129
- },
130
- search ? 250 : 0,
131
- );
132
- return () => {
133
- clearTimeout(timer);
134
- controller.abort();
135
- };
136
- }, [load, search]);
137
-
138
- const isChecked = (value: CellValue) => (toggled.has(keyOf(value)) ? !base : base);
139
- const shown = values?.values ?? [];
140
- const allChecked = toggled.size === 0 ? base : shown.every((item) => isChecked(item.value)) && base;
141
-
142
- function toggle(value: CellValue) {
143
- setToggled((current) => {
144
- const next = new Map(current);
145
- const key = keyOf(value);
146
- if (next.has(key)) next.delete(key);
147
- else next.set(key, value);
148
- return next;
149
- });
150
- }
151
-
152
- function selectAll(checked: boolean) {
153
- setBase(checked);
154
- setToggled(new Map());
155
- }
156
-
157
- const valuesFilter = useMemo((): Filter | null => {
158
- const list = [...toggled.values()];
159
- if (base && list.length === 0) return null;
160
- if (base) return { type: "values", column, values: list, exclude: true };
161
- return { type: "values", column, values: list };
162
- }, [base, toggled, column]);
163
-
164
- function conditionFilter(): Filter | null {
165
- const { operator, first, second } = condition;
166
- switch (operator) {
167
- case "none":
168
- return null;
169
- case "blank":
170
- case "notBlank":
171
- return { type: "blank", column, blank: operator === "blank" };
172
- case "between":
173
- if (!first && !second) return null;
174
- return {
175
- type: "between",
176
- column,
177
- ...(first ? { min: comparable(first, isNumericType(schema.type)) } : {}),
178
- ...(second ? { max: comparable(second, isNumericType(schema.type)) } : {}),
179
- };
180
- case "eq":
181
- case "ne":
182
- case "lt":
183
- case "le":
184
- case "gt":
185
- case "ge":
186
- return first === ""
187
- ? null
188
- : { type: "compare", column, op: operator, value: comparable(first, isNumericType(schema.type)) };
189
- default:
190
- return first === "" ? null : { type: "text", column, op: operator, value: first };
191
- }
192
- }
193
-
194
- function apply(event: FormEvent) {
195
- event.preventDefault();
196
- onApply(condition.operator === "none" ? valuesFilter : conditionFilter());
197
- }
198
-
199
- const needsValue =
200
- condition.operator !== "none" && condition.operator !== "blank" && condition.operator !== "notBlank";
201
-
202
- return (
203
- <form className="vg-filter" onSubmit={apply}>
204
- <p className="vg-panel__title" id={`${id}-title`}>
205
- {messages.filterBy(schema.name)}
206
- </p>
207
- <input
208
- type="search"
209
- className="vg-input"
210
- placeholder={messages.valuesSearch}
211
- aria-label={messages.valuesSearch}
212
- value={search}
213
- data-autofocus
214
- onChange={(event) => setSearch(event.target.value)}
215
- disabled={condition.operator !== "none"}
216
- />
217
- <fieldset className="vg-filter__values" aria-labelledby={`${id}-title`} aria-busy={loading}>
218
- {!search && (
219
- <label className="vg-check vg-check--all">
220
- <input
221
- type="checkbox"
222
- checked={allChecked}
223
- onChange={(event) => selectAll(event.target.checked)}
224
- disabled={condition.operator !== "none"}
225
- />
226
- <span>{messages.selectAll}</span>
227
- </label>
228
- )}
229
- {shown.map((item) => (
230
- <label className="vg-check" key={keyOf(item.value)}>
231
- <input
232
- type="checkbox"
233
- checked={isChecked(item.value)}
234
- onChange={() => toggle(item.value)}
235
- disabled={condition.operator !== "none"}
236
- />
237
- <span className="vg-check__label">
238
- {item.value === null || item.value === "" ? messages.blank : formatter.format(item.value, schema.type)}
239
- </span>
240
- <span className="vg-check__count">{count(item.count)}</span>
241
- </label>
242
- ))}
243
- {loading && shown.length === 0 && <p className="vg-panel__muted">{messages.loading}</p>}
244
- {error && <p className="vg-panel__error">{error}</p>}
245
- </fieldset>
246
- {values?.truncated && <p className="vg-panel__muted">{messages.valuesTruncated(count(shown.length))}</p>}
247
- <fieldset className="vg-filter__condition">
248
- <legend>{messages.condition}</legend>
249
- <select
250
- className="vg-input"
251
- aria-label={messages.condition}
252
- value={condition.operator}
253
- onChange={(event) => setCondition({ ...condition, operator: event.target.value as Operator })}
254
- >
255
- {operators.map((operator) => (
256
- <option key={operator} value={operator}>
257
- {operator === "none" ? messages.conditionNone : messages.operators[operator]}
258
- </option>
259
- ))}
260
- </select>
261
- {needsValue && (
262
- <input
263
- className="vg-input"
264
- aria-label={condition.operator === "between" ? messages.minimum : messages.value}
265
- placeholder={condition.operator === "between" ? messages.minimum : messages.value}
266
- value={condition.first}
267
- onChange={(event) => setCondition({ ...condition, first: event.target.value })}
268
- />
269
- )}
270
- {condition.operator === "between" && (
271
- <input
272
- className="vg-input"
273
- aria-label={messages.maximum}
274
- placeholder={messages.maximum}
275
- value={condition.second}
276
- onChange={(event) => setCondition({ ...condition, second: event.target.value })}
277
- />
278
- )}
279
- </fieldset>
280
- <div className="vg-panel__actions">
281
- <button type="button" className="vg-button" onClick={() => onApply(null)}>
282
- {messages.clear}
283
- </button>
284
- <button type="submit" className="vg-button vg-button--primary">
285
- {messages.apply}
286
- </button>
287
- </div>
288
- </form>
289
- );
290
- }