@svgrid/enterprise 3.1.8 → 3.1.10

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 (268) hide show
  1. package/README.md +1 -0
  2. package/dist/SvAlertRuleEditor.svelte +16 -5
  3. package/dist/SvGridAlerts.svelte +8 -0
  4. package/dist/SvGridAlerts.svelte.d.ts +4 -0
  5. package/dist/SvGridBoard.svelte +69 -10
  6. package/dist/SvGridBulkEditDrawer.svelte +13 -5
  7. package/dist/SvGridGallery.svelte +286 -0
  8. package/dist/SvGridGallery.svelte.d.ts +30 -0
  9. package/dist/SvGridScheduler.svelte +33 -11
  10. package/dist/SvGridSelectionBar.svelte +47 -0
  11. package/dist/advanced-filter/expr-columns-from-grid.d.ts +1 -0
  12. package/dist/advanced-filter/expr-columns-from-grid.js +11 -7
  13. package/dist/advanced-filter-enable.d.ts +6 -0
  14. package/dist/advanced-filter-enable.js +8 -0
  15. package/dist/alerts/alert-engine.d.ts +7 -0
  16. package/dist/alerts/alert-engine.js +5 -2
  17. package/dist/alerts/condition-editor.d.ts +26 -0
  18. package/dist/alerts/condition-editor.js +17 -0
  19. package/dist/base/index.d.ts +20 -0
  20. package/dist/base/index.js +20 -0
  21. package/dist/bulk-edit.d.ts +11 -0
  22. package/dist/bulk-edit.js +18 -1
  23. package/dist/cdn/svgrid-enterprise.svelte-external.js +11197 -10639
  24. package/dist/designer/assets/GridFooter-Cu8_yBlQ.js +2 -0
  25. package/dist/designer/assets/GridMenus-BUjkkAU2.js +6 -0
  26. package/dist/designer/assets/PinSplitter-BgTpgh1d.css +1 -0
  27. package/dist/designer/assets/PinSplitter-C5AwA1xV.js +1 -0
  28. package/dist/designer/assets/SvGrid.helpers-CIrmIo9U.js +1 -0
  29. package/dist/designer/assets/SvGridCellEditor-CJxThKL_.js +4 -0
  30. package/dist/designer/assets/SvListBox-CS7oUAiX.js +2 -0
  31. package/dist/designer/assets/conditional-formatting-MDWbF8_Y.js +1 -0
  32. package/dist/designer/assets/grid-extension.svelte-BZ7AqyJI.js +1 -0
  33. package/dist/designer/assets/index-BmvG99kR.css +1 -0
  34. package/dist/designer/assets/{index-Obk8sI77.js → index-CJodU3tU.js} +307 -307
  35. package/dist/designer/assets/sparkline-C-NcnB2i.js +1 -0
  36. package/dist/designer/assets/src-C_U1NKOY.js +58 -0
  37. package/dist/designer/assets/sv-grid-scrollbar-B9N6Wuj0.js +109 -0
  38. package/dist/designer/assets/validate-H5u2HEQh.js +1 -0
  39. package/dist/designer/index.html +11 -7
  40. package/dist/expressions/compile.js +10 -0
  41. package/dist/expressions/evaluate.js +18 -0
  42. package/dist/expressions/expression-types.d.ts +10 -0
  43. package/dist/expressions/kinds.d.ts +28 -0
  44. package/dist/expressions/kinds.js +19 -0
  45. package/dist/expressions/parse.js +9 -1
  46. package/dist/fields/AttachmentCell.svelte +242 -0
  47. package/dist/fields/AttachmentCell.svelte.d.ts +5 -0
  48. package/dist/fields/AttachmentDialog.svelte +357 -0
  49. package/dist/fields/AttachmentDialog.svelte.d.ts +13 -0
  50. package/dist/fields/AttachmentEditor.svelte +41 -0
  51. package/dist/fields/AttachmentEditor.svelte.d.ts +11 -0
  52. package/dist/fields/AttachmentPreview.svelte +72 -0
  53. package/dist/fields/AttachmentPreview.svelte.d.ts +8 -0
  54. package/dist/fields/BarcodeCell.svelte +20 -0
  55. package/dist/fields/BarcodeCell.svelte.d.ts +5 -0
  56. package/dist/fields/BaseAutomations.svelte +558 -0
  57. package/dist/fields/BaseAutomations.svelte.d.ts +18 -0
  58. package/dist/fields/BaseChartView.svelte +47 -0
  59. package/dist/fields/BaseChartView.svelte.d.ts +8 -0
  60. package/dist/fields/BaseTableView.svelte +185 -0
  61. package/dist/fields/BaseTableView.svelte.d.ts +37 -0
  62. package/dist/fields/BaseTabs.svelte +308 -0
  63. package/dist/fields/BaseTabs.svelte.d.ts +21 -0
  64. package/dist/fields/ButtonCell.svelte +47 -0
  65. package/dist/fields/ButtonCell.svelte.d.ts +20 -0
  66. package/dist/fields/CheckboxCell.svelte +46 -0
  67. package/dist/fields/CheckboxCell.svelte.d.ts +14 -0
  68. package/dist/fields/DeleteFieldConfirm.svelte +80 -0
  69. package/dist/fields/DeleteFieldConfirm.svelte.d.ts +8 -0
  70. package/dist/fields/DeleteRecordConfirm.svelte +48 -0
  71. package/dist/fields/DeleteRecordConfirm.svelte.d.ts +9 -0
  72. package/dist/fields/FieldCell.svelte +80 -0
  73. package/dist/fields/FieldCell.svelte.d.ts +9 -0
  74. package/dist/fields/FieldEditor.svelte +321 -0
  75. package/dist/fields/FieldEditor.svelte.d.ts +10 -0
  76. package/dist/fields/FieldInput.svelte +188 -0
  77. package/dist/fields/FieldInput.svelte.d.ts +29 -0
  78. package/dist/fields/FieldsHeaderEnd.svelte +146 -0
  79. package/dist/fields/FieldsHeaderEnd.svelte.d.ts +6 -0
  80. package/dist/fields/FootRowAdder.svelte +8 -0
  81. package/dist/fields/FootRowAdder.svelte.d.ts +6 -0
  82. package/dist/fields/GroupHeader.svelte +127 -0
  83. package/dist/fields/GroupHeader.svelte.d.ts +24 -0
  84. package/dist/fields/LinkCell.svelte +46 -0
  85. package/dist/fields/LinkCell.svelte.d.ts +9 -0
  86. package/dist/fields/LinkPickerEditor.svelte +172 -0
  87. package/dist/fields/LinkPickerEditor.svelte.d.ts +9 -0
  88. package/dist/fields/LinkPreview.svelte +60 -0
  89. package/dist/fields/LinkPreview.svelte.d.ts +7 -0
  90. package/dist/fields/LinkedRecordView.svelte +94 -0
  91. package/dist/fields/LinkedRecordView.svelte.d.ts +9 -0
  92. package/dist/fields/LinkedRecordsCell.svelte +59 -0
  93. package/dist/fields/LinkedRecordsCell.svelte.d.ts +11 -0
  94. package/dist/fields/LongTextEditor.svelte +157 -0
  95. package/dist/fields/LongTextEditor.svelte.d.ts +11 -0
  96. package/dist/fields/NumberEditor.svelte +309 -0
  97. package/dist/fields/NumberEditor.svelte.d.ts +12 -0
  98. package/dist/fields/OptionsEditor.svelte +117 -0
  99. package/dist/fields/OptionsEditor.svelte.d.ts +11 -0
  100. package/dist/fields/PercentCell.svelte +21 -0
  101. package/dist/fields/PercentCell.svelte.d.ts +8 -0
  102. package/dist/fields/PropertyInput.svelte +204 -0
  103. package/dist/fields/PropertyInput.svelte.d.ts +11 -0
  104. package/dist/fields/RatingCell.svelte +75 -0
  105. package/dist/fields/RatingCell.svelte.d.ts +16 -0
  106. package/dist/fields/RatingEditor.svelte +82 -0
  107. package/dist/fields/RatingEditor.svelte.d.ts +9 -0
  108. package/dist/fields/RecordView.svelte +310 -0
  109. package/dist/fields/RecordView.svelte.d.ts +11 -0
  110. package/dist/fields/RichTextCell.svelte +30 -0
  111. package/dist/fields/RichTextCell.svelte.d.ts +5 -0
  112. package/dist/fields/RowAdder.svelte +93 -0
  113. package/dist/fields/RowAdder.svelte.d.ts +7 -0
  114. package/dist/fields/RowHeader.svelte +99 -0
  115. package/dist/fields/RowHeader.svelte.d.ts +11 -0
  116. package/dist/fields/SelectCell.svelte +35 -0
  117. package/dist/fields/SelectCell.svelte.d.ts +15 -0
  118. package/dist/fields/SelectEditor.svelte +350 -0
  119. package/dist/fields/SelectEditor.svelte.d.ts +17 -0
  120. package/dist/fields/SummaryCell.svelte +160 -0
  121. package/dist/fields/SummaryCell.svelte.d.ts +11 -0
  122. package/dist/fields/SvBase.svelte +545 -0
  123. package/dist/fields/SvBase.svelte.d.ts +30 -0
  124. package/dist/fields/SvBaseForm.svelte +295 -0
  125. package/dist/fields/SvBaseForm.svelte.d.ts +22 -0
  126. package/dist/fields/SvBaseViewBar.svelte +644 -0
  127. package/dist/fields/SvBaseViewBar.svelte.d.ts +26 -0
  128. package/dist/fields/UserCell.svelte +39 -0
  129. package/dist/fields/UserCell.svelte.d.ts +9 -0
  130. package/dist/fields/UserFace.svelte +30 -0
  131. package/dist/fields/UserFace.svelte.d.ts +12 -0
  132. package/dist/fields/activity.svelte.d.ts +50 -0
  133. package/dist/fields/activity.svelte.js +110 -0
  134. package/dist/fields/attachments.d.ts +95 -0
  135. package/dist/fields/attachments.js +145 -0
  136. package/dist/fields/automation-outbound.d.ts +46 -0
  137. package/dist/fields/automation-outbound.js +61 -0
  138. package/dist/fields/automation-server.d.ts +53 -0
  139. package/dist/fields/automation-server.js +333 -0
  140. package/dist/fields/automations.svelte.d.ts +208 -0
  141. package/dist/fields/automations.svelte.js +437 -0
  142. package/dist/fields/bar-request.svelte.d.ts +16 -0
  143. package/dist/fields/bar-request.svelte.js +15 -0
  144. package/dist/fields/base-alerts.d.ts +24 -0
  145. package/dist/fields/base-alerts.js +64 -0
  146. package/dist/fields/base-connect.d.ts +82 -0
  147. package/dist/fields/base-connect.js +271 -0
  148. package/dist/fields/base-schema.d.ts +104 -0
  149. package/dist/fields/base-schema.js +266 -0
  150. package/dist/fields/base-sql.d.ts +62 -0
  151. package/dist/fields/base-sql.js +251 -0
  152. package/dist/fields/base-sync.d.ts +142 -0
  153. package/dist/fields/base-sync.js +499 -0
  154. package/dist/fields/base-tables.d.ts +89 -0
  155. package/dist/fields/base-tables.js +349 -0
  156. package/dist/fields/builtin-types.d.ts +13 -0
  157. package/dist/fields/builtin-types.js +773 -0
  158. package/dist/fields/button.d.ts +2 -0
  159. package/dist/fields/button.js +17 -0
  160. package/dist/fields/chart-view.d.ts +35 -0
  161. package/dist/fields/chart-view.js +154 -0
  162. package/dist/fields/column-order.d.ts +6 -0
  163. package/dist/fields/column-order.js +12 -0
  164. package/dist/fields/convert.d.ts +16 -0
  165. package/dist/fields/convert.js +176 -0
  166. package/dist/fields/copy-text.d.ts +13 -0
  167. package/dist/fields/copy-text.js +45 -0
  168. package/dist/fields/editor-request.svelte.d.ts +71 -0
  169. package/dist/fields/editor-request.svelte.js +52 -0
  170. package/dist/fields/field-ops.d.ts +72 -0
  171. package/dist/fields/field-ops.js +334 -0
  172. package/dist/fields/filter-model.d.ts +84 -0
  173. package/dist/fields/filter-model.js +446 -0
  174. package/dist/fields/format.d.ts +109 -0
  175. package/dist/fields/format.js +288 -0
  176. package/dist/fields/formula.d.ts +50 -0
  177. package/dist/fields/formula.js +530 -0
  178. package/dist/fields/grid-access.d.ts +60 -0
  179. package/dist/fields/grid-access.js +120 -0
  180. package/dist/fields/index.d.ts +34 -0
  181. package/dist/fields/index.js +312 -0
  182. package/dist/fields/linked-fields.d.ts +8 -0
  183. package/dist/fields/linked-fields.js +40 -0
  184. package/dist/fields/links.d.ts +10 -0
  185. package/dist/fields/links.js +46 -0
  186. package/dist/fields/name-list.d.ts +16 -0
  187. package/dist/fields/name-list.js +49 -0
  188. package/dist/fields/palette.d.ts +47 -0
  189. package/dist/fields/palette.js +69 -0
  190. package/dist/fields/record-delete.svelte.d.ts +12 -0
  191. package/dist/fields/record-delete.svelte.js +5 -0
  192. package/dist/fields/record-menu.d.ts +9 -0
  193. package/dist/fields/record-menu.js +78 -0
  194. package/dist/fields/registry.svelte.d.ts +176 -0
  195. package/dist/fields/registry.svelte.js +51 -0
  196. package/dist/fields/rich-text.d.ts +23 -0
  197. package/dist/fields/rich-text.js +48 -0
  198. package/dist/fields/row-color-choice.svelte.d.ts +31 -0
  199. package/dist/fields/row-color-choice.svelte.js +25 -0
  200. package/dist/fields/row-colors.d.ts +36 -0
  201. package/dist/fields/row-colors.js +90 -0
  202. package/dist/fields/row-height.d.ts +28 -0
  203. package/dist/fields/row-height.js +50 -0
  204. package/dist/fields/sources.svelte.d.ts +80 -0
  205. package/dist/fields/sources.svelte.js +131 -0
  206. package/dist/fields/summaries.d.ts +18 -0
  207. package/dist/fields/summaries.js +138 -0
  208. package/dist/fields/view-model.d.ts +129 -0
  209. package/dist/fields/view-model.js +492 -0
  210. package/dist/fields/viewbar/ChartPanel.svelte +61 -0
  211. package/dist/fields/viewbar/ChartPanel.svelte.d.ts +9 -0
  212. package/dist/fields/viewbar/ColorPanel.svelte +112 -0
  213. package/dist/fields/viewbar/ColorPanel.svelte.d.ts +9 -0
  214. package/dist/fields/viewbar/ConditionValue.svelte +153 -0
  215. package/dist/fields/viewbar/ConditionValue.svelte.d.ts +15 -0
  216. package/dist/fields/viewbar/FieldIcon.svelte +18 -0
  217. package/dist/fields/viewbar/FieldIcon.svelte.d.ts +9 -0
  218. package/dist/fields/viewbar/FilterPanel.svelte +192 -0
  219. package/dist/fields/viewbar/FilterPanel.svelte.d.ts +13 -0
  220. package/dist/fields/viewbar/GanttPanel.svelte +60 -0
  221. package/dist/fields/viewbar/GanttPanel.svelte.d.ts +10 -0
  222. package/dist/fields/viewbar/HideFieldsPanel.svelte +104 -0
  223. package/dist/fields/viewbar/HideFieldsPanel.svelte.d.ts +7 -0
  224. package/dist/fields/viewbar/SortPanel.svelte +85 -0
  225. package/dist/fields/viewbar/SortPanel.svelte.d.ts +8 -0
  226. package/dist/fields/viewbar/ViewsMenu.svelte +250 -0
  227. package/dist/fields/viewbar/ViewsMenu.svelte.d.ts +15 -0
  228. package/dist/fields/viewbar/panel-focus.d.ts +3 -0
  229. package/dist/fields/viewbar/panel-focus.js +22 -0
  230. package/dist/fields/viewbar/sort-state.d.ts +20 -0
  231. package/dist/fields/viewbar/sort-state.js +24 -0
  232. package/dist/gallery.d.ts +7 -0
  233. package/dist/gallery.js +36 -0
  234. package/dist/gantt/SvGridGantt.svelte +88 -16
  235. package/dist/gantt/gantt-config.d.ts +28 -0
  236. package/dist/index.d.ts +3 -0
  237. package/dist/index.js +6 -0
  238. package/dist/install.js +3 -0
  239. package/dist/license-core.d.ts +3 -3
  240. package/dist/license.js +1 -0
  241. package/dist/node/studio.js +105 -4
  242. package/dist/scheduler-config.d.ts +11 -0
  243. package/dist/sources/introspect-supabase.js +4 -1
  244. package/dist/sources/rest.js +6 -2
  245. package/dist/studio/ui-components.generated.js +93 -1
  246. package/dist/upgrade-prompt.d.ts +1 -1
  247. package/dist/version.d.ts +2 -2
  248. package/dist/version.js +2 -2
  249. package/dist/wc/GridFooter-f5K0aLe_.js +145 -0
  250. package/dist/wc/GridMenus-FtSN_8t9.js +1369 -0
  251. package/dist/wc/PinSplitter-D-qtDhSM.js +132 -0
  252. package/dist/wc/{SvGrid.helpers-zBkDTryR.js → SvGrid.helpers-B2GEKrxS.js} +84 -36
  253. package/dist/wc/{SvGridCellEditor-CzUKEbkI.js → SvGridCellEditor-B_cLJrsp.js} +20 -9
  254. package/dist/wc/conditional-formatting-BXis_oP1.js +189 -0
  255. package/dist/wc/grid-extension.svelte-Bufq4B4C.js +20 -0
  256. package/dist/wc/sv-sheet-element.js +7556 -7617
  257. package/dist/wc/svelte-virtualizer.svelte-ehwOupl-.js +360 -0
  258. package/dist/wc/{validate-43C03DLP.js → validate-BaMO8UPE.js} +1 -1
  259. package/package.json +14 -3
  260. package/dist/designer/assets/GridMenus-CvcASlKv.js +0 -8
  261. package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +0 -1
  262. package/dist/designer/assets/SvGridCellEditor-C7NWcnNC.js +0 -4
  263. package/dist/designer/assets/SvListBox-BlmmzYcU.js +0 -110
  264. package/dist/designer/assets/index-KmSEY7Lb.css +0 -1
  265. package/dist/designer/assets/src-DqHCqori.js +0 -58
  266. package/dist/designer/assets/validate-B71dE1Cs.js +0 -1
  267. package/dist/wc/GridMenus-fWbBUiRH.js +0 -1088
  268. package/dist/wc/svelte-virtualizer.svelte-Dntdh8Ms.js +0 -581
