@svgrid/enterprise 3.1.8 → 3.1.9

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-Obk8sI77.js → index-BPU2KNc5.js} +307 -307
  34. package/dist/designer/assets/index-BmvG99kR.css +1 -0
  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,492 @@
1
+ import { compileFieldFilter } from './filter-model.js';
2
+ import { fieldColumns, primaryIdOf, writeCell } from './grid-access.js';
3
+ import { fieldPropsOf, originalColumn } from './registry.svelte.js';
4
+ import { getFieldUsers, getLinkedTable } from './sources.svelte.js';
5
+ import { chooseRowColor, chosenRowColor, clearRowColorChoice } from './row-color-choice.svelte.js';
6
+ import { newRowValues, recordCopyValues } from './field-ops.js';
7
+ import { openRecord } from './editor-request.svelte.js';
8
+ import { rowColorOf, rowMarkColor } from './row-colors.js';
9
+ import { recordHeightOf } from './row-height.js';
10
+ import { askDeleteRecord } from './record-delete.svelte.js';
11
+ import { commentsOf } from './activity.svelte.js';
12
+ export const VIEW_TYPES = [
13
+ { type: 'grid', label: 'Grid', icon: 'M3 5h18v14H3zM3 10h18M3 15h18M9 5v14' },
14
+ { type: 'kanban', label: 'Kanban', icon: 'M4 4h4v16H4zM10 4h4v10h-4zM16 4h4v13h-4z' },
15
+ { type: 'calendar', label: 'Calendar', icon: 'M4 6h16v14H4zM4 10h16M8 3v4M16 3v4' },
16
+ { type: 'gantt', label: 'Gantt', icon: 'M4 5h9M7 10h11M10 15h8M4 20h16' },
17
+ { type: 'gallery', label: 'Gallery', icon: 'M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z' },
18
+ { type: 'chart', label: 'Chart', icon: 'M4 20h16M6 20V11M11 20V5M16 20v-7' },
19
+ { type: 'form', label: 'Form', icon: 'M6 3h12v18H6zM9 8h6M9 12h6M9 16h3' },
20
+ ];
21
+ export const viewTypeInfo = (t) => VIEW_TYPES.find((v) => v.type === t) ?? VIEW_TYPES[0];
22
+ let seq = 0;
23
+ export const newViewId = () => `view-${Date.now().toString(36)}-${(seq += 1)}`;
24
+ /** Sort direction labels that fit the field ("1 → 9" for a number, "A → Z" for text). */
25
+ export function sortLabels(def) {
26
+ const t = def?.fieldType;
27
+ const dt = def?.cellDataType;
28
+ if (t === 'checkbox' || dt === 'boolean')
29
+ return ['☐ → ☑', '☑ → ☐'];
30
+ // A select sorts in its options' order.
31
+ if (t === 'singleSelect')
32
+ return ['first → last', 'last → first'];
33
+ if (dt === 'number' ||
34
+ dt === 'date' ||
35
+ dt === 'dateString' ||
36
+ ['number', 'currency', 'percent', 'rating', 'duration', 'autoNumber', 'count', 'date', 'createdTime', 'lastModifiedTime'].includes(t ?? ''))
37
+ return ['1 → 9', '9 → 1'];
38
+ return ['A → Z', 'Z → A'];
39
+ }
40
+ // ---- Row colors ------------------------------------------------------------
41
+ /** The rows-colored setting as the grid now has it. */
42
+ export function colorSettingOf(ctrl, saved) {
43
+ const cfg = rowColorOf(ctrl);
44
+ // Conditions still being written color nothing yet, and are still the setting.
45
+ if (!cfg)
46
+ return saved?.mode === 'conditions' ? saved : { mode: 'none' };
47
+ if ('field' in cfg)
48
+ return { mode: 'field', field: cfg.field };
49
+ return saved?.mode === 'conditions' ? saved : { mode: 'none' };
50
+ }
51
+ /**
52
+ * The grid's `rowColor` for a setting, painted where the `rowColor` prop says
53
+ * (`target`). Conditions are tested as the filter tests them: on the value the
54
+ * cell shows (computed for a formula, lookup, rollup or count), and one still
55
+ * missing its value is left out.
56
+ */
57
+ export function rowColorFor(ctrl, setting, linkNames) {
58
+ if (setting.mode === 'none')
59
+ return null;
60
+ const target = ctrl?.props?.rowColor?.target;
61
+ const where = target ? { target } : {};
62
+ if (setting.mode === 'field')
63
+ return { field: setting.field, ...where };
64
+ const cols = new Map(fieldColumns(ctrl).map((c) => [c.id, c]));
65
+ const valueOf = (row, id) => {
66
+ const col = cols.get(id);
67
+ const fn = col.def.fieldFn;
68
+ return fn ? fn(row) : row?.[col.field];
69
+ };
70
+ const rules = setting.rules
71
+ .map((r) => {
72
+ if (!cols.has(r.condition.column))
73
+ return null;
74
+ const test = compileFieldFilter({ kind: 'fieldFilter', conjunction: 'and', conditions: [r.condition] }, valueOf, linkNames);
75
+ return test ? { color: r.color, when: (row) => test(row) } : null;
76
+ })
77
+ .filter((r) => !!r);
78
+ return rules.length ? { rules, ...where } : null;
79
+ }
80
+ export function applyColor(ctrl, setting, linkNames) {
81
+ chooseRowColor(ctrl, rowColorFor(ctrl, setting, linkNames));
82
+ }
83
+ // ---- Kanban, calendar, Gantt -----------------------------------------------
84
+ /**
85
+ * A row's record id. A view may hand back a copy of the row (a Gantt merges
86
+ * its pending edits into one), which the api cannot find by identity: the
87
+ * grid's own getRowId reads the id from the row's values.
88
+ */
89
+ function recordId(ctrl, api, row) {
90
+ const own = ctrl?.props?.getRowId;
91
+ if (own)
92
+ return String(own(row, -1));
93
+ const id = api.getRowId(row);
94
+ return id == null ? String(row?.id ?? '') : String(id);
95
+ }
96
+ /** Lanes for a Kanban stacked by a field: an "Uncategorized" lane, then one per option or person. */
97
+ export function lanesFor(column) {
98
+ const lanes = [{ id: '', title: 'Uncategorized', hideWhenEmpty: true }];
99
+ if (column.fieldType === 'user') {
100
+ for (const u of getFieldUsers())
101
+ lanes.push({ id: u.id, title: u.name });
102
+ return lanes;
103
+ }
104
+ const p = fieldPropsOf(originalColumn(column.def));
105
+ for (const o of p.options ?? [])
106
+ lanes.push({ id: o.value, title: o.value, ...(o.color ? { color: o.color } : {}) });
107
+ return lanes;
108
+ }
109
+ /** Fields a Kanban can stack by. */
110
+ export const stackableColumns = (ctrl) => fieldColumns(ctrl).filter((c) => c.fieldType === 'singleSelect' || (c.fieldType === 'user' && !c.def.fieldProps?.multiple));
111
+ /** Fields a calendar or Gantt can place records by. */
112
+ export const dateColumns = (ctrl) => fieldColumns(ctrl).filter((c) => c.fieldType === 'date' || c.def.cellDataType === 'date' || c.def.cellDataType === 'dateString');
113
+ /** Fields a Gantt can draw as progress: a number shown as a percent. */
114
+ export const progressColumns = (ctrl) => fieldColumns(ctrl).filter((c) => c.fieldType === 'percent' || (c.fieldType === 'number' && fieldPropsOf(originalColumn(c.def)).format === 'percent'));
115
+ /** Link fields to the same table: what a record depends on, for a Gantt's arrows. */
116
+ export const dependencyColumns = (ctrl) => fieldColumns(ctrl).filter((c) => {
117
+ if (c.fieldType !== 'link')
118
+ return false;
119
+ const t = getLinkedTable(fieldPropsOf(originalColumn(c.def)).table);
120
+ if (!t)
121
+ return false;
122
+ const rows = typeof t.rows === 'function' ? t.rows() : t.rows;
123
+ return rows === ctrl.internalData || (rows.length > 0 && rows[0] === ctrl.internalData?.[0]);
124
+ });
125
+ /**
126
+ * Re-apply a calendar or Gantt view after its settings changed (its fields,
127
+ * progress, links or colors), keeping the grid's state.
128
+ */
129
+ export function applyGanttSettings(ctrl, api, view, primaryField) {
130
+ const id = primaryIdOf(ctrl);
131
+ const primary = primaryField ?? fieldColumns(ctrl).find((c) => c.id === id)?.field;
132
+ if (view.type === 'gantt')
133
+ api.setOption('gantt', ganttConfig(ctrl, api, view, primary));
134
+ else if (view.type === 'calendar')
135
+ api.setOption('scheduler', dateConfig(ctrl, api, view, primary));
136
+ }
137
+ /** Fields a gallery can take a cover from. */
138
+ export const coverColumns = (ctrl) => fieldColumns(ctrl).filter((c) => c.fieldType === 'attachment');
139
+ export function boardConfig(ctrl, api, view) {
140
+ const cols = fieldColumns(ctrl);
141
+ const col = cols.find((c) => c.id === view.stackBy);
142
+ if (!col)
143
+ return undefined;
144
+ const primary = cols.find((c) => c.id === primaryIdOf(ctrl));
145
+ return {
146
+ groupBy: col.field,
147
+ lanes: lanesFor(col),
148
+ // The card: the primary field as its title, then the next three fields
149
+ // the view shows (Hide fields takes one off the cards too).
150
+ ...(primary ? { titleField: primary.field } : {}),
151
+ cardFields: cols.filter((c) => c.visible && c !== primary && c !== col).slice(0, 3).map((c) => c.field),
152
+ // The view bar has the search.
153
+ searchable: false,
154
+ // A card dropped in another lane changes the field, through the grid's
155
+ // edit path (undo, validation, onCellValueChange).
156
+ onCardMove: (e) => {
157
+ if (e.toLane === e.fromLane)
158
+ return;
159
+ writeCell(ctrl, recordId(ctrl, api, e.row), col.id, e.toLane === '' ? null : e.toLane);
160
+ },
161
+ // "+ Add a card" in a lane: a record already in that lane, named by the
162
+ // primary field, made the way "+ Add row" makes one.
163
+ // Double-click or F2 on a card opens the record, as a row's expand button does.
164
+ onCardEdit: (row) => openRecord({ ctrl, rowId: recordId(ctrl, api, row) }),
165
+ // The record's discussion: its comment count on the card, the badge opens it.
166
+ commentCount: (row) => commentsOf(ctrl, recordId(ctrl, api, row)).length,
167
+ onCommentsOpen: (row) => openRecord({ ctrl, rowId: recordId(ctrl, api, row), focus: 'comment' }),
168
+ cellGrid: cardGrid(ctrl),
169
+ // Right-click on a card: the record's menu, as in the grid.
170
+ cardMenu: (row, menu) => {
171
+ const editable = !!ctrl.editingEnabled;
172
+ return [
173
+ { label: 'Expand record', onSelect: () => openRecord({ ctrl, rowId: recordId(ctrl, api, row) }) },
174
+ { label: 'Comment', onSelect: () => openRecord({ ctrl, rowId: recordId(ctrl, api, row), focus: 'comment' }) },
175
+ ...(editable
176
+ ? [
177
+ { label: 'Move to', children: menu.lanes.map((l) => ({ label: l.title ?? (l.id || 'Empty'), onSelect: () => menu.moveTo(l.id) })) },
178
+ { label: 'Duplicate record', onSelect: () => duplicateRecord(ctrl, api, row) },
179
+ { separator: true },
180
+ { label: 'Delete record', onSelect: () => askDeleteRecord({ ctrl, api, row }) },
181
+ ]
182
+ : []),
183
+ ];
184
+ },
185
+ composer: true,
186
+ onCardAdd: (laneId, title) => {
187
+ const primary = fieldColumns(ctrl).find((c) => c.id === primaryIdOf(ctrl));
188
+ const cfg = ctrl.props.rowAdder;
189
+ const defaults = cfg && typeof cfg === 'object' && typeof cfg.defaults === 'function' ? cfg.defaults() : {};
190
+ const row = newRowValues(ctrl, {
191
+ ...defaults,
192
+ [col.field]: laneId === '' ? null : laneId,
193
+ ...(primary && title ? { [primary.field]: title } : {}),
194
+ });
195
+ api.addRow(row, 'bottom');
196
+ ctrl.props.onRowAdd?.({ row });
197
+ },
198
+ };
199
+ }
200
+ /**
201
+ * The grid's `gallery` for a gallery view: the cover field, the primary field
202
+ * as the title, then the fields the grid shows, in its order. A card opens
203
+ * its record. Built again when fields are hidden or moved.
204
+ */
205
+ export function galleryConfig(ctrl, api, view) {
206
+ const cols = fieldColumns(ctrl);
207
+ const primary = cols.find((c) => c.id === primaryIdOf(ctrl)) ?? cols.find((c) => c.visible);
208
+ const cover = cols.find((c) => c.id === view.coverField);
209
+ return {
210
+ ...(cover ? { coverField: cover.field } : {}),
211
+ ...(primary ? { titleField: primary.field } : {}),
212
+ cardFields: cols.filter((c) => c.visible && c !== primary && c !== cover).map((c) => c.field),
213
+ // The bar has its own search; the grid's would be a second box.
214
+ searchable: false,
215
+ onCardClick: (row) => openRecord({ ctrl, rowId: recordId(ctrl, api, row) }),
216
+ cellGrid: cardGrid(ctrl),
217
+ };
218
+ }
219
+ /**
220
+ * The grid as a field cell on a card sees it: every method and prop of the
221
+ * grid, with the card's width as the column's. A custom field type's cell
222
+ * can open its record or read the grid's props from a card as from a row,
223
+ * and a button runs its automation. A card is read, not edited (the record
224
+ * it opens is), so the grid says editing is off: no stars to click, no drop.
225
+ */
226
+ export function cardGrid(ctrl) {
227
+ return (width) => new Proxy(ctrl, {
228
+ get: (t, key) => {
229
+ if (key === 'getColumnWidth')
230
+ return () => width;
231
+ if (key === 'editingEnabled')
232
+ return false;
233
+ // Read with the grid itself as `this`: its state lives in private fields.
234
+ const v = Reflect.get(t, key, t);
235
+ return typeof v === 'function' ? v.bind(t) : v;
236
+ },
237
+ });
238
+ }
239
+ /** A date field without a time holds days. */
240
+ const holdsDays = (c) => (c.fieldType === 'date' && !c.def.fieldProps?.time) || c.def.cellDataType === 'dateString';
241
+ /** A moved date, written the way the field keeps it: 'YYYY-MM-DD' for days, else an ISO time. */
242
+ function dateValue(c, d) {
243
+ if (!holdsDays(c))
244
+ return d.toISOString();
245
+ const p = (n) => String(n).padStart(2, '0');
246
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
247
+ }
248
+ /**
249
+ * A view colored by a select field colors its events the same way: the options
250
+ * become the calendar's color legend, which also hides and shows them.
251
+ */
252
+ function eventColors(cols, color) {
253
+ if (color?.mode !== 'field')
254
+ return {};
255
+ const col = cols.find((c) => c.field === color.field);
256
+ if (!col || col.fieldType !== 'singleSelect')
257
+ return {};
258
+ const calendars = lanesFor(col).filter((l) => l.id !== '' && l.color).map(({ id, title, color }) => ({ id, title, color }));
259
+ return calendars.length ? { calendars, calendarField: col.field } : {};
260
+ }
261
+ function dateConfig(ctrl, api, view, primaryField) {
262
+ const cols = fieldColumns(ctrl);
263
+ const start = cols.find((c) => c.id === view.dateField);
264
+ if (!start)
265
+ return undefined;
266
+ const end = cols.find((c) => c.id === view.endField);
267
+ // A record dragged to another day changes its dates, through the grid's edit
268
+ // path. The calendar's ends are exclusive; a date-only field keeps the last day.
269
+ const move = (e) => {
270
+ const id = recordId(ctrl, api, e.row);
271
+ writeCell(ctrl, id, start.id, dateValue(start, e.start));
272
+ if (end)
273
+ writeCell(ctrl, id, end.id, e.end.getTime() > e.start.getTime() ? lastDayValue(end, e.end) : dateValue(end, e.start));
274
+ };
275
+ const editable = gridEditable(ctrl);
276
+ return {
277
+ searchable: false,
278
+ editable,
279
+ history: editable,
280
+ tooltip: true,
281
+ // An event opens its record, as a card or a bar does.
282
+ onEventOpen: (row) => openRecord({ ctrl, rowId: recordId(ctrl, api, row) }),
283
+ onEventDelete: (row) => deleteRecord(ctrl, api, row),
284
+ startField: start.field,
285
+ ...(end ? { endField: end.field } : {}),
286
+ ...(primaryField ? { titleField: primaryField } : {}),
287
+ ...(holdsDays(start) ? { allDay: true } : {}),
288
+ ...eventColors(cols, view.color),
289
+ onEventMove: move,
290
+ // One date field: a record has no end to change, so its events only move.
291
+ ...(end ? { onEventResize: move } : { resizable: false }),
292
+ // A day (or range) picked on the calendar: a record on it, opened to fill in.
293
+ onEventAdd: (from, to) => {
294
+ const cfg = ctrl.props.rowAdder;
295
+ const defaults = cfg && typeof cfg === 'object' && typeof cfg.defaults === 'function' ? cfg.defaults() : {};
296
+ const row = newRowValues(ctrl, {
297
+ ...defaults,
298
+ [start.field]: dateValue(start, from),
299
+ ...(end ? { [end.field]: to.getTime() > from.getTime() ? lastDayValue(end, to) : dateValue(end, from) } : {}),
300
+ });
301
+ api.addRow(row, 'bottom');
302
+ ctrl.props.onRowAdd?.({ row });
303
+ queueMicrotask(() => openRecord({ ctrl, rowId: recordId(ctrl, api, row), editPrimary: true }));
304
+ },
305
+ };
306
+ }
307
+ /** Whether the grid lets people edit (the base's `editable`). */
308
+ const gridEditable = (ctrl) => ctrl.props.editable !== false && !!(ctrl.props.editable ?? ctrl.props.enableInlineEditing);
309
+ /** An exclusive end written back: a date-only field keeps the last day (the day before). */
310
+ function lastDayValue(c, d) {
311
+ return holdsDays(c) ? dateValue(c, new Date(d.getFullYear(), d.getMonth(), d.getDate() - 1)) : dateValue(c, d);
312
+ }
313
+ /** A copy of a record a view handed over, right after it, made as "+ Add row" makes one. */
314
+ function duplicateRecord(ctrl, api, row) {
315
+ const id = recordId(ctrl, api, row);
316
+ const data = ctrl.internalData;
317
+ const at = data.findIndex((r) => recordId(ctrl, api, r) === id);
318
+ if (at < 0)
319
+ return;
320
+ const next = newRowValues(ctrl, recordCopyValues(ctrl, data[at]));
321
+ api.addRow(next, at + 1);
322
+ ctrl.props.onRowAdd?.({ row: next });
323
+ }
324
+ /** Remove a record a view handed over (a copy, perhaps), and report it as the grid's delete does. */
325
+ export function deleteRecord(ctrl, api, row) {
326
+ const id = recordId(ctrl, api, row);
327
+ const data = ctrl.internalData;
328
+ const at = data.findIndex((r) => recordId(ctrl, api, r) === id);
329
+ if (at < 0)
330
+ return;
331
+ const gone = data[at];
332
+ api.removeRows([at]);
333
+ ctrl.props.onRowsDelete?.({ rows: [gone], ids: [id], data: ctrl.internalData });
334
+ }
335
+ /**
336
+ * A Gantt: the calendar's dates and moves, and a task table beside the bars
337
+ * that holds the record's name, its dates and its length in working days.
338
+ * Every field would fill the table and push the chart out of view.
339
+ */
340
+ // The zoom a grid's Gantt was last set to, per grid.
341
+ const ganttZoom = new WeakMap();
342
+ function ganttConfig(ctrl, api, view, primaryField) {
343
+ ganttZoom.delete(ctrl);
344
+ const cols = fieldColumns(ctrl);
345
+ const start = cols.find((c) => c.id === view.dateField);
346
+ if (!start)
347
+ return undefined;
348
+ const end = cols.find((c) => c.id === view.endField);
349
+ const progress = cols.find((c) => c.id === view.progressField);
350
+ const deps = cols.find((c) => c.id === view.dependencyField && c.fieldType === 'link');
351
+ const idOfField = (field) => cols.find((c) => c.field === field)?.id;
352
+ const table = [idOfField(primaryField), start.id, end?.id].filter((id) => !!id);
353
+ const idOf = (row) => recordId(ctrl, api, row);
354
+ // Bars take the view's row colors, by a field or by conditions, as the rows
355
+ // do: the color the grid gives a row is its bar's.
356
+ const colorOf = view.color && view.color.mode !== 'none' ? (row) => rowMarkColor(ctrl, row) ?? undefined : undefined;
357
+ // The Gantt's ends are exclusive; a date-only field keeps the last day.
358
+ const endValue = (d) => lastDayValue(end, d);
359
+ const writeDates = (id, from, to) => {
360
+ writeCell(ctrl, id, start.id, dateValue(start, from));
361
+ if (end)
362
+ writeCell(ctrl, id, end.id, to.getTime() > from.getTime() ? endValue(to) : dateValue(end, from));
363
+ };
364
+ // A percent field holds 0 to 1 unless it says 0 to 100; the Gantt draws 0 to 100.
365
+ const points = !!(progress && fieldPropsOf(originalColumn(progress.def)).points);
366
+ const depsOf = (id) => {
367
+ const row = ctrl.internalData.find((r) => idOf(r) === id);
368
+ const v = row?.[deps.field];
369
+ return Array.isArray(v) ? v.map(String) : v == null || v === '' ? [] : [String(v)];
370
+ };
371
+ const editable = gridEditable(ctrl);
372
+ return {
373
+ searchable: false,
374
+ startField: start.field,
375
+ ...(end ? { endField: end.field } : {}),
376
+ ...(primaryField ? { titleField: primaryField } : {}),
377
+ tableColumns: [...new Set(table), '__duration'],
378
+ // The dates sit compact beside the bars; the record's name takes the rest.
379
+ tableColumnWidths: Object.fromEntries([start, end].filter((c) => !!c).map((c) => [c.id, holdsDays(c) ? 144 : 176])),
380
+ ...(colorOf ? { colorOf } : {}),
381
+ ...(view.zoom ? { zoom: view.zoom } : {}),
382
+ tooltip: true,
383
+ editable,
384
+ history: editable,
385
+ // A bar opens its record, as a card or a calendar event does.
386
+ onTaskOpen: (row) => openRecord({ ctrl, rowId: idOf(row) }),
387
+ onTaskMove: (e) => {
388
+ writeDates(idOf(e.row), e.start, e.end);
389
+ for (const s of e.subtree ?? [])
390
+ writeDates(idOf(s.row), s.start, s.end);
391
+ },
392
+ onTaskResize: (e) => writeDates(idOf(e.row), e.start, e.end),
393
+ // Records a moved task pushed later to keep its links legal.
394
+ onDependenciesChange: (moves) => {
395
+ for (const m of moves)
396
+ writeDates(m.id, m.start, m.end);
397
+ },
398
+ ...(progress
399
+ ? {
400
+ progressField: progress.field,
401
+ // The Gantt reads 0 to 100: a fraction field is scaled on the way in.
402
+ ...(points ? {} : { progressValue: (row) => Number(row?.[progress.field] ?? 0) * 100 }),
403
+ onProgressChange: (e) => writeCell(ctrl, idOf(e.row), progress.id, points ? e.progress : e.progress / 100),
404
+ }
405
+ : {}),
406
+ ...(deps
407
+ ? {
408
+ predecessorField: deps.field,
409
+ onDependencyAdd: (d) => {
410
+ const now = depsOf(d.to);
411
+ if (!now.includes(d.from))
412
+ writeCell(ctrl, d.to, deps.id, [...now, d.from]);
413
+ },
414
+ onDependencyRemove: (depId) => {
415
+ const [from, to] = depId.split('->');
416
+ if (from && to)
417
+ writeCell(ctrl, to, deps.id, depsOf(to).filter((x) => x !== from));
418
+ },
419
+ }
420
+ : {}),
421
+ // Kept with the view when it is left (captureView).
422
+ onZoomChange: (zoom) => {
423
+ ganttZoom.set(ctrl, zoom);
424
+ },
425
+ // A day picked on the chart: a record on it, opened to fill in.
426
+ onTaskAdd: (from, to) => {
427
+ const cfg = ctrl.props.rowAdder;
428
+ const defaults = cfg && typeof cfg === 'object' && typeof cfg.defaults === 'function' ? cfg.defaults() : {};
429
+ const row = newRowValues(ctrl, {
430
+ ...defaults,
431
+ [start.field]: dateValue(start, from),
432
+ ...(end ? { [end.field]: to.getTime() > from.getTime() ? endValue(to) : dateValue(end, from) } : {}),
433
+ });
434
+ api.addRow(row, 'bottom');
435
+ ctrl.props.onRowAdd?.({ row });
436
+ queueMicrotask(() => openRecord({ ctrl, rowId: idOf(row), editPrimary: true }));
437
+ },
438
+ // By id: the Gantt hands over a copy of the row.
439
+ onTaskDelete: (row) => deleteRecord(ctrl, api, row),
440
+ };
441
+ }
442
+ // ---- Capture and apply ------------------------------------------------------
443
+ /** A new view: no filter, sorts, groups or hidden fields. */
444
+ export const PLAIN_STATE = { sorting: [], grouping: [], hiddenColumns: [], advancedFilter: null };
445
+ /** The view as the grid now shows it, to keep when the view is left. */
446
+ export function captureView(ctrl, api, view) {
447
+ const state = { ...api.getState() };
448
+ // Paging, charts and the search box's text are not part of a view.
449
+ delete state.pagination;
450
+ delete state.chart;
451
+ delete state.charts;
452
+ delete state.chartActive;
453
+ delete state.globalFilter;
454
+ const rowHeight = recordHeightOf(api.getOption('rowHeight'));
455
+ const zoom = view.type === 'gantt' ? ganttZoom.get(ctrl) : undefined;
456
+ // No colors chosen (by a view or the column menu): the `rowColor` prop is in
457
+ // force, and the view keeps no color setting of its own.
458
+ const { color: _color, ...rest } = view;
459
+ const color = chosenRowColor(ctrl) === undefined ? undefined : colorSettingOf(ctrl, view.color);
460
+ return {
461
+ ...rest,
462
+ state,
463
+ ...(typeof rowHeight === 'number' ? { rowHeight } : {}),
464
+ ...(zoom ? { zoom } : {}),
465
+ ...(color ? { color } : {}),
466
+ };
467
+ }
468
+ /** Show a view: its grid state, row height, colors, and the grid's mode for its type. */
469
+ export function applyView(ctrl, api, view, opts = {}) {
470
+ // A view not shown before starts plain; one saved before a filter existed
471
+ // clears the filter now on. The search box starts empty in every view.
472
+ api.setState({ advancedFilter: null, ...(view.state ?? PLAIN_STATE), globalFilter: '' });
473
+ // A grouped view opens with its groups open, unless it kept which were.
474
+ const grouping = view.state;
475
+ // The grouped rows exist once the grid has taken the new state: open them then.
476
+ if (grouping?.grouping?.length && grouping.expanded === undefined)
477
+ setTimeout(() => api.expandAllGroups?.(), 0);
478
+ if (view.rowHeight != null)
479
+ api.setOption('rowHeight', view.rowHeight);
480
+ // A view with no color setting shows the `rowColor` prop's colors.
481
+ if (view.color)
482
+ applyColor(ctrl, view.color, opts.linkNames);
483
+ else
484
+ clearRowColorChoice(ctrl);
485
+ api.setOption('board', view.type === 'kanban' ? boardConfig(ctrl, api, view) : undefined);
486
+ api.setOption('gallery', view.type === 'gallery' ? galleryConfig(ctrl, api, view) : undefined);
487
+ // A record's title on a calendar or Gantt: the primary field.
488
+ const id = primaryIdOf(ctrl);
489
+ const primary = opts.primaryField ?? fieldColumns(ctrl).find((c) => c.id === id)?.field;
490
+ api.setOption('scheduler', view.type === 'calendar' ? dateConfig(ctrl, api, view, primary) : undefined);
491
+ api.setOption('gantt', view.type === 'gantt' ? ganttConfig(ctrl, api, view, primary) : undefined);
492
+ }
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ /**
3
+ * "Customize chart": the chart's kind, the field the records are grouped
4
+ * by, and the value - the number of records, or a number field's sum or
5
+ * average. Each change shows at once and is kept in the view.
6
+ */
7
+ import { panelFocus } from './panel-focus'
8
+ import { CHART_KINDS, chartXColumns, chartYColumns, type ChartKind, type ChartSetting } from '../chart-view'
9
+
10
+ let { ctrl, setting, onChange }: { ctrl: any; setting: ChartSetting; onChange: (next: ChartSetting) => void } = $props()
11
+
12
+ const xs = $derived(chartXColumns(ctrl))
13
+ const ys = $derived(chartYColumns(ctrl))
14
+ const hasX = $derived(xs.some((c) => c.id === setting.x))
15
+ const valueOf = (e: Event) => (e.currentTarget as HTMLSelectElement).value
16
+
17
+ function setY(v: string) {
18
+ const { y: _old, aggregation: _agg, ...rest } = setting
19
+ onChange(v ? { ...rest, y: v, aggregation: setting.aggregation ?? 'sum' } : rest)
20
+ }
21
+ </script>
22
+
23
+ <div class="sv-fvb-panel sv-fchart-panel" use:panelFocus>
24
+ <p class="sv-fvb-title">Chart</p>
25
+ <label class="sv-fchart-field">
26
+ <span>Type</span>
27
+ <select class="sv-fvb-select" value={setting.kind} onchange={(e) => onChange({ ...setting, kind: valueOf(e) as ChartKind })}>
28
+ {#each CHART_KINDS as k (k.kind)}<option value={k.kind}>{k.label}</option>{/each}
29
+ </select>
30
+ </label>
31
+ <label class="sv-fchart-field">
32
+ <span>Group by</span>
33
+ <select class="sv-fvb-select" value={setting.x} onchange={(e) => onChange({ ...setting, x: valueOf(e) })}>
34
+ <!-- No field yet (or the one picked was deleted): a prompt, so any pick is a change. -->
35
+ {#if !hasX}<option value={setting.x} disabled>Choose a field</option>{/if}
36
+ {#each xs as c (c.id)}<option value={c.id}>{c.header}</option>{/each}
37
+ </select>
38
+ </label>
39
+ <label class="sv-fchart-field">
40
+ <span>Value</span>
41
+ <select class="sv-fvb-select" value={setting.y ?? ''} onchange={(e) => setY(valueOf(e))}>
42
+ <option value="">Number of records</option>
43
+ {#each ys as c (c.id)}<option value={c.id}>{c.header}</option>{/each}
44
+ </select>
45
+ </label>
46
+ {#if setting.y}
47
+ <label class="sv-fchart-field">
48
+ <span>Combine</span>
49
+ <select class="sv-fvb-select" value={setting.aggregation ?? 'sum'} onchange={(e) => onChange({ ...setting, aggregation: valueOf(e) as 'sum' | 'avg' })}>
50
+ <option value="sum">Sum</option>
51
+ <option value="avg">Average</option>
52
+ </select>
53
+ </label>
54
+ {/if}
55
+ </div>
56
+
57
+ <style>
58
+ .sv-fchart-panel { display: flex; flex-direction: column; gap: 10px; min-width: 260px; }
59
+ .sv-fchart-field { display: grid; grid-template-columns: 90px 1fr; align-items: center; gap: 8px; font-size: 13px; }
60
+ .sv-fchart-field > span { color: var(--sg-muted, #64748b); }
61
+ </style>
@@ -0,0 +1,9 @@
1
+ import { type ChartSetting } from '../chart-view.js';
2
+ type $$ComponentProps = {
3
+ ctrl: any;
4
+ setting: ChartSetting;
5
+ onChange: (next: ChartSetting) => void;
6
+ };
7
+ declare const ChartPanel: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ChartPanel = ReturnType<typeof ChartPanel>;
9
+ export default ChartPanel;