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
@@ -0,0 +1,303 @@
1
+ /**
2
+ * The filter of one column as the column menu edits it: values ticked in a list, or a condition. Each is written
3
+ * as the parts of `where` that `filter.column` sets (§6.2.1 of the specification), and read back from them.
4
+ */
5
+ import { columnsOf, joinParts, partsOf, readCellText } from "vastgrid-spec";
6
+ const ORDERED = ["eq", "ne", "gt", "ge", "lt", "le", "between", "blank", "notBlank"];
7
+ const TEXT = ["contains", "notContains", "startsWith", "endsWith", "eq", "ne", "blank", "notBlank"];
8
+
9
+ /** The operators offered for a column's type. */
10
+ export function operatorsFor(type) {
11
+ if (type === "string") return TEXT;
12
+ if (type === "boolean" || type === "binary" || type === "interval") return ["blank", "notBlank"];
13
+ if (type === "uuid") return ["eq", "ne", "blank", "notBlank"];
14
+ return ORDERED;
15
+ }
16
+
17
+ /** Whether an operator takes no text. */
18
+ export function takesNoValue(operator) {
19
+ return operator === "blank" || operator === "notBlank";
20
+ }
21
+
22
+ /** A literal of a value of a column (§2.5): values that JSON writes as strings carry the column's type. */
23
+ function literalOf(value, column) {
24
+ if (typeof value === "string" && column.type !== "string") return {
25
+ lit: value,
26
+ type: column.type
27
+ };
28
+ return {
29
+ lit: value
30
+ };
31
+ }
32
+ function call(fn, ...args) {
33
+ return {
34
+ fn,
35
+ args
36
+ };
37
+ }
38
+
39
+ /** Rows for which `expr` is not true: null counts as not matching, so `not` keeps the rows where it is null. */
40
+ function notMatching(expr) {
41
+ return call("not", call("coalesce", expr, {
42
+ lit: false
43
+ }));
44
+ }
45
+ const TEXT_FUNCTIONS = {
46
+ contains: "contains",
47
+ notContains: "contains",
48
+ startsWith: "starts_with",
49
+ endsWith: "ends_with"
50
+ };
51
+ export class FilterTextError extends Error {
52
+ constructor(side) {
53
+ super(`The ${side} value is not a value of the column.`);
54
+ this.side = side;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * The parts of `where` for a column's filter; none when it keeps every row. Text that is not a value of the column
60
+ * throws a `FilterTextError` (§7.3 reads it as a cell's text is read).
61
+ */
62
+ export function filterParts(filter, column) {
63
+ const c = {
64
+ col: column.name
65
+ };
66
+ if (filter.kind === "values") {
67
+ const parts = [];
68
+ if (filter.values.length > 0) parts.push(call("in", c, ...filter.values.map(value => literalOf(value, column))));
69
+ if (filter.blank) parts.push(call("is_blank", c));
70
+ const matching = parts.length === 0 ? null : parts.length === 1 ? parts[0] : call("or", ...parts);
71
+ if (filter.exclude) return matching ? [notMatching(matching)] : [];
72
+ return [matching ?? {
73
+ lit: false
74
+ }];
75
+ }
76
+ const {
77
+ operator
78
+ } = filter;
79
+ if (operator === "blank") return [call("is_blank", c)];
80
+ if (operator === "notBlank") return [call("not", call("is_blank", c))];
81
+ const text = TEXT_FUNCTIONS[operator];
82
+ if (text) {
83
+ if (filter.first === "") return [];
84
+ const expr = call(text, call("lower", call("text", c)), call("lower", {
85
+ lit: filter.first
86
+ }));
87
+ return [operator === "notContains" ? notMatching(expr) : expr];
88
+ }
89
+ const read = (input, side) => {
90
+ if (input.trim() === "") return null;
91
+ const result = readCellText(input, column, {
92
+ grouping: true
93
+ });
94
+ if (!result.ok || result.value === null) throw new FilterTextError(side);
95
+ return literalOf(result.value, column);
96
+ };
97
+ if (operator === "between") {
98
+ const low = read(filter.first, "first");
99
+ const high = read(filter.second, "second");
100
+ return [...(low ? [call("ge", c, low)] : []), ...(high ? [call("le", c, high)] : [])];
101
+ }
102
+ const value = read(filter.first, "first");
103
+ return value ? [call(operator, c, value)] : [];
104
+ }
105
+ function isColumn(expr, name) {
106
+ return expr !== undefined && "col" in expr && expr.col === name;
107
+ }
108
+ function isLiteral(expr) {
109
+ return expr !== undefined && "lit" in expr;
110
+ }
111
+ function isCall(expr, fn) {
112
+ return expr !== undefined && "fn" in expr && expr.fn === fn;
113
+ }
114
+
115
+ /** The text of a literal as the menu shows it to edit. */
116
+ function literalText(literal) {
117
+ return literal.lit === null ? "" : String(literal.lit);
118
+ }
119
+
120
+ /** `in(c, …)`, `is_blank(c)` or the `or` of both, as values. */
121
+ function readValues(expr, name) {
122
+ const parts = isCall(expr, "or") ? expr.args : [expr];
123
+ let values = null;
124
+ let blank = false;
125
+ for (const part of parts) {
126
+ if (isCall(part, "in") && isColumn(part.args[0], name) && values === null && part.args.slice(1).every(isLiteral)) {
127
+ values = part.args.slice(1).map(literal => literal.lit);
128
+ } else if (isCall(part, "is_blank") && isColumn(part.args[0], name) && !blank) {
129
+ blank = true;
130
+ } else {
131
+ return null;
132
+ }
133
+ }
134
+ return {
135
+ values: values ?? [],
136
+ blank
137
+ };
138
+ }
139
+
140
+ /** `contains(lower(text(c)), lower(lit))` and the like: the operator and the text. */
141
+ function readText(expr, name) {
142
+ if (!("fn" in expr) || !["contains", "starts_with", "ends_with"].includes(expr.fn)) return null;
143
+ const [haystack, needle] = expr.args;
144
+ if (!isCall(haystack, "lower") || !isCall(haystack.args[0], "text") || !isColumn(haystack.args[0].args[0], name)) {
145
+ return null;
146
+ }
147
+ if (!isCall(needle, "lower") || !isLiteral(needle.args[0]) || typeof needle.args[0].lit !== "string") return null;
148
+ return {
149
+ fn: expr.fn,
150
+ text: needle.args[0].lit
151
+ };
152
+ }
153
+ const COMPARISONS = ["eq", "ne", "lt", "le", "gt", "ge"];
154
+
155
+ /**
156
+ * The filter that the conditions of a column (§6.2.1) make, when the menu wrote them or they have the same shape;
157
+ * null when there are none, and `"other"` for conditions written otherwise (an agent, a saved view).
158
+ */
159
+ export function readFilter(parts, name) {
160
+ if (parts.length === 0) return null;
161
+ if (parts.length === 2) {
162
+ const [low, high] = parts;
163
+ if (isCall(low, "ge") && isCall(high, "le") && isColumn(low.args[0], name) && isColumn(high.args[0], name) && isLiteral(low.args[1]) && isLiteral(high.args[1])) {
164
+ return {
165
+ kind: "condition",
166
+ operator: "between",
167
+ first: literalText(low.args[1]),
168
+ second: literalText(high.args[1])
169
+ };
170
+ }
171
+ return "other";
172
+ }
173
+ if (parts.length !== 1) return "other";
174
+ const [part] = parts;
175
+ if (isLiteral(part) && part.lit === false) return {
176
+ kind: "values",
177
+ values: [],
178
+ blank: false,
179
+ exclude: false
180
+ };
181
+ if (isCall(part, "is_blank") && isColumn(part.args[0], name)) {
182
+ return {
183
+ kind: "condition",
184
+ operator: "blank",
185
+ first: "",
186
+ second: ""
187
+ };
188
+ }
189
+ if (isCall(part, "not")) {
190
+ const inner = part.args[0];
191
+ if (isCall(inner, "is_blank") && isColumn(inner.args[0], name)) {
192
+ return {
193
+ kind: "condition",
194
+ operator: "notBlank",
195
+ first: "",
196
+ second: ""
197
+ };
198
+ }
199
+ if (isCall(inner, "coalesce") && isLiteral(inner.args[1]) && inner.args[1].lit === false) {
200
+ const matching = inner.args[0];
201
+ const values = readValues(matching, name);
202
+ if (values) return {
203
+ kind: "values",
204
+ ...values,
205
+ exclude: true
206
+ };
207
+ const text = readText(matching, name);
208
+ if (text?.fn === "contains") return {
209
+ kind: "condition",
210
+ operator: "notContains",
211
+ first: text.text,
212
+ second: ""
213
+ };
214
+ }
215
+ return "other";
216
+ }
217
+ const values = readValues(part, name);
218
+ if (values && values.values.length > 0) return {
219
+ kind: "values",
220
+ ...values,
221
+ exclude: false
222
+ };
223
+ const text = readText(part, name);
224
+ if (text) {
225
+ const operator = text.fn === "contains" ? "contains" : text.fn === "starts_with" ? "startsWith" : "endsWith";
226
+ return {
227
+ kind: "condition",
228
+ operator,
229
+ first: text.text,
230
+ second: ""
231
+ };
232
+ }
233
+ const comparison = COMPARISONS.find(fn => isCall(part, fn));
234
+ if (comparison && isCall(part, comparison)) {
235
+ const [left, right] = part.args;
236
+ // One side of a range is read as the comparison it is.
237
+ if (isColumn(left, name) && isLiteral(right)) {
238
+ return {
239
+ kind: "condition",
240
+ operator: comparison,
241
+ first: literalText(right),
242
+ second: ""
243
+ };
244
+ }
245
+ }
246
+ return "other";
247
+ }
248
+
249
+ /** The conditions of a column (§6.2.1): the parts of `where` that are not the search and refer to it and no other. */
250
+ export function conditionsOf(where, name) {
251
+ return partsOf(where).filter(part => {
252
+ if ("search" in part) return false;
253
+ const names = columnsOf(part);
254
+ return names.size === 1 && names.has(name);
255
+ });
256
+ }
257
+
258
+ /**
259
+ * The plan whose rows are a column's values and how many rows have each, the most common first: the rows of
260
+ * `plan` without its conditions on the column (so that the list holds the values that other filters leave), and
261
+ * with those whose text contains `search`, ignoring letter case. Null when the plan groups rows.
262
+ */
263
+ export function valuesPlan(plan, name, search) {
264
+ if (plan.aggregate) return null;
265
+ const own = new Set(conditionsOf(plan.where, name));
266
+ const parts = partsOf(plan.where).filter(part => !own.has(part));
267
+ if (search.trim() !== "") {
268
+ parts.push(call("contains", call("lower", call("text", {
269
+ col: name
270
+ })), call("lower", {
271
+ lit: search
272
+ })));
273
+ }
274
+ let count = "count";
275
+ for (let n = 2; count === name; n++) count = `count ${n}`;
276
+ const where = joinParts(parts);
277
+ return {
278
+ plan: {
279
+ ...(plan.derive ? {
280
+ derive: plan.derive
281
+ } : {}),
282
+ ...(where ? {
283
+ where
284
+ } : {}),
285
+ aggregate: {
286
+ keys: [name],
287
+ measures: [{
288
+ name: count,
289
+ fn: "count"
290
+ }]
291
+ },
292
+ sort: [{
293
+ column: count,
294
+ direction: "desc"
295
+ }, {
296
+ column: name,
297
+ direction: "asc"
298
+ }]
299
+ },
300
+ count
301
+ };
302
+ }
303
+ //# sourceMappingURL=filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["columnsOf","joinParts","partsOf","readCellText","ORDERED","TEXT","operatorsFor","type","takesNoValue","operator","literalOf","value","column","lit","call","fn","args","notMatching","expr","TEXT_FUNCTIONS","contains","notContains","startsWith","endsWith","FilterTextError","Error","constructor","side","filterParts","filter","c","col","name","kind","parts","values","length","push","map","blank","matching","exclude","text","first","read","input","trim","result","grouping","ok","low","high","second","isColumn","undefined","isLiteral","isCall","literalText","literal","String","readValues","part","slice","every","readText","includes","haystack","needle","COMPARISONS","readFilter","inner","comparison","find","left","right","conditionsOf","where","names","size","has","valuesPlan","plan","search","aggregate","own","Set","count","n","derive","keys","measures","sort","direction"],"sources":["filters.ts"],"sourcesContent":["/**\n * The filter of one column as the column menu edits it: values ticked in a list, or a condition. Each is written\n * as the parts of `where` that `filter.column` sets (§6.2.1 of the specification), and read back from them.\n */\nimport {\n type Column,\n columnsOf,\n type Expr,\n type FunctionName,\n type JsonValue,\n joinParts,\n type Literal,\n type LogicalType,\n type Plan,\n partsOf,\n readCellText,\n} from \"vastgrid-spec\";\n\nexport type FilterOperator =\n | \"eq\"\n | \"ne\"\n | \"lt\"\n | \"le\"\n | \"gt\"\n | \"ge\"\n | \"between\"\n | \"contains\"\n | \"notContains\"\n | \"startsWith\"\n | \"endsWith\"\n | \"blank\"\n | \"notBlank\";\n\nexport type ColumnFilter =\n /** Rows with one of `values`, or blank (§3.2) with `blank`; with `exclude`, the other rows. */\n | { kind: \"values\"; values: JsonValue[]; blank: boolean; exclude: boolean }\n /** A condition on typed text: `first`, and `second` for `between`; an empty text leaves that side out. */\n | { kind: \"condition\"; operator: FilterOperator; first: string; second: string };\n\nconst ORDERED: FilterOperator[] = [\"eq\", \"ne\", \"gt\", \"ge\", \"lt\", \"le\", \"between\", \"blank\", \"notBlank\"];\nconst TEXT: FilterOperator[] = [\"contains\", \"notContains\", \"startsWith\", \"endsWith\", \"eq\", \"ne\", \"blank\", \"notBlank\"];\n\n/** The operators offered for a column's type. */\nexport function operatorsFor(type: LogicalType): FilterOperator[] {\n if (type === \"string\") return TEXT;\n if (type === \"boolean\" || type === \"binary\" || type === \"interval\") return [\"blank\", \"notBlank\"];\n if (type === \"uuid\") return [\"eq\", \"ne\", \"blank\", \"notBlank\"];\n return ORDERED;\n}\n\n/** Whether an operator takes no text. */\nexport function takesNoValue(operator: FilterOperator) {\n return operator === \"blank\" || operator === \"notBlank\";\n}\n\n/** A literal of a value of a column (§2.5): values that JSON writes as strings carry the column's type. */\nfunction literalOf(value: JsonValue, column: Column): Literal {\n if (typeof value === \"string\" && column.type !== \"string\") return { lit: value, type: column.type };\n return { lit: value };\n}\n\nfunction call(fn: FunctionName, ...args: Expr[]): Expr {\n return { fn, args };\n}\n\n/** Rows for which `expr` is not true: null counts as not matching, so `not` keeps the rows where it is null. */\nfunction notMatching(expr: Expr): Expr {\n return call(\"not\", call(\"coalesce\", expr, { lit: false }));\n}\n\nconst TEXT_FUNCTIONS: Partial<Record<FilterOperator, FunctionName>> = {\n contains: \"contains\",\n notContains: \"contains\",\n startsWith: \"starts_with\",\n endsWith: \"ends_with\",\n};\n\nexport class FilterTextError extends Error {\n constructor(readonly side: \"first\" | \"second\") {\n super(`The ${side} value is not a value of the column.`);\n }\n}\n\n/**\n * The parts of `where` for a column's filter; none when it keeps every row. Text that is not a value of the column\n * throws a `FilterTextError` (§7.3 reads it as a cell's text is read).\n */\nexport function filterParts(filter: ColumnFilter, column: Column): Expr[] {\n const c: Expr = { col: column.name };\n if (filter.kind === \"values\") {\n const parts: Expr[] = [];\n if (filter.values.length > 0) parts.push(call(\"in\", c, ...filter.values.map((value) => literalOf(value, column))));\n if (filter.blank) parts.push(call(\"is_blank\", c));\n const matching = parts.length === 0 ? null : parts.length === 1 ? parts[0] : call(\"or\", ...parts);\n if (filter.exclude) return matching ? [notMatching(matching)] : [];\n return [matching ?? { lit: false }];\n }\n const { operator } = filter;\n if (operator === \"blank\") return [call(\"is_blank\", c)];\n if (operator === \"notBlank\") return [call(\"not\", call(\"is_blank\", c))];\n const text = TEXT_FUNCTIONS[operator];\n if (text) {\n if (filter.first === \"\") return [];\n const expr = call(text, call(\"lower\", call(\"text\", c)), call(\"lower\", { lit: filter.first }));\n return [operator === \"notContains\" ? notMatching(expr) : expr];\n }\n const read = (input: string, side: \"first\" | \"second\") => {\n if (input.trim() === \"\") return null;\n const result = readCellText(input, column, { grouping: true });\n if (!result.ok || result.value === null) throw new FilterTextError(side);\n return literalOf(result.value, column);\n };\n if (operator === \"between\") {\n const low = read(filter.first, \"first\");\n const high = read(filter.second, \"second\");\n return [...(low ? [call(\"ge\", c, low)] : []), ...(high ? [call(\"le\", c, high)] : [])];\n }\n const value = read(filter.first, \"first\");\n return value ? [call(operator as FunctionName, c, value)] : [];\n}\n\nfunction isColumn(expr: Expr | undefined, name: string) {\n return expr !== undefined && \"col\" in expr && expr.col === name;\n}\n\nfunction isLiteral(expr: Expr | undefined): expr is Literal {\n return expr !== undefined && \"lit\" in expr;\n}\n\nfunction isCall(expr: Expr | undefined, fn: FunctionName): expr is { fn: FunctionName; args: Expr[] } {\n return expr !== undefined && \"fn\" in expr && expr.fn === fn;\n}\n\n/** The text of a literal as the menu shows it to edit. */\nfunction literalText(literal: Literal): string {\n return literal.lit === null ? \"\" : String(literal.lit);\n}\n\n/** `in(c, …)`, `is_blank(c)` or the `or` of both, as values. */\nfunction readValues(expr: Expr, name: string): { values: JsonValue[]; blank: boolean } | null {\n const parts = isCall(expr, \"or\") ? expr.args : [expr];\n let values: JsonValue[] | null = null;\n let blank = false;\n for (const part of parts) {\n if (isCall(part, \"in\") && isColumn(part.args[0], name) && values === null && part.args.slice(1).every(isLiteral)) {\n values = (part.args.slice(1) as Literal[]).map((literal) => literal.lit);\n } else if (isCall(part, \"is_blank\") && isColumn(part.args[0], name) && !blank) {\n blank = true;\n } else {\n return null;\n }\n }\n return { values: values ?? [], blank };\n}\n\n/** `contains(lower(text(c)), lower(lit))` and the like: the operator and the text. */\nfunction readText(expr: Expr, name: string): { fn: FunctionName; text: string } | null {\n if (!(\"fn\" in expr) || ![\"contains\", \"starts_with\", \"ends_with\"].includes(expr.fn)) return null;\n const [haystack, needle] = expr.args;\n if (!isCall(haystack, \"lower\") || !isCall(haystack.args[0], \"text\") || !isColumn(haystack.args[0].args[0], name)) {\n return null;\n }\n if (!isCall(needle, \"lower\") || !isLiteral(needle.args[0]) || typeof needle.args[0].lit !== \"string\") return null;\n return { fn: expr.fn, text: needle.args[0].lit };\n}\n\nconst COMPARISONS: FilterOperator[] = [\"eq\", \"ne\", \"lt\", \"le\", \"gt\", \"ge\"];\n\n/**\n * The filter that the conditions of a column (§6.2.1) make, when the menu wrote them or they have the same shape;\n * null when there are none, and `\"other\"` for conditions written otherwise (an agent, a saved view).\n */\nexport function readFilter(parts: readonly Expr[], name: string): ColumnFilter | \"other\" | null {\n if (parts.length === 0) return null;\n if (parts.length === 2) {\n const [low, high] = parts;\n if (\n isCall(low, \"ge\") &&\n isCall(high, \"le\") &&\n isColumn(low.args[0], name) &&\n isColumn(high.args[0], name) &&\n isLiteral(low.args[1]) &&\n isLiteral(high.args[1])\n ) {\n return {\n kind: \"condition\",\n operator: \"between\",\n first: literalText(low.args[1]),\n second: literalText(high.args[1]),\n };\n }\n return \"other\";\n }\n if (parts.length !== 1) return \"other\";\n const [part] = parts;\n if (isLiteral(part) && part.lit === false) return { kind: \"values\", values: [], blank: false, exclude: false };\n if (isCall(part, \"is_blank\") && isColumn(part.args[0], name)) {\n return { kind: \"condition\", operator: \"blank\", first: \"\", second: \"\" };\n }\n if (isCall(part, \"not\")) {\n const inner = part.args[0];\n if (isCall(inner, \"is_blank\") && isColumn(inner.args[0], name)) {\n return { kind: \"condition\", operator: \"notBlank\", first: \"\", second: \"\" };\n }\n if (isCall(inner, \"coalesce\") && isLiteral(inner.args[1]) && inner.args[1].lit === false) {\n const matching = inner.args[0];\n const values = readValues(matching, name);\n if (values) return { kind: \"values\", ...values, exclude: true };\n const text = readText(matching, name);\n if (text?.fn === \"contains\") return { kind: \"condition\", operator: \"notContains\", first: text.text, second: \"\" };\n }\n return \"other\";\n }\n const values = readValues(part, name);\n if (values && values.values.length > 0) return { kind: \"values\", ...values, exclude: false };\n const text = readText(part, name);\n if (text) {\n const operator = text.fn === \"contains\" ? \"contains\" : text.fn === \"starts_with\" ? \"startsWith\" : \"endsWith\";\n return { kind: \"condition\", operator, first: text.text, second: \"\" };\n }\n const comparison = COMPARISONS.find((fn) => isCall(part, fn as FunctionName));\n if (comparison && isCall(part, comparison as FunctionName)) {\n const [left, right] = part.args;\n // One side of a range is read as the comparison it is.\n if (isColumn(left, name) && isLiteral(right)) {\n return { kind: \"condition\", operator: comparison, first: literalText(right), second: \"\" };\n }\n }\n return \"other\";\n}\n\n/** The conditions of a column (§6.2.1): the parts of `where` that are not the search and refer to it and no other. */\nexport function conditionsOf(where: Expr | undefined, name: string): Expr[] {\n return partsOf(where).filter((part) => {\n if (\"search\" in part) return false;\n const names = columnsOf(part);\n return names.size === 1 && names.has(name);\n });\n}\n\n/**\n * The plan whose rows are a column's values and how many rows have each, the most common first: the rows of\n * `plan` without its conditions on the column (so that the list holds the values that other filters leave), and\n * with those whose text contains `search`, ignoring letter case. Null when the plan groups rows.\n */\nexport function valuesPlan(plan: Plan, name: string, search: string): { plan: Plan; count: string } | null {\n if (plan.aggregate) return null;\n const own = new Set(conditionsOf(plan.where, name));\n const parts = partsOf(plan.where).filter((part) => !own.has(part));\n if (search.trim() !== \"\") {\n parts.push(call(\"contains\", call(\"lower\", call(\"text\", { col: name })), call(\"lower\", { lit: search })));\n }\n let count = \"count\";\n for (let n = 2; count === name; n++) count = `count ${n}`;\n const where = joinParts(parts);\n return {\n plan: {\n ...(plan.derive ? { derive: plan.derive } : {}),\n ...(where ? { where } : {}),\n aggregate: { keys: [name], measures: [{ name: count, fn: \"count\" }] },\n sort: [\n { column: count, direction: \"desc\" },\n { column: name, direction: \"asc\" },\n ],\n },\n count,\n };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * How cells show their values with the `number` and `date` of a column's format (§5.1 of the specification): 0.1's
3
+ * `NumberFormat` and `DateFormat`, ported from `vastgrid-core` (Intl formats, with the exact digits of decimals,
4
+ * and date patterns such as `yyyy/MM/dd`).
5
+ */
6
+ import type { Column, ColumnFormat, JsonValue } from "vastgrid-spec";
7
+ /** Whether a column holds numbers. */
8
+ export declare function isNumeric(column: Column): boolean;
9
+ /**
10
+ * The text of values of a column by its format, or null where the format says nothing about a value (the grid then
11
+ * shows its text form, §2.4). Formats that Intl refuses (an unknown currency or time zone) give null for every value.
12
+ */
13
+ export declare function formatterFor(column: Column, format: ColumnFormat | undefined, locale: string | undefined): ((value: JsonValue) => string | null) | null;
14
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/core/format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAc,SAAS,EAAgB,MAAM,eAAe,CAAC;AAwQ/F,sCAAsC;AACtC,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,WAEvC;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,CAAC,CAAC,KAAK,EAAE,SAAS,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAwB9C"}
@@ -0,0 +1,303 @@
1
+ /**
2
+ * How cells show their values with the `number` and `date` of a column's format (§5.1 of the specification): 0.1's
3
+ * `NumberFormat` and `DateFormat`, ported from `vastgrid-core` (Intl formats, with the exact digits of decimals,
4
+ * and date patterns such as `yyyy/MM/dd`).
5
+ */
6
+
7
+ const decimalLiteral = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
8
+
9
+ /**
10
+ * A value as input for `Intl.NumberFormat`, or null when it is not a finite number. Numeric strings
11
+ * stay strings, which `Intl.NumberFormat` formats from their exact digits.
12
+ */
13
+ function numberInput(value) {
14
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
15
+ if (typeof value !== "string") return null;
16
+ const text = value.trim();
17
+ return decimalLiteral.test(text) ? text : null;
18
+ }
19
+ function numberFormatter(locale, format, grouping) {
20
+ const style = format.style ?? "decimal";
21
+ if (style === "currency" && !format.currency) throw new RangeError("A currency format needs `currency`.");
22
+ const options = {
23
+ style,
24
+ useGrouping: format.grouping ?? grouping,
25
+ minimumFractionDigits: format.fractionDigits ?? format.minimumFractionDigits,
26
+ maximumFractionDigits: format.fractionDigits ?? format.maximumFractionDigits
27
+ };
28
+ if (style === "currency") {
29
+ options.currency = format.currency;
30
+ if (format.currencyDisplay) options.currencyDisplay = format.currencyDisplay;
31
+ }
32
+ if (format.notation) options.notation = format.notation;
33
+ if (format.signDisplay) options.signDisplay = format.signDisplay;
34
+ // A maximum below the style's default minimum (2 for USD, 0 for percent) lowers the minimum too.
35
+ if (options.maximumFractionDigits !== undefined && options.minimumFractionDigits === undefined) {
36
+ options.minimumFractionDigits = Math.min(options.maximumFractionDigits, new Intl.NumberFormat(locale, {
37
+ ...options,
38
+ maximumFractionDigits: undefined
39
+ }).resolvedOptions().minimumFractionDigits ?? 0);
40
+ }
41
+ const intl = new Intl.NumberFormat(locale, options);
42
+ const prefix = format.prefix ?? "";
43
+ const suffix = format.suffix ?? "";
44
+ // `format` takes exact decimal strings (Intl.NumberFormat v3); the types only list numbers.
45
+ return value => `${prefix}${intl.format(value)}${suffix}`;
46
+ }
47
+
48
+ /** The parts of an ISO-like date, time or timestamp. */
49
+
50
+ const isoDateTime = /^([+-]?\d{4,})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?)?\s*(Z|[+-]\d{2}(?::?\d{2})?)?$/i;
51
+ const isoTime = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?$/;
52
+ function parseOffset(text) {
53
+ if (!text) return null;
54
+ if (text.toUpperCase() === "Z") return 0;
55
+ const sign = text.startsWith("-") ? -1 : 1;
56
+ const digits = text.slice(1).replace(":", "");
57
+ return sign * (Number(digits.slice(0, 2)) * 60 + Number(digits.slice(2) || 0));
58
+ }
59
+ function parseDate(text) {
60
+ const date = isoDateTime.exec(text.trim());
61
+ if (date) {
62
+ const [, year, month, day, hour, minute, second, fraction, offset] = date;
63
+ return {
64
+ year: Number(year),
65
+ month: Number(month),
66
+ day: Number(day),
67
+ hour: Number(hour ?? 0),
68
+ minute: Number(minute ?? 0),
69
+ second: Number(second ?? 0),
70
+ fraction: fraction ?? "",
71
+ hasDate: true,
72
+ hasTime: hour !== undefined,
73
+ offset: parseOffset(offset)
74
+ };
75
+ }
76
+ const time = isoTime.exec(text.trim());
77
+ if (!time) return null;
78
+ const [, hour, minute, second, fraction] = time;
79
+ return {
80
+ year: 1970,
81
+ month: 1,
82
+ day: 1,
83
+ hour: Number(hour),
84
+ minute: Number(minute),
85
+ second: Number(second ?? 0),
86
+ fraction: fraction ?? "",
87
+ hasDate: false,
88
+ hasTime: true,
89
+ offset: null
90
+ };
91
+ }
92
+
93
+ /** The UTC time of the parts' wall clock (ignoring their offset). Years below 100 are kept. */
94
+ function wallClock(parts) {
95
+ const date = new Date(0);
96
+ date.setUTCFullYear(parts.year, parts.month - 1, parts.day);
97
+ date.setUTCHours(parts.hour, parts.minute, parts.second, Number(parts.fraction.slice(0, 3).padEnd(3, "0")));
98
+ return date;
99
+ }
100
+ const patternToken = /'(?:[^']|'')*'|yyyy|yy|MMMM|MMM|MM|M|dd|d|EEEE|EEE|HH|H|hh|h|mm|m|ss|s|S+|a/g;
101
+ function dateFormatter(locale, format) {
102
+ const {
103
+ pattern,
104
+ timeZone,
105
+ ...options
106
+ } = format;
107
+ // Checks the time zone and the options now, not on the first value.
108
+ new Intl.DateTimeFormat(locale, {
109
+ ...options,
110
+ timeZone
111
+ });
112
+ const custom = Object.keys(options).length > 0;
113
+ const formats = new Map();
114
+ const intl = (key, make) => {
115
+ let item = formats.get(key);
116
+ if (!item) {
117
+ item = make();
118
+ formats.set(key, item);
119
+ }
120
+ return item;
121
+ };
122
+ /** The parts in the shown time zone: values with an offset move to `timeZone`. */
123
+ const local = parts => {
124
+ if (parts.offset === null) return parts;
125
+ const instant = new Date(wallClock(parts).getTime() - parts.offset * 60_000);
126
+ const fields = intl("parts", () => new Intl.DateTimeFormat("en-US-u-nu-latn", {
127
+ timeZone,
128
+ year: "numeric",
129
+ month: "numeric",
130
+ day: "numeric",
131
+ hour: "numeric",
132
+ minute: "numeric",
133
+ second: "numeric",
134
+ hourCycle: "h23",
135
+ era: "short"
136
+ })).formatToParts(instant);
137
+ const field = type => Number(fields.find(part => part.type === type)?.value ?? 0);
138
+ const bc = fields.find(part => part.type === "era")?.value.startsWith("B");
139
+ return {
140
+ ...parts,
141
+ year: bc ? 1 - field("year") : field("year"),
142
+ month: field("month"),
143
+ day: field("day"),
144
+ hour: field("hour"),
145
+ minute: field("minute"),
146
+ second: field("second"),
147
+ offset: null
148
+ };
149
+ };
150
+ if (pattern !== undefined) {
151
+ const tokens = pattern.match(patternToken) ?? [];
152
+ const texts = pattern.split(patternToken);
153
+ const name = (key, options, date) => intl(key, () => new Intl.DateTimeFormat(locale, {
154
+ ...options,
155
+ timeZone: "UTC"
156
+ })).format(date);
157
+ const pad = (value, length) => value < 0 ? `-${String(-value).padStart(length, "0")}` : String(value).padStart(length, "0");
158
+ return parts => {
159
+ const shown = local(parts);
160
+ const date = wallClock(shown);
161
+ let text = texts[0];
162
+ tokens.forEach((token, index) => {
163
+ let part;
164
+ switch (token) {
165
+ case "yyyy":
166
+ part = pad(shown.year, 4);
167
+ break;
168
+ case "yy":
169
+ part = pad(Math.abs(shown.year) % 100, 2);
170
+ break;
171
+ case "MMMM":
172
+ part = name("MMMM", {
173
+ month: "long"
174
+ }, date);
175
+ break;
176
+ case "MMM":
177
+ part = name("MMM", {
178
+ month: "short"
179
+ }, date);
180
+ break;
181
+ case "MM":
182
+ part = pad(shown.month, 2);
183
+ break;
184
+ case "M":
185
+ part = String(shown.month);
186
+ break;
187
+ case "dd":
188
+ part = pad(shown.day, 2);
189
+ break;
190
+ case "d":
191
+ part = String(shown.day);
192
+ break;
193
+ case "EEEE":
194
+ part = name("EEEE", {
195
+ weekday: "long"
196
+ }, date);
197
+ break;
198
+ case "EEE":
199
+ part = name("EEE", {
200
+ weekday: "short"
201
+ }, date);
202
+ break;
203
+ case "HH":
204
+ part = pad(shown.hour, 2);
205
+ break;
206
+ case "H":
207
+ part = String(shown.hour);
208
+ break;
209
+ case "hh":
210
+ part = pad(shown.hour % 12 || 12, 2);
211
+ break;
212
+ case "h":
213
+ part = String(shown.hour % 12 || 12);
214
+ break;
215
+ case "a":
216
+ part = intl("a", () => new Intl.DateTimeFormat(locale, {
217
+ hour: "numeric",
218
+ hour12: true,
219
+ timeZone: "UTC"
220
+ })).formatToParts(date).find(item => item.type === "dayPeriod")?.value ?? (shown.hour < 12 ? "AM" : "PM");
221
+ break;
222
+ case "mm":
223
+ part = pad(shown.minute, 2);
224
+ break;
225
+ case "m":
226
+ part = String(shown.minute);
227
+ break;
228
+ case "ss":
229
+ part = pad(shown.second, 2);
230
+ break;
231
+ case "s":
232
+ part = String(shown.second);
233
+ break;
234
+ default:
235
+ part = token.startsWith("S") ? shown.fraction.slice(0, token.length).padEnd(token.length, "0") : token.slice(1, -1).replaceAll("''", "'");
236
+ }
237
+ text += part + texts[index + 1];
238
+ });
239
+ return text;
240
+ };
241
+ }
242
+ return parts => {
243
+ const zone = parts.offset !== null ? "z" : "";
244
+ const shape = custom ? `custom${zone}` : `${parts.hasDate ? "d" : ""}${parts.hasTime ? "t" : ""}${zone}`;
245
+ const item = intl(shape, () => {
246
+ const defaults = custom ? options : {};
247
+ if (!custom && parts.hasDate) Object.assign(defaults, {
248
+ year: "numeric",
249
+ month: "2-digit",
250
+ day: "2-digit"
251
+ });
252
+ if (!custom && parts.hasTime) Object.assign(defaults, {
253
+ hour: "2-digit",
254
+ minute: "2-digit",
255
+ second: "2-digit"
256
+ });
257
+ // Values without an offset are wall-clock times: show them as they are.
258
+ return new Intl.DateTimeFormat(locale, {
259
+ ...defaults,
260
+ timeZone: parts.offset !== null ? timeZone : "UTC"
261
+ });
262
+ });
263
+ const time = wallClock(parts).getTime() - (parts.offset ?? 0) * 60_000;
264
+ return item.format(time);
265
+ };
266
+ }
267
+
268
+ /** Whether a column holds numbers. */
269
+ export function isNumeric(column) {
270
+ return column.type === "integer" || column.type === "bigint" || column.type === "decimal" || column.type === "float";
271
+ }
272
+ const TEMPORAL = new Set(["date", "time", "timestamp", "timestamptz"]);
273
+
274
+ /**
275
+ * The text of values of a column by its format, or null where the format says nothing about a value (the grid then
276
+ * shows its text form, §2.4). Formats that Intl refuses (an unknown currency or time zone) give null for every value.
277
+ */
278
+ export function formatterFor(column, format, locale) {
279
+ if (!format?.number && !format?.date) return null;
280
+ let number = null;
281
+ let date = null;
282
+ try {
283
+ if (format.number && isNumeric(column)) number = numberFormatter(locale, format.number, true);
284
+ if (format.date && TEMPORAL.has(column.type)) date = dateFormatter(locale, format.date);
285
+ } catch (error) {
286
+ if (error instanceof RangeError) return null;
287
+ throw error;
288
+ }
289
+ if (!number && !date) return null;
290
+ return value => {
291
+ if (value === null) return null;
292
+ if (number) {
293
+ const input = numberInput(value);
294
+ return input === null ? null : number(input);
295
+ }
296
+ if (date && typeof value === "string") {
297
+ const parts = parseDate(value.replace(/\+00$/, "Z"));
298
+ return parts ? date(parts) : null;
299
+ }
300
+ return null;
301
+ };
302
+ }
303
+ //# sourceMappingURL=format.js.map