@@ -0,0 +1,309 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The one editor of the number field, in every format (registered as
4
+ * 'fieldNumber'; older currency and percent columns use it too). The box
5
+ * shows the plain number with the field's decimal character, with the
6
+ * currency symbol or percent sign right beside it, aligned as the column
7
+ * aligns its values. A value it cannot take shows why under the cell.
8
+ *
9
+ * It reads what people type the way the field writes it: its own thousands
10
+ * and decimal characters, a pasted symbol or percent sign, "2.5k", "3M",
11
+ * "(5)" for -5. On commit the value is rounded to the field's precision (an
12
+ * integer field to whole numbers); a percent field keeps fractions unless
13
+ * its values are 0 to 100. A value that is not a number, or a negative one
14
+ * where the field allows none, is refused and the box stays open.
15
+ */
16
+ import { portalToBody } from '@svgrid/grid'
17
+ import { onMount } from 'svelte'
18
+ import { fieldPropsOf } from './registry.svelte'
19
+ import { formatDuration, numberEditText, numberFieldShape, numberFromText, numberKind, parseDuration, parseNumberText, roundTo, toNumber, type DurationFormat, type NumberFieldProps } from './format'
20
+
21
+ let {
22
+ value,
23
+ typed = false,
24
+ columnDef,
25
+ onCommit,
26
+ onCancel,
27
+ onCommitAndMove,
28
+ onCommitAndMoveDown,
29
+ }: {
30
+ value: unknown
31
+ typed?: boolean
32
+ columnDef?: Record<string, unknown>
33
+ onCommit: (v?: unknown) => void
34
+ onCancel: () => void
35
+ onCommitAndMove?: (v: unknown, direction: 1 | -1) => void
36
+ onCommitAndMoveDown?: (v: unknown, up?: boolean) => void
37
+ } = $props()
38
+
39
+ const def = $derived((columnDef ?? {}) as { fieldType?: unknown; fieldProps?: Record<string, unknown>; formatter?: unknown })
40
+ const p = $derived(fieldPropsOf(def) as NumberFieldProps)
41
+ const kind = $derived(numberKind(def.fieldType, p))
42
+ const symbol = $derived(kind === 'currency' ? (p.symbol ?? '$') : '')
43
+ const symbolAfter = $derived(p.symbolPosition === 'after')
44
+ // A percent field stores fractions (0.75) unless its values are 0 to 100;
45
+ // the box always shows percent points (75).
46
+ const scale = $derived(numberFieldShape(kind, p).scale)
47
+ // A duration field: seconds, shown and typed as "1:30" (or "1h 30m").
48
+ const isDuration = $derived(def.fieldType === 'duration')
49
+ const durationFormat = $derived(((p as { durationFormat?: unknown }).durationFormat ?? 'h:mm') as DurationFormat)
50
+
51
+ // svelte-ignore state_referenced_locally
52
+ // Typed on the cell: the value is that text, not the cell's number.
53
+ const seeded = typed
54
+ // svelte-ignore state_referenced_locally
55
+ let text = $state(seeded ? keepNumberChars(String(value ?? '')) : initialText(value))
56
+
57
+ function initialText(v: unknown): string {
58
+ const n = toNumber(v)
59
+ if (n == null) return ''
60
+ return def.fieldType === 'duration' ? formatDuration(n, durationFormat) : numberEditText(n * scale, p)
61
+ }
62
+ /** What a typed key may add: digits, separators, a sign, K / M / B, the symbol; for a duration also : h m s. */
63
+ function keepNumberChars(s: string): string {
64
+ return [...s].filter((c) => /[\d.,\s+\-−()%kmbKMB]/.test(c) || (isDuration && /[:hsHS]/.test(c)) || (!!p.symbol && p.symbol.includes(c))).join('')
65
+ }
66
+
67
+ const result = $derived(!text.trim() ? null : isDuration ? parseDuration(text) : numberFromText(text, kind, p))
68
+ const error = $derived(
69
+ text.trim() && result == null
70
+ ? isDuration ? 'Not a duration (1:30, 1h 30m)' : 'Not a number'
71
+ : result != null && result < 0 && p.allowNegative === false
72
+ ? 'Negative numbers are not allowed'
73
+ : null,
74
+ )
75
+ // ---- the spinner: up / down buttons and the Up / Down keys
76
+ // A duration steps a minute (a second when the format shows seconds).
77
+ const step = $derived(
78
+ Number((p as { step?: unknown }).step) > 0 ? Number((p as { step?: unknown }).step) : isDuration ? (durationFormat === 'h:mm' ? 60 : 1) : 1,
79
+ )
80
+ /** Add `dir` steps (ten with Shift) to the number in the box, in the box's own units. */
81
+ function bump(dir: 1 | -1, big = false) {
82
+ if (isDuration) {
83
+ const secs = text.trim() ? parseDuration(text) : 0
84
+ if (secs == null) return
85
+ text = formatDuration(Math.max(0, secs + dir * step * (big ? 10 : 1)), durationFormat)
86
+ return
87
+ }
88
+ const now = text.trim() ? parseNumberText(text, p) : 0
89
+ if (now == null) return
90
+ let next = roundTo(now + dir * step * (big ? 10 : 1), numberFieldShape(kind, p).places)
91
+ if (next < 0 && p.allowNegative === false) next = 0
92
+ text = numberEditText(next, p)
93
+ }
94
+ // Holding a button repeats, after a pause, as a native spinner does.
95
+ let hold: ReturnType<typeof setTimeout> | null = null
96
+ function press(dir: 1 | -1, e: PointerEvent) {
97
+ // mousedown is cancelled too (on the button): that, not this, keeps the
98
+ // focus in the box, so the keys still go to the editor.
99
+ e.preventDefault()
100
+ input?.focus({ preventScroll: true })
101
+ bump(dir, e.shiftKey)
102
+ stopHold()
103
+ const repeat = () => {
104
+ bump(dir, e.shiftKey)
105
+ hold = setTimeout(repeat, 60)
106
+ }
107
+ hold = setTimeout(repeat, 400)
108
+ }
109
+ function stopHold() {
110
+ if (hold) clearTimeout(hold)
111
+ hold = null
112
+ }
113
+
114
+ // The number and its symbol sit together where the column puts values.
115
+ const justify = $derived(
116
+ (def as { align?: string }).align === 'left' ? 'flex-start' : (def as { align?: string }).align === 'center' ? 'center' : 'flex-end',
117
+ )
118
+
119
+ // ---- why a value is refused, under the cell, in the body so the cell
120
+ // cannot clip it
121
+ let box = $state<HTMLElement | null>(null)
122
+ let input = $state<HTMLInputElement | null>(null)
123
+ let errorAt = $state<{ top: number; left: number } | null>(null)
124
+ let shake = $state(false)
125
+ function place() {
126
+ const cell = box?.closest('td') ?? box
127
+ if (!cell) return
128
+ const r = cell.getBoundingClientRect()
129
+ errorAt = { top: r.bottom + 2, left: r.left }
130
+ }
131
+ function takeFocus() {
132
+ if (!input || document.activeElement === input) return
133
+ input.focus({ preventScroll: true })
134
+ if (!seeded) input.select()
135
+ else input.setSelectionRange(text.length, text.length)
136
+ }
137
+ onMount(() => {
138
+ place()
139
+ takeFocus()
140
+ // The double-click that opened the editor can still hand focus back to
141
+ // the grid; take it once more on the next frame.
142
+ const frame = requestAnimationFrame(takeFocus)
143
+ // A press anywhere else ends the edit: a number is kept, text that is
144
+ // not one is dropped rather than leave the cell stuck open. Not on blur:
145
+ // focus also leaves when the window does, and the grid can move it.
146
+ const onDown = (e: PointerEvent) => {
147
+ if (finished || box?.contains(e.target as Node)) return
148
+ if (error) {
149
+ finished = true
150
+ onCancel()
151
+ } else finish('commit')
152
+ }
153
+ window.addEventListener('resize', place)
154
+ window.addEventListener('scroll', place, true)
155
+ document.addEventListener('pointerdown', onDown, true)
156
+ return () => {
157
+ stopHold()
158
+ cancelAnimationFrame(frame)
159
+ window.removeEventListener('resize', place)
160
+ window.removeEventListener('scroll', place, true)
161
+ document.removeEventListener('pointerdown', onDown, true)
162
+ }
163
+ })
164
+
165
+ let finished = false
166
+ function finish(how: 'commit' | 'next' | 'prev' | 'down' | 'up'): boolean {
167
+ if (finished) return true
168
+ if (error) {
169
+ shake = false
170
+ queueMicrotask(() => (shake = true))
171
+ return false
172
+ }
173
+ finished = true
174
+ // Enter moves a row down, as in a text cell; Tab a column across.
175
+ if ((how === 'down' || how === 'up') && onCommitAndMoveDown) onCommitAndMoveDown(result, how === 'up')
176
+ else if ((how === 'next' || how === 'prev') && onCommitAndMove) onCommitAndMove(result, how === 'next' ? 1 : -1)
177
+ else onCommit(result)
178
+ return true
179
+ }
180
+ function onKey(e: KeyboardEvent) {
181
+ if (e.key === 'Enter') finish(e.shiftKey ? 'up' : 'down')
182
+ else if (e.key === 'Tab') finish(e.shiftKey ? 'prev' : 'next')
183
+ else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') bump(e.key === 'ArrowUp' ? 1 : -1, e.shiftKey)
184
+ else if (e.key === 'Escape') {
185
+ finished = true
186
+ onCancel()
187
+ } else {
188
+ // Caret keys move in the box, not between cells.
189
+ e.stopPropagation()
190
+ return
191
+ }
192
+ e.preventDefault()
193
+ e.stopPropagation()
194
+ }
195
+ function onBeforeInput(e: InputEvent) {
196
+ // Typed letters that cannot be part of a number never reach the box;
197
+ // a paste is read as a whole on commit.
198
+ if (e.inputType === 'insertText' && e.data && keepNumberChars(e.data) !== e.data) e.preventDefault()
199
+ }
200
+ const inputValue = (e: Event) => (e.currentTarget as HTMLInputElement).value
201
+ </script>
202
+
203
+ <!-- Presses inside the editor (the spinner, placing the caret) stay here: the
204
+ cell's own handlers would move focus to the grid and end the typing. -->
205
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
206
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
207
+ <span
208
+ class="sv-fne"
209
+ class:is-invalid={!!error}
210
+ class:is-shake={shake}
211
+ style:justify-content={justify}
212
+ bind:this={box}
213
+ onanimationend={() => (shake = false)}
214
+ onpointerdown={(e) => e.stopPropagation()}
215
+ onmousedown={(e) => e.stopPropagation()}
216
+ onclick={(e) => e.stopPropagation()}
217
+ ondblclick={(e) => e.stopPropagation()}
218
+ >
219
+ {#if symbol && !symbolAfter}<span class="sv-fne-affix" aria-hidden="true">{symbol}</span>{/if}
220
+ <!-- The input takes the width of its text (a hidden copy sizes it), so
221
+ the symbol sits right against the number. -->
222
+ <span class="sv-fne-sizer" data-text={text}>
223
+ <input
224
+ bind:this={input}
225
+ class="sv-fne-input"
226
+ size="1"
227
+ inputmode="decimal"
228
+ autocomplete="off"
229
+ spellcheck="false"
230
+ aria-label={isDuration ? `Duration (${durationFormat})` : kind === 'currency' ? `Amount in ${symbol || 'money'}` : kind === 'percent' ? 'Percent' : 'Number'}
231
+ aria-invalid={error ? 'true' : undefined}
232
+ value={text}
233
+ oninput={(e) => (text = inputValue(e))}
234
+ onbeforeinput={onBeforeInput}
235
+ onkeydown={onKey}
236
+ />
237
+ </span>
238
+ {#if symbol && symbolAfter}<span class="sv-fne-affix" aria-hidden="true">{symbol}</span>{/if}
239
+ {#if kind === 'percent'}<span class="sv-fne-affix" aria-hidden="true">%</span>{/if}
240
+ <!-- Pointer only: Up / Down do the same from the keyboard, and focus stays
241
+ in the box. -->
242
+ <span class="sv-fne-spin">
243
+ <button type="button" tabindex="-1" aria-label={`Increase by ${step}`} onmousedown={(e) => e.preventDefault()} onpointerdown={(e) => press(1, e)} onpointerup={stopHold} onpointerleave={stopHold}>
244
+ <svg viewBox="0 0 10 6" width="8" height="5" aria-hidden="true"><path d="M1 5l4-4 4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
245
+ </button>
246
+ <button type="button" tabindex="-1" aria-label={`Decrease by ${step}`} onmousedown={(e) => e.preventDefault()} onpointerdown={(e) => press(-1, e)} onpointerup={stopHold} onpointerleave={stopHold}>
247
+ <svg viewBox="0 0 10 6" width="8" height="5" aria-hidden="true"><path d="M1 1l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" /></svg>
248
+ </button>
249
+ </span>
250
+ </span>
251
+
252
+ {#if errorAt && error}
253
+ <div
254
+ class="sv-fne-error"
255
+ role="alert"
256
+ use:portalToBody
257
+ style:top={`${errorAt.top}px`}
258
+ style:left={`${errorAt.left}px`}
259
+ >
260
+ <svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path d="M12 3 2 20h20L12 3zM12 10v4M12 17v.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
261
+ {error}
262
+ </div>
263
+ {/if}
264
+
265
+ <style>
266
+ /* The whole cell, edge to edge, with the input only as wide as its text,
267
+ so the symbol stays beside the number: an input's default width (about
268
+ 20 characters) is wider than a number column. The cell's own gutter
269
+ (--sg-cell-px) on the left, the spinner at the right edge with the
270
+ number right-aligned against it. Accent outline while focused, as the
271
+ built-in editors have; red when invalid. */
272
+ .sv-fne {
273
+ position: absolute; inset: 0; z-index: 1;
274
+ display: flex; align-items: center; gap: 1px; padding: 0 22px 0 var(--sg-cell-px, 7px); box-sizing: border-box; overflow: hidden;
275
+ background: var(--sg-input-bg, var(--sg-bg, #fff));
276
+ }
277
+ .sv-fne:focus-within { box-shadow: inset 0 0 0 2px var(--sg-accent, #2563eb); }
278
+ .sv-fne.is-invalid { box-shadow: inset 0 0 0 2px var(--sg-danger, #dc2626); }
279
+ .sv-fne.is-shake { animation: sv-fne-shake 0.24s; }
280
+ @keyframes sv-fne-shake { 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
281
+ @media (prefers-reduced-motion: reduce) { .sv-fne.is-shake { animation: none; } }
282
+ .sv-fne-affix { flex: none; color: var(--sg-fg, #0f172a); font-variant-numeric: tabular-nums; }
283
+ /* Only the hidden copy of the text (plus a pixel for the caret) sets the
284
+ width; the input lies over it. An input's own minimum width (even at
285
+ size=1) would leave a gap after the symbol. */
286
+ .sv-fne-sizer { position: relative; flex: 0 1 auto; min-width: 1ch; max-width: 100%; height: 100%; display: flex; align-items: center; }
287
+ .sv-fne-sizer::after {
288
+ content: attr(data-text); padding-inline-start: 1px; visibility: hidden; white-space: pre; overflow: hidden;
289
+ font: inherit; font-variant-numeric: tabular-nums;
290
+ }
291
+ .sv-fne-input {
292
+ position: absolute; inset: 0; width: 100%; min-width: 0; height: 100%; padding: 0; margin: 0; border: 0; outline: none;
293
+ background: none; box-shadow: none; color: var(--sg-fg, #0f172a); font: inherit; text-align: end; font-variant-numeric: tabular-nums;
294
+ }
295
+ .sv-fne-spin {
296
+ position: absolute; top: 0; bottom: 0; inset-inline-end: 3px;
297
+ display: flex; flex-direction: column; justify-content: center; gap: 1px;
298
+ }
299
+ .sv-fne-spin button {
300
+ display: flex; align-items: center; justify-content: center; width: 16px; height: 11px; padding: 0; border: 0;
301
+ border-radius: 3px; background: none; color: var(--sg-muted, #64748b); cursor: pointer;
302
+ }
303
+ .sv-fne-spin button:hover { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
304
+ .sv-fne-error {
305
+ position: fixed; z-index: 2147483646; display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px;
306
+ border-radius: var(--sg-radius, 6px); background: var(--sg-danger, #dc2626); color: #fff;
307
+ font-size: 12px; white-space: nowrap; pointer-events: none;
308
+ }
309
+ </style>
@@ -0,0 +1,12 @@
1
+ type $$ComponentProps = {
2
+ value: unknown;
3
+ typed?: boolean;
4
+ columnDef?: Record<string, unknown>;
5
+ onCommit: (v?: unknown) => void;
6
+ onCancel: () => void;
7
+ onCommitAndMove?: (v: unknown, direction: 1 | -1) => void;
8
+ onCommitAndMoveDown?: (v: unknown, up?: boolean) => void;
9
+ };
10
+ declare const NumberEditor: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type NumberEditor = ReturnType<typeof NumberEditor>;
12
+ export default NumberEditor;
@@ -0,0 +1,117 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The select options list in the field editor: a color and a name per
4
+ * option, reorder with the arrows, remove, add, sort A to Z. A swatch opens
5
+ * the 40-color palette (ten hues, four strengths) under its option.
6
+ */
7
+ import { OPTION_COLORS, OPTION_CYCLE } from './palette'
8
+
9
+ type Option = { value: string; color?: string }
10
+ let { options, onChange }: { options: Option[]; onChange: (next: Option[]) => void } = $props()
11
+ let paletteFor = $state<number | null>(null)
12
+
13
+ // Kept out of the markup: AGENTS.md keeps `as` casts in <script>.
14
+ const inputValue = (e: Event) => (e.currentTarget as HTMLInputElement).value
15
+
16
+ function set(i: number, patch: Partial<Option>) {
17
+ onChange(options.map((o, j) => (j === i ? { ...o, ...patch } : o)))
18
+ }
19
+ function move(i: number, d: number) {
20
+ const j = i + d
21
+ if (j < 0 || j >= options.length) return
22
+ const next = options.slice()
23
+ ;[next[i], next[j]] = [next[j]!, next[i]!]
24
+ onChange(next)
25
+ }
26
+ function add() {
27
+ onChange([...options, { value: '', color: OPTION_CYCLE[options.length % OPTION_CYCLE.length] }])
28
+ queueMicrotask(() => {
29
+ const inputs = document.querySelectorAll<HTMLInputElement>('.sv-fopt-name')
30
+ inputs[inputs.length - 1]?.focus()
31
+ })
32
+ }
33
+ function sortAZ() {
34
+ onChange(options.slice().sort((a, b) => a.value.localeCompare(b.value, undefined, { sensitivity: 'base', numeric: true })))
35
+ }
36
+ </script>
37
+
38
+ <ul class="sv-fopt" aria-label="Options">
39
+ {#each options as o, i (i)}
40
+ <li class="sv-fopt-row">
41
+ <button
42
+ type="button"
43
+ class="sv-fopt-swatch"
44
+ style:background={o.color ?? '#cccccc'}
45
+ aria-label={`Color of ${o.value || 'option ' + (i + 1)}`}
46
+ aria-expanded={paletteFor === i}
47
+ onclick={() => (paletteFor = paletteFor === i ? null : i)}
48
+ ></button>
49
+ <input
50
+ class="sv-fopt-name"
51
+ value={o.value}
52
+ placeholder="Option name"
53
+ aria-label={`Option ${i + 1}`}
54
+ oninput={(e) => set(i, { value: inputValue(e) })}
55
+ />
56
+ <button type="button" class="sv-fopt-icon" aria-label="Move up" disabled={i === 0} onclick={() => move(i, -1)}>
57
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 15l6-6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
58
+ </button>
59
+ <button type="button" class="sv-fopt-icon" aria-label="Move down" disabled={i === options.length - 1} onclick={() => move(i, 1)}>
60
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
61
+ </button>
62
+ <button type="button" class="sv-fopt-icon" aria-label={`Remove ${o.value || 'option'}`} onclick={() => onChange(options.filter((_, j) => j !== i))}>
63
+ <svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
64
+ </button>
65
+ </li>
66
+ {#if paletteFor === i}
67
+ <li><div class="sv-fopt-palette" role="radiogroup" aria-label={`Color of ${o.value || 'option ' + (i + 1)}`}>
68
+ {#each OPTION_COLORS as c (c.value)}
69
+ <button
70
+ type="button"
71
+ role="radio"
72
+ aria-checked={o.color === c.value}
73
+ aria-label={c.label}
74
+ title={c.label}
75
+ class:is-on={o.color === c.value}
76
+ style:background={c.value}
77
+ onclick={() => {
78
+ set(i, { color: c.value })
79
+ paletteFor = null
80
+ }}
81
+ ></button>
82
+ {/each}
83
+ </div></li>
84
+ {/if}
85
+ {/each}
86
+ </ul>
87
+ <div class="sv-fopt-actions">
88
+ <button type="button" class="sv-fopt-add" onclick={add}>+ Add option</button>
89
+ {#if options.length > 1}<button type="button" class="sv-fopt-add" onclick={sortAZ}>Sort A to Z</button>{/if}
90
+ </div>
91
+
92
+ <style>
93
+ .sv-fopt { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; max-height: 260px; overflow: auto; }
94
+ .sv-fopt-row { display: flex; align-items: center; gap: 6px; }
95
+ .sv-fopt-swatch { flex: none; width: 18px; height: 18px; border-radius: 999px; border: 2px solid var(--sg-bg, #fff); box-shadow: 0 0 0 1px var(--sg-border, #e2e8f0); cursor: pointer; }
96
+ .sv-fopt-swatch:focus-visible { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: 2px; }
97
+ .sv-fopt-name {
98
+ flex: 1; min-width: 0; height: 30px; padding: 0 8px; font: inherit; font-size: 13px;
99
+ border: 1px solid var(--sg-input-border, var(--sg-border, #e2e8f0)); border-radius: var(--sg-radius, 6px);
100
+ background: var(--sg-input-bg, var(--sg-bg, #fff)); color: var(--sg-fg, #0f172a);
101
+ }
102
+ .sv-fopt-name::placeholder { color: var(--sg-muted, #94a3b8); }
103
+ .sv-fopt-name:focus { outline: none; border-color: var(--sg-accent, #2563eb); box-shadow: 0 0 0 3px color-mix(in srgb, var(--sg-accent, #2563eb) 22%, transparent); }
104
+ .sv-fopt-icon { flex: none; display: inline-flex; padding: 4px; border: 0; border-radius: 4px; background: none; color: var(--sg-muted, #64748b); cursor: pointer; }
105
+ .sv-fopt-icon:disabled { opacity: 0.35; cursor: default; }
106
+ .sv-fopt-icon:not(:disabled):hover { color: var(--sg-fg, #0f172a); }
107
+ .sv-fopt-palette {
108
+ display: grid; grid-template-columns: repeat(10, 20px); gap: 5px; padding: 8px; margin-inline-start: 24px;
109
+ border-radius: var(--sg-radius, 6px); border: 1px solid var(--sg-border, #e2e8f0); background: var(--sg-bg, #fff); width: max-content;
110
+ }
111
+ .sv-fopt-palette button { width: 20px; height: 20px; padding: 0; border-radius: 4px; border: 0; cursor: pointer; }
112
+ .sv-fopt-palette button.is-on { box-shadow: 0 0 0 2px var(--sg-bg, #fff), 0 0 0 4px var(--sg-fg, #0f172a); }
113
+ .sv-fopt-palette button:focus-visible { outline: 2px solid var(--sg-accent, #2563eb); outline-offset: 2px; }
114
+ .sv-fopt-actions { display: flex; gap: 14px; margin-top: 6px; }
115
+ .sv-fopt-add { padding: 4px 0; border: 0; background: none; color: var(--sg-muted, #64748b); font: inherit; font-size: 13px; cursor: pointer; }
116
+ .sv-fopt-add:hover { color: var(--sg-fg, #0f172a); }
117
+ </style>
@@ -0,0 +1,11 @@
1
+ type Option = {
2
+ value: string;
3
+ color?: string;
4
+ };
5
+ type $$ComponentProps = {
6
+ options: Option[];
7
+ onChange: (next: Option[]) => void;
8
+ };
9
+ declare const OptionsEditor: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type OptionsEditor = ReturnType<typeof OptionsEditor>;
11
+ export default OptionsEditor;
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ /** Percent cells with `showBar`: the number and a bar filled to it (0 to 100%). */
3
+ import type { FieldCellProps } from './registry.svelte'
4
+ import { formatPercent, toNumber, type NumberFormatProps } from './format'
5
+
6
+ let { value, props }: FieldCellProps<NumberFormatProps & { points?: boolean }> = $props()
7
+ const n = $derived(toNumber(value))
8
+ const share = $derived(n == null ? 0 : Math.max(0, Math.min(1, props.points ? n / 100 : n)))
9
+ </script>
10
+
11
+ <span class="sv-fpc">
12
+ <span class="sv-fpc-bar" aria-hidden="true"><span style:width={`${share * 100}%`}></span></span>
13
+ <span class="sv-fpc-text">{n == null ? '' : formatPercent(n, props)}</span>
14
+ </span>
15
+
16
+ <style>
17
+ .sv-fpc { display: flex; align-items: center; gap: 8px; width: 100%; }
18
+ .sv-fpc-bar { flex: 1; min-width: 24px; height: 6px; border-radius: 3px; background: var(--sg-row-hover-bg, #f1f5f9); overflow: hidden; }
19
+ .sv-fpc-bar > span { display: block; height: 100%; border-radius: 3px; background: var(--sg-muted, #64748b); }
20
+ .sv-fpc-text { flex: none; font-variant-numeric: tabular-nums; }
21
+ </style>
@@ -0,0 +1,8 @@
1
+ /** Percent cells with `showBar`: the number and a bar filled to it (0 to 100%). */
2
+ import type { FieldCellProps } from './registry.svelte.js';
3
+ import { type NumberFormatProps } from './format.js';
4
+ declare const PercentCell: import("svelte").Component<FieldCellProps<NumberFormatProps & {
5
+ points?: boolean;
6
+ }>, {}, "">;
7
+ type PercentCell = ReturnType<typeof PercentCell>;
8
+ export default PercentCell;